如何使用正确的 dll 文件在 Cython C 扩展中启用第 3 方 C 库?
How do I use the correct dll files to enable 3rd party C libraries in a Cython C extension?
我有一个涉及使用 zstd 解压缩数据的 C 函数。我正在尝试使用 Cython 调用该函数。
Using this page 以文档为指导,我可以编译 运行 下面的代码没有问题。
(我实际上并没有在这里使用 zstd 库)
// hello.c
#include <stdio.h>
#include <zstd.h>
int hello() {
printf("Hello, World!\n");
void *next_in = malloc(0);
void *next_out = malloc(0);
return 0;
}
# Hello.pyx
cdef extern from "hello.c":
int hello()
cpdef int callHello():
hello()
# hello_wrapper.setup.py
from setuptools import setup, Extension
from Cython.Build import cythonize
ext_modules = [
Extension(
"hello_wrapper",
["hello_wrapper.pyx"],
libraries=["zstd"],
library_dirs=["path/to/zstd/lib"],
include_dirs=['path/to/zstd/include'],
)
]
setup(
ext_modules = cythonize(ext_modules, gdb_debug=True)
)
使用如下命令我得到了预期的输出:
>py hello_wrapper.setup.py build_ext --inplace
>py
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:20:19) [MSC v.1925 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_wrapper
>>> hello_wrapper.callHello()
Hello, World!
0
然而,当我修改 hello.c
以实际使用 zstd 库时:
// hello.c
#include <stdio.h>
#include <zstd.h>
int hello() {
printf("Hello, World!\n");
void *next_in = malloc(0);
void *next_out = malloc(0);
size_t const dSize = ZSTD_decompress(next_out, 0, next_in, 0); //the added line
return 0;
}
虽然 hello_wrapper.setup.py
编译正常,但当我进入导入语句时,出现以下错误:
>>> import hello_wrapper
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: DLL load failed while importing hello_wrapper: The specified module could not be found.
通过阅读 ,我了解到这个错误意味着我没有正确指向或可能首先创建了 zstd.lib 发挥其魔力所需的 DLL 文件。这个对吗?如果是这样,我该怎么做?如果不是,是什么问题?
我们 link 我们的 cython 扩展针对 windows-dll,这意味着:
*.lib
-文件(即 zstd.lib
)在编译期间 "path/to/zstd/lib"
中需要
*.dll
-文件(即 zstd.dll
)需要在导入模块时 Windows 可以找到的地方。
通常,Windows 不会在 "path/to/zstd/lib"
中查找。所以我们得到了一个有点神秘的错误信息:
ImportError: DLL load failed: The specified module could not be found.
这并不意味着模块有问题 - 它只是恰好依赖于无法找到的 dll。
虽然 linux 有 可以传递 "path/to/zstd/lib"
(它可以用 runtime_library_dirs
参数添加到 Extension
),但有Windows.
上没有这样的选项
Windows can be found here 的 dll 搜索算法。简而言之,搜索 dll(可能以此处显示的另一种顺序)
- 加载模块的目录。
- 当前工作目录。
- 系统目录(例如
C:\Windows\System32
)
- windows-目录(例如
C:\Windows
)
PATH
-变量中列出的目录
- 其他
但是,since Python3.8 the above default algorithm isn't used for CPython: The current working directory and the PATH
-variable are no longer used during the call, but there is os.add_dll_directory
可用于添加解析依赖项时将使用的路径。
将 dll 放入 system- 或 windows- 目录听起来不太吸引人,这给我们留下了以下选择:
- (最简单?)复制编译扩展
旁边的zstd.dll
- 使用
os.add_dll_directory
将位置添加到 Python>=3.8 的搜索中
- 将 zstd 路径添加到
PATH
变量,例如set PATH="path/to/zstd/lib";%PATH%
(Python<3.8)
另一个选项有点棘手:Given that
If a DLL with the same module name is already loaded in memory, the
system checks only for redirection and a manifest before resolving to
the loaded DLL, no matter which directory it is in. The system does
not search for the DLL.
我们可以使用ctypes
来“预加载”正确的dll,在导入包装器模块时将使用(无需在光盘上搜索它),即:
import ctypes;
ctypes.CDLL("path/to/zstd/lib/zstd.dll"); # we preload with the full path
import hello_wrapper # works now!
如果扩展是在同一系统上构建和使用的(例如通过 build_ext --inplace
),则以上内容适用。 installation/distribution有点麻烦(这个涵盖了),一个想法是:
- 将
*.h
-、*.lib
- 和 *.dll
- 文件放入 'package_data'(无论如何它似乎自动发生)
- 可以在
setup.py
中设置正确的相对 library_path
(或以编程方式设置绝对路径),因此 *.lib
会被 linker 找到。
- dll 将放在编译后的
*.pyd
-文件旁边。
一个例子可能是以下或多或少最小的 setup.py
,其中所有内容(pyx 文件、h 文件、lib 文件、dll 文件)都放入 package/folder src/zstd
:
from setuptools import setup, Extension, find_packages
from Cython.Build import cythonize
ext_modules = [
Extension(
"zstd.zstdwrapper",
["src/zstd/zstdwrapper.pyx"],
libraries=["zstd"],
library_dirs=["src/zstd"],
include_dirs=[], # set automatically to src/zstd during the build
)
]
print(find_packages(where='src'))
setup(
name = 'zstdwrapper',
ext_modules = cythonize(ext_modules),
packages = find_packages(where='src'),
package_dir = {"": "src"},
)
现在可以使用 python setup.py install
安装或用于创建例如通过 python setup.py sdist
的源代码分发,然后可以通过 pip
.
安装
我有一个涉及使用 zstd 解压缩数据的 C 函数。我正在尝试使用 Cython 调用该函数。
Using this page 以文档为指导,我可以编译 运行 下面的代码没有问题。
(我实际上并没有在这里使用 zstd 库)
// hello.c
#include <stdio.h>
#include <zstd.h>
int hello() {
printf("Hello, World!\n");
void *next_in = malloc(0);
void *next_out = malloc(0);
return 0;
}
# Hello.pyx
cdef extern from "hello.c":
int hello()
cpdef int callHello():
hello()
# hello_wrapper.setup.py
from setuptools import setup, Extension
from Cython.Build import cythonize
ext_modules = [
Extension(
"hello_wrapper",
["hello_wrapper.pyx"],
libraries=["zstd"],
library_dirs=["path/to/zstd/lib"],
include_dirs=['path/to/zstd/include'],
)
]
setup(
ext_modules = cythonize(ext_modules, gdb_debug=True)
)
使用如下命令我得到了预期的输出:
>py hello_wrapper.setup.py build_ext --inplace
>py
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:20:19) [MSC v.1925 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_wrapper
>>> hello_wrapper.callHello()
Hello, World!
0
然而,当我修改 hello.c
以实际使用 zstd 库时:
// hello.c
#include <stdio.h>
#include <zstd.h>
int hello() {
printf("Hello, World!\n");
void *next_in = malloc(0);
void *next_out = malloc(0);
size_t const dSize = ZSTD_decompress(next_out, 0, next_in, 0); //the added line
return 0;
}
虽然 hello_wrapper.setup.py
编译正常,但当我进入导入语句时,出现以下错误:
>>> import hello_wrapper
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: DLL load failed while importing hello_wrapper: The specified module could not be found.
通过阅读
我们 link 我们的 cython 扩展针对 windows-dll,这意味着:
*.lib
-文件(即zstd.lib
)在编译期间"path/to/zstd/lib"
中需要*.dll
-文件(即zstd.dll
)需要在导入模块时 Windows 可以找到的地方。
通常,Windows 不会在 "path/to/zstd/lib"
中查找。所以我们得到了一个有点神秘的错误信息:
ImportError: DLL load failed: The specified module could not be found.
这并不意味着模块有问题 - 它只是恰好依赖于无法找到的 dll。
虽然 linux 有 "path/to/zstd/lib"
(它可以用 runtime_library_dirs
参数添加到 Extension
),但有Windows.
Windows can be found here 的 dll 搜索算法。简而言之,搜索 dll(可能以此处显示的另一种顺序)
- 加载模块的目录。
- 当前工作目录。
- 系统目录(例如
C:\Windows\System32
) - windows-目录(例如
C:\Windows
) PATH
-变量中列出的目录- 其他
但是,since Python3.8 the above default algorithm isn't used for CPython: The current working directory and the PATH
-variable are no longer used during the call, but there is os.add_dll_directory
可用于添加解析依赖项时将使用的路径。
将 dll 放入 system- 或 windows- 目录听起来不太吸引人,这给我们留下了以下选择:
- (最简单?)复制编译扩展 旁边的
- 使用
os.add_dll_directory
将位置添加到 Python>=3.8 的搜索中
- 将 zstd 路径添加到
PATH
变量,例如set PATH="path/to/zstd/lib";%PATH%
(Python<3.8)
zstd.dll
另一个选项有点棘手:Given that
If a DLL with the same module name is already loaded in memory, the system checks only for redirection and a manifest before resolving to the loaded DLL, no matter which directory it is in. The system does not search for the DLL.
我们可以使用ctypes
来“预加载”正确的dll,在导入包装器模块时将使用(无需在光盘上搜索它),即:
import ctypes;
ctypes.CDLL("path/to/zstd/lib/zstd.dll"); # we preload with the full path
import hello_wrapper # works now!
如果扩展是在同一系统上构建和使用的(例如通过 build_ext --inplace
),则以上内容适用。 installation/distribution有点麻烦(这个
- 将
*.h
-、*.lib
- 和*.dll
- 文件放入 'package_data'(无论如何它似乎自动发生) - 可以在
setup.py
中设置正确的相对library_path
(或以编程方式设置绝对路径),因此*.lib
会被 linker 找到。 - dll 将放在编译后的
*.pyd
-文件旁边。
一个例子可能是以下或多或少最小的 setup.py
,其中所有内容(pyx 文件、h 文件、lib 文件、dll 文件)都放入 package/folder src/zstd
:
from setuptools import setup, Extension, find_packages
from Cython.Build import cythonize
ext_modules = [
Extension(
"zstd.zstdwrapper",
["src/zstd/zstdwrapper.pyx"],
libraries=["zstd"],
library_dirs=["src/zstd"],
include_dirs=[], # set automatically to src/zstd during the build
)
]
print(find_packages(where='src'))
setup(
name = 'zstdwrapper',
ext_modules = cythonize(ext_modules),
packages = find_packages(where='src'),
package_dir = {"": "src"},
)
现在可以使用 python setup.py install
安装或用于创建例如通过 python setup.py sdist
的源代码分发,然后可以通过 pip
.