【问题标题】:Statically link python37.dll and vcruntime140.dll when using cython --embed使用 cython --embed 时静态链接 python37.dll 和 vcruntime140.dll
【发布时间】:2020-10-11 00:39:40
【问题描述】:

假设我正在“cythonizing”这个test.py

import json
print(json.dumps({'key': 'hello world'}))

与:

cython test.py --embed
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
cl test.c /I C:\Python37\include /link C:\Python37\libs\python37.lib

正如Minimal set of files required to distribute an embed-Cython-compiled code and make it work on any machine 中提到的,有必要将python37.dllvcruntime140.dll 以及Lib\ 的内容(作为Lib\ 或打包到python37.zip)以及@987654333 @文件。

问题:如何修改cl.exe ...命令让编译器在test.exe文件中静态链接python37.dllvcruntime140.dll

(因此不再需要单独发送 python37.dllvcruntime140.dll

【问题讨论】:

  • 参见Building Python Statically 和其中的链接。
  • @dxiv 谢谢。这是否意味着它意味着要完全重建 Python?我可以想象这需要很多时间? (遗憾的是,此页面上的最后一个链接是 404)。 Windows 的详细教程会很有趣(使用cl.exe 而不是 gcc 等)
  • 我的理解是“是的”,尽管我没有亲身尝试过。损坏链接的缓存副本是here
  • vcruntime 非常简单:stackoverflow.com/a/36270902/5769463。 windows上的静态链接python比linux上的要复杂一些
  • 谢谢@ead 我将首先从 vcruntime 开始。您将如何使用 cl.exe 从命令行执行此操作?只需添加 /MT ?这似乎太简单了 :) 因为我不使用 MSVC++ IDE,所以我需要从 cl.exe 执行此操作

标签: python visual-c++ dll cython cl


【解决方案1】:

虽然在 Linux 上创建静态链接的嵌入式 Python 可执行文件相对容易(例如,参见 SO-post),但在 Windows 上要复杂得多。而你可能不想这样做。

结果可能不是人们所期望的:由于 dll 与 Linux 的共享对象相比的限制,生成的静态 python 版本将无法使用/加载任何其他 c 扩展,因为支持的-in 在编译/链接期间。

我也不建议从 vcruntime-dll 切换到其静态版本 - 只有当 所有内容(exe、c-extensions、其他依赖于 vcruntime 的 dll)都是静态的时才有意义链接到一个巨大的可执行文件中。

第一个绊脚石:虽然在 Linux 上的 Python 发行版通常已经发布了静态 Python 库,但 Windows 发行版只有 dll,无法静态链接。

因此需要在 Windows 上构建一个静态库。一个很好的起点是link

下载正确 Python 版本 (git clone --depth=1 --branch v3.8.0 https://github.com/python/cpython.git) 的源代码后,您可以转到 cpython\PCBuild 并按照文档中的说明构建 cpython(可能因版本而异)。

在我的情况下是

cd cpython/PCbuild
.\build.bat -e -p x64 

不,我们有一个正常运行的 Python3.8 安装,可以在 cpython/PCbuild/amd64 中找到。创建文件夹cpython/PCbuild/static_amd64并添加以下pyx文件:

#hello.pyx
print("I'm standalone")

暂时将python38.dll复制到static_amd64

现在让我们用嵌入式 python 解释器构建我们的程序:

cython --embed -3 hello.pyx
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
cl /c hello.c /Fohello.obj  /nologo /Ox /W3 /GL /DNDEBUG /MD -I<path_to_code>\cpython\include -I<path_to_code>\cpython\PC
link hello.obj python38.lib  /OUT:hello_prog.exe /nologo "/LIBPATH:<path_to_code>\cpython\PCbuild\amd64"

在开始之后,hello_prog.exe 对我们说谎,因为它并不是真正独立的。好消息是:它找到了所需的 Python 安装,例如 here

现在让我们创建一个静态 python38 库。为此,我们在 cpython/PCbuild-folder 中打开 pcbuild.sln 并更改 pythoncore-project 的设置以在 PCbuild\amd64_static-folder 中生成静态库。重建它。

现在我们可以构建 Embedded-python-exe:

cl /c hello.c /Fohello.obj /D "Py_NO_ENABLE_SHARED" /nologo /Ox /W3 /GL /DNDEBUG /MD -I<path_to_code>\cpython\include -I<path_to_code>\cpython\PC
link hello.obj python38.lib "version.lib" "shlwapi.lib" "ws2_32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /OUT:hello_prog.exe /nologo "/LIBPATH:<path_to_code>\cpython\PCbuild\static_amd64"

与针对 dll 的构建相比,我们必须更改以下内容:

  • Py_NO_ENABLE_SHARED(即/D "Py_NO_ENABLE_SHARED")被添加到预处理器定义中,否则链接器会查找错误的符号。
  • 由python-dll带来的Windows依赖项(即version.lib等)现在需要显式传递给链接器(这可以在pythoncore-project的链接器命令行中查找)。
  • lib 路径显示到静态文件夹,即"/LIBPATH:&lt;path_to_code&gt;\cpython\PCbuild\static_amd64" now。
  • 可能还有其他较小的问题(不同的优化级别、链接时间代码生成、禁用整个程序优化等),具体取决于您的确切工具链。

我们现在可以从static_amd64 中删除python38.dllhello_prog.exe 仍然有效。

在 Linux 上,这将是“任务完成”,在 Windows 上,我们才刚刚开始......

确保cpython-folder 有一个DLLs-folder,其中包含所有正确的pyd-files,否则从PCbuild/amd64-folder 创建并复制所有pyd-files。

让我们的 pyx 文件稍微复杂一点:

import _decimal
print("I'm standalone")

_decimaldecimal 模块的快速实现,它是一个 C 扩展,可以在 DLL 文件夹中找到。

在 cythonizing 和构建之后,运行 hello_prog.exe 会导致以下错误消息:

import _decimal
ImportError: DLL load failed while importing _decimal: The specified module could not be found.

问题很容易找到:

dumpbin /DEPENDENTS ../amd64/_decimal.pyd
...
python38.dll
... 

我们安装的扩展仍然依赖于 python-dll。让我们针对静态库重新构建它们——我们需要将库路径从amd64 更改为static_amd64,添加预处理器定义Py_NO_ENABLE_SHARED 和所有缺少的windows-libraries(即“version.lib”& Co.)和将/EXPORT:PyInit__decimal添加到链接选项,否则,由于Py_NO_ENABLE_SHAREDbecomes invisible。结果不依赖于python-dll!我们将它复制到DLLs文件夹并...

hello_prog.exe
# crash/stopped worked

发生了什么?我们违反了一个定义规则 (ODR),最终得到了两个 Python 解释器:一个来自 hello_prog.exe,它已初始化,另一个来自 _decimal.pyd,它未初始化。 _decimal.pyd 对其未初始化的解释器“说话”,并且发生了不好的事情。

与 Linux 的区别在于共享对象和 dll 之间的区别:虽然共享对象可以使用 exe 中的符号(如果 exe 是使用正确的选项构建的),但 dll 不能因此必须依赖于 dll(我们不想要)或需要有自己的版本。

为了避免违反 ODR,我们只有一个出路:它必须直接链接到我们的 hello_word-executable。因此,让我们将_decimal 的项目更改为静态库并在static_amd64-文件夹中重建它。从“DLLs”文件夹中删除 pyd 并将/WHOLEARCHIVE:_decimal.lib 添加到链接器命令行(整个存档,否则链接器将丢弃_decimal.lib,因为它的符号在某处都没有被引用),导致一个可执行文件,它出现以下错误:

ModuleNotFoundError: No module named '_decimal'

这是意料之中的 - 我们需要告诉解释器,模块 _decimal 已被备份,不应在 python 路径上搜索。

这个问题通常的解决方案是在Py_Initialize之前使用PyImport_AppendInittab,这意味着我们需要更改cython生成的c文件(可能有workarounds,但由于multi-phase initialization它是没那么容易。因此,嵌入 Python 的一种更明智的方法可能是提供的 hereheremain 不是由 Cython 编写的)。 c 文件应如下所示:

//decalare init-functions
extern  PyObject* PyInit__decimal();
...
int main(int argc, char** argv) {
...
    if (argc && argv)
        Py_SetProgramName(argv[0]);
    PyImport_AppendInittab("_decimal", PyInit__decimal); //  HERE WE GO
                                                         //  BEFORE Py_Initialize
    
    Py_Initialize();

现在构建所有内容会生成一个打印的 exe

I'm standalone

这一次不是在骗我们!

现在我们必须为我们需要的所有其他内置扩展重复最后的步骤。


以上意味着静态构建的 python-interpreter 有一些限制:所有内置模块都需要备份到可执行文件中,我们不能使用 numpy/scipy 之类的库来扩展解释器(但可以直接这样做在编译/链接时)。


摆脱 vcruntime-dll 更容易:以上所有步骤都必须完成with /MT option instead of MD-option。但是,由于使用其他 dll(例如 _ctypes 需要 ffi-dll)可能会出现一些问题,这些 dll 是使用 dll 版本构建的(因此我们再次违反了 ODR) - 所以我不推荐它。

【讨论】:

  • 如果您尝试“类似于”Linux 方式会怎样。将静态 python 库链接到您的可执行文件,所以这个可执行文件导出所有 python 函数?当然,扩展也必须“链接”到您的可执行文件而不是 python dll,因此您只能将它用于这个可执行文件。
  • @ssbssa 问题是,MSVC 没有 -Xlinker -export-dynamic,这就是 python exe 只是 dll 的包装器的原因,您需要将 c-extensions 链接到它 - 无法链接到 exe。使用 gcc-ports 编译时它可能会起作用:我没有尝试过。
  • 是的,你可以。而且我认为如果可执行文件有任何导出的函数,MSVC 甚至会自动创建一个导入库。
  • @ssvssa 你能提供一个链接吗?从未见过有人这样做。
  • 我不确定你想要什么链接。 thisthis 之类的东西?肯定有人这样做过(包括我)。
猜你喜欢
  • 2010-09-30
  • 1970-01-01
  • 1970-01-01
  • 2011-05-21
  • 2017-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-02
相关资源
最近更新 更多