【问题标题】:Python Extension Dll InstallationPython扩展DLL安装
【发布时间】:2021-06-19 07:34:27
【问题描述】:

我有一个用 C++ 编写的大型程序,我希望通过 Python 使其可用。我编写了一个 python 扩展来公开一个接口,python 代码可以通过该接口调用 C++ 函数。我遇到的问题是安装似乎很重要。

我能找到的所有文档似乎都表明我应该创建一个setup.py,它会创建一个distutils.core.Extension。在我发现的每个示例中,正在创建的 Extension 对象都被提供了一个源文件列表,它会编译这些文件。如果我的代码是一两个文件,那就没问题了。不幸的是,它有几十个文件,并且我使用了一些相对复杂的 Visual Studio 构建设置。因此,至少可以说,通过列出 .c 文件进行构建似乎具有挑战性。

我目前已将我的 Python 扩展配置为构建为 .dll 并链接到 python39.lib。我尝试将扩展名更改为 .pyd 并将文件包含在 manifest.in 中。在我创建 setup.py 并运行它之后,它创建了一个 .egg 文件,我验证该文件确实包含我创建的 .pyd。但是,安装后,当我将模块导入python时,模块完全是空的(我验证了 PyInit_[module] 函数没有被调用)。 Python dll Extension Import 说如果我将扩展名更改为 .pyd 并将文件放在 python 安装的 Dlls 目录中,我可以导入 dll。我遇到了两个问题。

首先,在我看来,它不是像这样可分发的。我想把它打包成一个 python 轮子,我不确定轮子是如何做到这一点的。第二个问题更大——它并不完全有效。它调用了我的扩展的初始化函数,我已经在 WinDbg 中验证了它正在返回一个 python 模块。然而,这是我总是从控制台得到的。

>>> import bluespawn
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
SystemError: initialization of bluespawn did not return an extension module

Python 文档中有一个关于发布二进制扩展的部分,但在过去的四年里,它一直被用作占位符。链接到here 的 github 问题也不是很有帮助;它归结为使用 distutils 构建或使用 enscons 构建。但是由于我的构建是一个相当复杂的过程,至少可以说,完全重写它以使用 enscons 是不可取的。

在我看来,将文件放在 DLLs 目录中是错误的做法。鉴于我有一个 DLL 并且让 setuptools 编译一切本身似乎是不可行的,我应该如何安装我的扩展程序?

作为参考,这是我的初始化函数,以防不正确。

PyModuleDef bsModule{ PyModuleDef_HEAD_INIT, "bluespawn", "Bluespawn python bindings", -1, methods };
 
PyMODINIT_FUNC PyInit_bluespawn() {
    PyObject* m; 
    Py_Initialize();
    PyEval_InitThreads();
    PyGILState_STATE state = PyGILState_Ensure(); // Crashes without this. Call to PyEval_InitThreads() required for this. 
    m = PyModule_Create(&bsModule);
    PyGILState_Release(state);
    Py_Finalize();
    return m;
}

python 接口在这里可用:https://github.com/ION28/BLUESPAWN/blob/client-add-pylib/BLUESPAWN-win-client/src/user/python/PythonInterface.cpp

编辑:我有一个可行的解决方案,我确定不是最佳实践。我创建了一个非常小的 C 文件,它只是将它接收到的所有调用传递到我已经创建的大型 DLL 上。 C 文件负责初始化模块,但其他一切都在 DLL 中处理。它有效,但它似乎是一种非常糟糕的做事方式。我正在寻找的是一种更好的方法。

【问题讨论】:

  • 那么 .pyd 在导入时是否有效?然后在外部构建它并仅从 setup.py 引用它是一个好主意(将其包含在 .whl 中 - 它应该将其安装在 站点包 目录)。但是您分享的文件很奇怪:它不是核心模块,然后是那些空函数定义,然后是所有extern "C" __declspec(dllexport),并且PyInit函数也不存在。我不确定您打算从中导出什么。
  • 也许stackoverflow.com/questions/49493537/…stackoverflow.com/questions/61692747/… 可能会有所帮助。我可以帮助你继续前进,但是这周我有点被抓住了。
  • 我可以建议您查看pybind11 的绑定吗?这是一个很棒的库,它可能比自己编写绑定代码更容易。
  • 在与 PythonInterface.cpp 相同的文件夹中有一个名为 bs_shims.c 的文件,我目前正在使用它。它与生成的 dll 一起放入 build 文件夹中,并由 setup.py 构建。它负责调用 PyInit,其中的模块定义将引用传递给 Dll 中的导出函数。

标签: python c++ windows


【解决方案1】:

让我试着把你的帖子分成两个不同的问题:

  1. 如何使用 setuptools 将 C++ 库打包成一个非平凡的编译过程
  2. 是否可以分发带有预编译库的 python 包

1。如何使用 setuptools 打包具有非平凡编译过程的 C++ 库

这是可能的。看到setuptools 提供了许多覆盖编译过程的方法,我感到非常惊讶,请参阅文档here。例如,您可以使用关键字参数extra_compile_args 将额外的参数传递给编译器。 另外,由于setup.py是一个python文件,你可以相对容易地编写一些代码来自动收集编译所需的所有文件。我自己在一个项目 (github) 中完成了这项工作,对我来说效果很好。

这是来自setup.py的一些代码:

libinjector = Extension('pyinjector.libinjector',
                        sources=[str(c.relative_to(PROJECT_ROOT))
                                 for c in [LIBINJECTOR_WRAPPER, *LIBINJECTOR_SRC.iterdir()]
                                 if c.suffix == '.c'],
                        include_dirs=[str(LIBINJECTOR_DIR.relative_to(PROJECT_ROOT) / 'include')],
                        export_symbols=['injector_attach', 'injector_inject', 'injector_detach'],
                        define_macros=[('EM_AARCH64', '183')])

2。是否可以分发带有预编译库的python包

我从您的编辑中了解到,您已经设法让它发挥作用,但我还是要说几句话。可以使用源代码分发发布预编译的二进制文件,也可以在 wheel 文件中发布手动编译的二进制文件,但不建议这样做。

主要原因是与目标架构的兼容性。首先,您必须在您的发行版中包含两个 DLL,一个用于 x64,一个用于 x86。其次,您可能会失去一些不错的优化,因为您必须指示编译器忽略可用于特定 CPU 类型的优化(请注意,这也适用于正常的车轮分布)。如果您正在针对 Windows SDK 进行编译,您可能也希望使用用户的版本。此外,在您的版本中包含两个 DLL 可能会使其大小变得难以用于源代码分发。

【讨论】:

  • 我正在针对 Windows XP sdk 进行编译以实现很多向后兼容性。我希望它易于安装,这意味着我不希望人们必须拥有整个 Windows SDK 以及我链接的所有库。构建过程(包括 vcpkg 依赖项)大约需要 45 分钟,这绝对不够理想。我可以只复制用于构建和链接的确切命令行,但我也使用了很多自定义构建步骤,并且需要构建(和使用)一些自定义依赖项。简而言之,在主机上构建完整的项目是我最后的选择。
  • 我的同情 :) 我从您的编辑中了解到您让它工作了,不是吗?您现在面临的问题是什么?
  • 我得到了它的工作,但我的解决方案绝对不理想。它涉及将 DLL 放到系统上,然后使用 distutils 构建单个源文件,该文件在运行时链接到 DLL。据我了解, distutils 和 setuptools 都会产生一个 .egg-info 和一个 .pyd 文件。我看不出为什么我不能自己制作这个 .egg-info 和 .pyd,这样我就可以安装而不需要在主机上构建。
  • 我会注意到,我将 DLL(重命名为 .pyd)的问题诊断为使用单独的 python 运行时与我的库有关的问题。所以它返回的模块是在单独的python运行时初始化的,在python解释器的运行时是无效的。
  • 所以现在您可以手动导入.pyd,并且只想了解如何将其捆绑到您的发行版中而不至于太笨拙?
猜你喜欢
  • 1970-01-01
  • 2021-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-15
  • 2015-01-29
  • 1970-01-01
相关资源
最近更新 更多