使用 cython --embed 时静态链接 python37.dll 和 vcruntime140.dll

Bas*_*asj 3 python dll cython visual-c++ cl

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

import json
print(json.dumps({'key': 'hello world'}))
Run Code Online (Sandbox Code Playgroud)

和:

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
Run Code Online (Sandbox Code Playgroud)

正如分发嵌入 Cython 编译的代码并使其在任何机器上工作所需的最小文件集中提到的,有必要沿着文件分发python37.dllvcruntime140.dll的内容Lib\(作为Lib\或打包到 a 中python37.ziptest.exe

问题:如何修改cl.exe ...命令来要求编译器静态链接python37.dll到文件vcruntime140.dll内部test.exe

(这样就不再需要运输python37.dll和单独)vcruntime140.dll

ead*_*ead 5

备注:可能 比下面介绍的更好/更明智/更简单的选择。

这两种方法之间的主要区别:虽然在这种方法中,所有 C 扩展都必须备份到生成的可执行文件中,但在替代方法中,C 扩展是单独编译的,或者也可以稍后将附加 C 扩展添加到发行版中。


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

另外,结果可能不是人们所期望的:由于 dll 与 Linux 共享对象相比的限制,生成的静态 python 版本将无法使用/加载任何其他 c 扩展,因为在编译/链接时间(注意:这并不完全正确,此答案中提出了一种解决方法)。

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

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

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

下载正确的 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 
Run Code Online (Sandbox Code Playgroud)

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

#hello.pyx
print("I'm standalone")
Run Code Online (Sandbox Code Playgroud)

暂时复制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"
Run Code Online (Sandbox Code Playgroud)

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

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

现在我们可以构建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"
Run Code Online (Sandbox Code Playgroud)

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

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

我们可以python38.dllstatic_amd64现在开始删除它,但它hello_prog.exe仍然有效。

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

确保cpython-folder 具有DLLs包含所有正确 pyd 文件的 -folder,否则从 -folder 创建并复制所有 pyd 文件PCbuild/amd64

让我们让 pyx 文件变得更复杂一点:

import _decimal
print("I'm standalone")
Run Code Online (Sandbox Code Playgroud)

_decimal是 -module 的快速实现,decimal它是 C 扩展,可以在 -folder 中找到 DLL

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

import _decimal
ImportError: DLL load failed while importing _decimal: The specified module could not be found.
Run Code Online (Sandbox Code Playgroud)

问题很容易发现:

dumpbin /DEPENDENTS ../amd64/_decimal.pyd
...
python38.dll
... 
Run Code Online (Sandbox Code Playgroud)

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

hello_prog.exe
# crash/stopped worked
Run Code Online (Sandbox Code Playgroud)

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

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

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

ModuleNotFoundError: No module named '_decimal'
Run Code Online (Sandbox Code Playgroud)

这是预期的 - 我们需要告诉解释器,该模块_decimal已备份并且不应在 python 路径上搜索。

这个问题的通常解决方案是使用PyImport_AppendInittabjust before Py_Initialize,这意味着我们需要更改 cython 生成的 c 文件(可能有解决方法,但由于多阶段初始化,这并不容易。所以可能是一种更明智的方法embed Python 是这里这里介绍的,不是main由 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();
Run Code Online (Sandbox Code Playgroud)

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

I'm standalone
Run Code Online (Sandbox Code Playgroud)

这一次它没有骗我们!

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


上述意味着静态构建的 python 解释器有一些限制:所有内置模块都需要备份到可执行文件中,并且我们不能使用 numpy/scipy 等库扩展解释器(但可以直接在编译时执行此操作) /链接时间)。


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


归档时间:

查看次数:

1369 次

最近记录:

3 年,3 月 前