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.dll
和vcruntime140.dll
的内容Lib\
(作为Lib\
或打包到 a 中python37.zip
)test.exe
。
问题:如何修改cl.exe ...
命令来要求编译器静态链接python37.dll
到文件vcruntime140.dll
内部test.exe
?
(这样就不再需要运输python37.dll
和单独)vcruntime140.dll
备注:可能 有比下面介绍的更好/更明智/更简单的选择。
这两种方法之间的主要区别:虽然在这种方法中,所有 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"
) 添加到预处理器定义中,否则链接器将查找错误的符号。version.lib
等等)现在需要显式传递给链接器(这可以在 pythoncore-project 的链接器命令行中查找)。"/LIBPATH:<path_to_code>\cpython\PCbuild\static_amd64"
现在。我们可以python38.dll
从static_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 amd64
to 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_AppendInittab
just 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 次 |
最近记录: |