PyPy 的 ctypes 实现

总结

术语

  • 应用程序级代码 - 用完整 Python 编写的代码
  • 解释器级代码 - 用 RPython 编写的代码,编译成其他东西,比如 C,解释器的一部分。

PyPy 的 ctypes 实现,在其当前状态下证明了实现一个模块的可行性,该模块具有与 CPython 的 ctypes 相同的接口和行为。

PyPy 的实现内部使用 libffi,就像 CPython 的 ctypes 一样。在我们的实现中,尽可能多的代码是用完整的 Python 编写的,而不是 RPython。在 CPython 的情况下,等效的操作是尽可能少地用 C 编写代码。我们本质上更倾向于快速实验,而不是担心这个第一个试用实现的速度。这使得我们能够在 2 个月的实际时间内提供一个具有 ctypes 功能大部分的可用实现。

我们重用了 CPython 中的 ctypes 包版本 1.0.2,原封不动。我们实现了 _ctypes,它是一个 C 模块,在 CPython 中主要用纯 Python 实现,基于一个更底层的扩展模块 _rawffi

底层部分:_rawffi

这个 PyPy 扩展模块 (pypy/module/_rawffi) 提供了一个简单的接口来创建 C 对象(数组和结构)并通过 libffi 调用动态库中的函数。在大多数情况下释放对象,并确保引用彼此的对象保持活动,这是更高层的责任。

这个模块使用 libffi 的绑定,这些绑定在 rpython/rlib/libffi.py 中定义。

我们试图使这个模块尽可能小。可以想象,其他实现(例如 Jython)可以通过编写自己的 _rawffi 版本来使用我们的 ctypes 实现。

高级部分

重用的 ctypes 包位于 lib_pypy/ctypes 中。实现与 CPython 中 _ctypes 相同接口的 _ctypes 位于 lib_pypy/_ctypes 中。

讨论和限制

重新实现 ctypes 功能通常是可能的。PyPy 支持可插拔的垃圾收集器,其中一些是移动收集器,这意味着将 Python 对象中的直接引用传递给外部库的策略是不可行的(除非 GC 支持固定,而现在还没有)。这样做的结果是,有时需要复制而不是共享,这可能会导致一些语义差异。用 _rawffi 本身创建的 C 对象在 GC 堆之外分配,这样就可以将它们传递给外部函数,而无需担心。

将实现移植到解释器级别可能会提高其速度。此外,当前的分层和当前的 _rawffi 接口比严格必要的需要更多对象分配和复制;这也可以改进。

以下是当前实现的限制和缺失功能列表

  • ctypes.pythonapi 缺失。在之前的版本中,它存在并重定向到 cpyext C API 模拟层,但我们的实现没有对 GIL 做任何明智的处理,并且函数名称多了一个“Py”,例如 PyPyInt_FromLong()。它被移除是因为它没有帮助。
  • 我们复制 Python 字符串,而不是使用指向原始缓冲区的指针
  • 我们没有实现的功能
    • 自定义对齐和位域
    • 调整大小(resize() 函数)
    • 非原生字节序对象
    • 接受按值传递结构的回调函数
    • ctypes 在其基本类型和用户对基本类型的子类之间存在细微的语义差异

运行应用程序示例

pyglet 已知可以运行。我们还尝试了 pygame-ctypes(它不再维护)和 pysqlite-ctypes 的快照,并取得了一些成功。我们只描述如何运行 pyglet 示例。

pyglet

我们尝试从其存储库中检出 pyglet,版本为 1984。

从 pyglet 中,以下示例已知可以运行

  • opengl.py
  • multiple_windows.py
  • events.py
  • html_label.py
  • timer.py
  • window_platform_event.py
  • fixed_resolution.py

用于运行 ctypes 测试的 pypy-c 翻译也可以用来运行 pyglet 示例。它们可以像这样运行,例如

$ cd pyglet/
$ PYTHONPATH=. ../ctypes-stable/pypy/goal/pypy-c examples/opengl.py

它们通常应该使用 ctrl-c 终止。有关它们应该如何运行的详细信息,请参阅它们的文档字符串。

以下示例由于与 ctypes 无关的原因无法运行

  • image_convert.py 需要 PIL
  • image_display.py 需要 PIL
  • astraea/astraea.py 需要 PIL

我们没有尝试以下示例

  • media_player.py 需要 avbin 或至少需要为 .wav 文件设置合适的声卡
  • video.py 需要 avbin
  • soundscape 需要 avbin