使用 distutils 构建基于 ctypes 的 C 库

Posted

技术标签:

【中文标题】使用 distutils 构建基于 ctypes 的 C 库【英文标题】:Building a ctypes-"based" C library with distutils 【发布时间】:2011-05-30 14:18:58 【问题描述】:

在this recommendation 之后,我编写了一个本地 C 扩展库来通过 ctypes 优化 Python 模块的一部分。我选择了 ctypes 而不是编写 CPython 原生库,因为它更快更容易(只有几个函数,里面有所有紧密的循环)。

我现在遇到了一个障碍。如果我希望使用 distutils 使用 python setup.py install 轻松安装我的工作,那么 distutils 需要能够构建我的共享库并安装它(大概安装到 /usr/lib/<em>myproject</em> 中)。但是,这不是 Python 扩展模块,据我所知,distutils 无法做到这一点。

我找到了一些其他有此问题的人的参考资料:

Someone on numpy-discussion with a hack back in 2006. Somebody asking on distutils-sig and not getting an answer. Somebody asking on the main python list and being pointed to the innards of an existing project。

我知道我可以做一些原生的事情,而不是使用 distutils 作为共享库,或者确实使用我的发行版的打包系统。我担心这会限制可用性,因为不是每个人都能轻松安装它。

所以我的问题是:使用 distutils 分发共享库的当前最佳方式是什么,它将被 ctypes 使用,但其他方式是 OS-native 而不是 Python 扩展模块?

如果您可以扩展它并证明为什么这是最好的方法,请随意回答上面链接的黑客之一。如果没有更好的办法,至少所有信息都在一个地方。

【问题讨论】:

【参考方案1】:

我在这里设置了一个带有 ctypes 扩展的最小工作 python 包: https://github.com/himbeles/ctypes-example 适用于 Windows、Mac、Linux。

它采用上面memeplex 的方法覆盖build_ext.get_export_symbols() 并强制所有操作系统的库扩展名相同(.so)。 此外,c / c++ 源代码中的编译器指令可确保在 Windows 与 Unix 的情况下正确导出共享库符号。 作为奖励,二进制***由 GitHub Action 自动编译,适用于所有操作系统 :-)

【讨论】:

【参考方案2】:

distutils 文档 here 指出:

CPython 的 C 扩展是一个共享库(例如 Linux 上的 .so 文件,Windows 上的 .pyd),它导出初始化函数。

因此,关于普通共享库的唯一区别似乎是初始化函数(除了合理的文件命名约定,我认为您没有任何问题)。现在,如果您查看distutils.command.build_ext,您会看到它定义了一个get_export_symbols() 方法:

返回共享扩展必须导出的符号列表。这要么使用“ext.export_symbols”,或者如果未提供,则使用“PyInit_”+ module_name。仅适用于 Windows,其中 .pyd 文件 (DLL) 必须导出模块“PyInit_”函数。

因此,将它用于普通共享库应该是开箱即用的,除了在 视窗。但也很容易解决这个问题。 get_export_symbols() 的返回值被传递给 distutils.ccompiler.CCompiler.link(),文档说明:

'export_symbols' 是共享库将导出的符号列表。 (这似乎只适用于 Windows。)

因此,不将初始化函数添加到导出符号就可以了。为此,您只需简单地覆盖build_ext.get_export_symbols()

此外,您可能希望简化模块名称。下面是一个完整的build_ext 子类示例,它可以构建 ctypes 模块以及扩展模块:

from distutils.core import setup, Extension
from distutils.command.build_ext import build_ext


class build_ext(build_ext):

    def build_extension(self, ext):
        self._ctypes = isinstance(ext, CTypes)
        return super().build_extension(ext)

    def get_export_symbols(self, ext):
        if self._ctypes:
            return ext.export_symbols
        return super().get_export_symbols(ext)

    def get_ext_filename(self, ext_name):
        if self._ctypes:
            return ext_name + '.so'
        return super().get_ext_filename(ext_name)


class CTypes(Extension): pass


setup(name='testct', version='1.0',
      ext_modules=[CTypes('ct', sources=['testct/ct.c']),
                   Extension('ext', sources=['testct/ext.c'])],
      cmdclass='build_ext': build_ext)

【讨论】:

setuptools 已经过时了怎么办?【参考方案3】:

这里有一些说明:

    它不是“基于 ctypes”的库。它只是一个标准的 C 库,你想用 distutils 安装它。如果您使用 C 扩展,ctypes 或 cython 来包装该库与问题无关。

    由于该库显然不是通用的,而仅包含针对您的应用程序的优化,因此您链接到的建议不适用于您,在您的情况下,编写 C 扩展或使用 Cython,在这种情况下可以避免您的问题。

对于实际问题,您始终可以使用自己的自定义 distutils 命令,事实上,与此类命令相关的讨论之一是 OOF2 build_shlib 命令,它可以满足您的需求。在这种情况下,虽然你想安装一个真正不共享的自定义库,然后我认为你不需要将它安装在 /usr/lib/yourproject 中,但是你可以将它安装到 /usr 的包目录中/lib/python-xx/site-packages/yourmodule,连同你的 python 文件。但我不能 100% 确定这一点,所以你必须尝试一下。

【讨论】:

以上是关于使用 distutils 构建基于 ctypes 的 C 库的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 包的 distutils 安装的构建阶段编译 CoffeeScript?

python安装pyffmpeg,cython报错:ImportError: No module named Cython.Distutils

CC for python distutils 构建c

基于Cython和内置distutils库,实现python源码加密(非混淆模式)

如何使用 distutils 安装 Python 扩展模块?

构建 ctypes 类的简洁方式