使用 cython --embed 时静态 link python37.dll 和 vcruntime140.dll
Statically link python37.dll and vcruntime140.dll when using cython --embed
假设我正在“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
如中所述,需要分发python37.dll
和vcruntime140.dll
以及Lib\
的内容(作为Lib\
或打包到python37.zip
) 以及 test.exe
文件。
问题:如何修改cl.exe ...
命令让编译器在[=21=里面静态地linkpython37.dll
和vcruntime140.dll
] 文件?
(因此不再需要单独运送 python37.dll
和 vcruntime140.dll
)
备注: 可能是 better/saner/easier 选项,而不是下面显示的选项。
这两种方法的主要区别:在这种方法中,所有 C 扩展都必须返回到生成的可执行文件中,而在替代方法中,C 扩展是单独编译的,或者也可以添加额外的 C 扩展稍后进行分发。
虽然创建静态 linked 嵌入-Python-可执行文件在 Linux 上相对容易(例如参见 [=67=]),但在 Linux 上要复杂得多Windows。你可能不想这样做。
此外,结果可能不是人们所期望的:由于 dll 与 Linux' 共享对象相比的局限性,生成的静态 python-version 将无法 use/load 任何其他 c 扩展,作为在 compile/link 时间内支持的扩展(注意:这不完全正确, 中提供了一种解决方法)。
我也不建议从 vcruntime-dll 切换到它的静态版本 - 只有当 everything (exe, c-extensions, other dll which depend在 vcruntime 上)被静态 link 编辑成一个巨大的可执行文件。
第一个绊脚石:Linux python 发行版通常有一个静态 Python-library 已经发布,Windows 发行版只有 dll,不能静态 linked in.
因此需要在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 所需要的安装,例如 .
现在让我们创建一个静态 python38 库。为此,我们在 cpython/PCbuild-folder 中打开 pcbuild.sln
并更改 pythoncore
-项目的设置以在 PCbuild\amd64_static
- 文件夹中生成静态库。重建它。
现在我们可以构建嵌入式-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"
)被添加到预处理器定义中,否则 linker 将查找错误的符号。
- 由 python-dll 带来的 Windows 依赖项(即
version.lib
等)现在需要显式传递给 linker (这可以在 python 核心项目的 linker 命令行中查找。
- lib 路径显示到静态文件夹,即
"/LIBPATH:<path_to_code>\cpython\PCbuild\static_amd64"
现在。
- 根据您的具体工具链,可能还有其他小问题(不同的优化级别、link-时间代码生成、禁用整个程序优化等)。
我们现在可以从 static_amd64
中删除 python38.dll
,hello_prog.exe
仍然有效。
在 Linux,这将是“任务完成”,在 Windows,我们才刚刚开始...
确保 cpython
-文件夹有一个包含所有正确 pyd 文件的 DLLs
-文件夹,否则从 PCbuild/amd64
-文件夹创建并复制所有 pyd-文件。
让我们的 pyx 文件稍微复杂一点:
import _decimal
print("I'm standalone")
_decimal
是 decimal
-模块的快速实现,它是一个 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-库(即“ "version.lib"& Co.) 并将 /EXPORT:PyInit__decimal
添加到 link 选项,否则,由于 Py_NO_ENABLE_SHARED
它 。结果与 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,我们只有一个出路:它必须直接 linked 到我们的 hello_word
- 可执行文件中。因此,让我们将 _decimal
的项目更改为静态库并在 static_amd64
文件夹中重建它。从“DLLs”文件夹中删除 pyd 并将 /WHOLEARCHIVE:_decimal.lib
添加到 linker 命令行(整个存档,否则 linker 将丢弃 _decimal.lib
作为 none 的符号在某处被引用),导致可执行文件出现以下错误:
ModuleNotFoundError: No module named '_decimal'
这是预料之中的——我们需要告诉解释器,模块 _decimal
已返回,不应在 python 路径上搜索。
此问题的通常解决方案是使用 PyImport_AppendInittab
just before Py_Initialize
, that means we need to change the c-file generated by cython (there might be , but due to it is not that easy. So probably a saner way to embed Python is the one presented or ,因为 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();
构建所有内容现在会生成一个打印
的exe
I'm standalone
而且这次没有骗我们!
现在我们必须为我们需要的所有其他内置扩展重复最后的步骤。
以上意味着静态构建的 python-interpreter 有一些限制:所有内置模块都需要备份到可执行文件中,我们不能使用像 [=164= 这样的库扩展解释器](但可以直接在compile/link时进行)。
摆脱 vcruntime-dll 更容易:必须完成上述所有步骤 。但是,由于使用其他 dll(例如 _ctypes
需要 ffi
-dll)可能会出现一些问题,这些 dll 是使用 dll 版本构建的(因此我们再次违反了 ODR)- 所以我不会推荐它。
这是一种略有不同的方法(如@ssbssa 的评论所建议):主要区别在于使用此版本后,可以进一步添加 C-extensions 而不必将它们返回到生成的可执行文件。
创建与静态 python-library 链接的 hello_prog.exe
之前的第一步与 中的相同。命令
...
link hello.obj ... /OUT:hello_prog.exe ...
不仅创建 exe 本身,还创建 lib-file hello_prob.lib
。这个lib-file可以用来做联动,因为exe本身就从Python-library导出了很多符号。该行为类似于 -Xlinker -export-dynamic
在 Linux.
上链接嵌入式 python 可执行文件时的行为
现在,当构建 C-extensions 时(例如 _decimal
我们需要添加 hello_prog.lib
作为链接依赖项(即在 Properties/Linker/Input->Additional Dependencies 中)。
当我们查看 _decimal.pyd
的 运行 时间依赖性时,我们看到:
dumpbin /DEPENDENTS _decimal.pyd
...
Dump of file _decimal.pyd
File Type: DLL
Image has the following dependencies:
hello_prog.exe
...
当生成的 pyds 以这种方式链接并且可以被嵌入式解释器找到时,我们 运行 hello_prog
并查看:
I'm standalone
这意味着一切正常。
要进一步 C-extension 构建,hello_prob.lib
-文件必须在 provided/stored 某处。
假设我正在“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
如python37.dll
和vcruntime140.dll
以及Lib\
的内容(作为Lib\
或打包到python37.zip
) 以及 test.exe
文件。
问题:如何修改cl.exe ...
命令让编译器在[=21=里面静态地linkpython37.dll
和vcruntime140.dll
] 文件?
(因此不再需要单独运送 python37.dll
和 vcruntime140.dll
)
备注:
这两种方法的主要区别:在这种方法中,所有 C 扩展都必须返回到生成的可执行文件中,而在替代方法中,C 扩展是单独编译的,或者也可以添加额外的 C 扩展稍后进行分发。
虽然创建静态 linked 嵌入-Python-可执行文件在 Linux 上相对容易(例如参见 [=67=]),但在 Linux 上要复杂得多Windows。你可能不想这样做。
此外,结果可能不是人们所期望的:由于 dll 与 Linux' 共享对象相比的局限性,生成的静态 python-version 将无法 use/load 任何其他 c 扩展,作为在 compile/link 时间内支持的扩展(注意:这不完全正确,
我也不建议从 vcruntime-dll 切换到它的静态版本 - 只有当 everything (exe, c-extensions, other dll which depend在 vcruntime 上)被静态 link 编辑成一个巨大的可执行文件。
第一个绊脚石:Linux python 发行版通常有一个静态 Python-library 已经发布,Windows 发行版只有 dll,不能静态 linked in.
因此需要在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 所需要的安装,例如
现在让我们创建一个静态 python38 库。为此,我们在 cpython/PCbuild-folder 中打开 pcbuild.sln
并更改 pythoncore
-项目的设置以在 PCbuild\amd64_static
- 文件夹中生成静态库。重建它。
现在我们可以构建嵌入式-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"
)被添加到预处理器定义中,否则 linker 将查找错误的符号。- 由 python-dll 带来的 Windows 依赖项(即
version.lib
等)现在需要显式传递给 linker (这可以在 python 核心项目的 linker 命令行中查找。 - lib 路径显示到静态文件夹,即
"/LIBPATH:<path_to_code>\cpython\PCbuild\static_amd64"
现在。 - 根据您的具体工具链,可能还有其他小问题(不同的优化级别、link-时间代码生成、禁用整个程序优化等)。
我们现在可以从 static_amd64
中删除 python38.dll
,hello_prog.exe
仍然有效。
在 Linux,这将是“任务完成”,在 Windows,我们才刚刚开始...
确保 cpython
-文件夹有一个包含所有正确 pyd 文件的 DLLs
-文件夹,否则从 PCbuild/amd64
-文件夹创建并复制所有 pyd-文件。
让我们的 pyx 文件稍微复杂一点:
import _decimal
print("I'm standalone")
_decimal
是 decimal
-模块的快速实现,它是一个 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-库(即“ "version.lib"& Co.) 并将 /EXPORT:PyInit__decimal
添加到 link 选项,否则,由于 Py_NO_ENABLE_SHARED
它
hello_prog.exe
# crash/stopped worked
这是怎么回事?我们违反了一个定义规则 (ODR),最终得到两个 Python-解释器:一个来自 hello_prog.exe
,已初始化,另一个来自 _decimal.pyd
,未初始化。 _decimal.pyd
与其未初始化的解释器“对话”,并且发生了不好的事情。
与 Linux 的区别在于共享对象和 dll 之间的区别:共享对象可以使用 exe 中的符号(如果 exe 是使用正确的选项构建的)dll 不能,因此必须依赖在 dll 上(我们不想要)或需要有自己的版本。
为了避免违反 ODR,我们只有一个出路:它必须直接 linked 到我们的 hello_word
- 可执行文件中。因此,让我们将 _decimal
的项目更改为静态库并在 static_amd64
文件夹中重建它。从“DLLs”文件夹中删除 pyd 并将 /WHOLEARCHIVE:_decimal.lib
添加到 linker 命令行(整个存档,否则 linker 将丢弃 _decimal.lib
作为 none 的符号在某处被引用),导致可执行文件出现以下错误:
ModuleNotFoundError: No module named '_decimal'
这是预料之中的——我们需要告诉解释器,模块 _decimal
已返回,不应在 python 路径上搜索。
此问题的通常解决方案是使用 PyImport_AppendInittab
just before Py_Initialize
, that means we need to change the c-file generated by cython (there might be 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();
构建所有内容现在会生成一个打印
的exeI'm standalone
而且这次没有骗我们!
现在我们必须为我们需要的所有其他内置扩展重复最后的步骤。
以上意味着静态构建的 python-interpreter 有一些限制:所有内置模块都需要备份到可执行文件中,我们不能使用像 [=164= 这样的库扩展解释器](但可以直接在compile/link时进行)。
摆脱 vcruntime-dll 更容易:必须完成上述所有步骤 _ctypes
需要 ffi
-dll)可能会出现一些问题,这些 dll 是使用 dll 版本构建的(因此我们再次违反了 ODR)- 所以我不会推荐它。
这是一种略有不同的方法(如@ssbssa 的评论所建议):主要区别在于使用此版本后,可以进一步添加 C-extensions 而不必将它们返回到生成的可执行文件。
创建与静态 python-library 链接的 hello_prog.exe
之前的第一步与
...
link hello.obj ... /OUT:hello_prog.exe ...
不仅创建 exe 本身,还创建 lib-file hello_prob.lib
。这个lib-file可以用来做联动,因为exe本身就从Python-library导出了很多符号。该行为类似于 -Xlinker -export-dynamic
在 Linux.
现在,当构建 C-extensions 时(例如 _decimal
我们需要添加 hello_prog.lib
作为链接依赖项(即在 Properties/Linker/Input->Additional Dependencies 中)。
当我们查看 _decimal.pyd
的 运行 时间依赖性时,我们看到:
dumpbin /DEPENDENTS _decimal.pyd
...
Dump of file _decimal.pyd
File Type: DLL
Image has the following dependencies:
hello_prog.exe
...
当生成的 pyds 以这种方式链接并且可以被嵌入式解释器找到时,我们 运行 hello_prog
并查看:
I'm standalone
这意味着一切正常。
要进一步 C-extension 构建,hello_prob.lib
-文件必须在 provided/stored 某处。