Python 如何使用正确的dll文件在Cython C扩展中启用第三方C库?

Python 如何使用正确的dll文件在Cython C扩展中启用第三方C库?,python,c,dll,cython,python-c-api,Python,C,Dll,Cython,Python C Api,我有一个C函数,涉及使用zstd解压缩数据。我正在尝试使用Cython调用该函数 以文档为指导,我可以毫无问题地编译和运行下面的代码 (这里我实际上不使用zstd库) 但是,当我修改hello.c以实际使用zstd库时: // hello.c #include <stdio.h> #include <zstd.h> int hello() { printf("Hello, World!\n"); void *next_in = mallo

我有一个C函数,涉及使用zstd解压缩数据。我正在尝试使用Cython调用该函数

以文档为指导,我可以毫无问题地编译和运行下面的代码

(这里我实际上不使用zstd库)

但是,当我修改
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;
}

通过阅读,我推断这个错误意味着我没有正确地指向或者可能没有首先创建zstd.lib发挥其神奇作用所需的DLL文件。这是正确的吗?如果是,我该怎么做?如果没有,问题是什么?

我们将cython扩展与windows dll链接,这意味着:

  • *.lib
    -编译期间,
    路径/to/zstd/lib
    中需要文件(即
    zstd.lib
  • *.dll
    -导入模块时,Windows可以在某个地方找到该文件(即
    zstd.dll
通常,Windows不会在
“path/to/zstd/lib”
中查找。因此我们得到了一个有点神秘的错误信息:

ImportError:DLL加载失败:找不到指定的模块

这并不意味着模块有问题——它只是碰巧依赖于一个无法找到的dll

虽然linux具有可传递的
“path/to/zstd/lib”
(可以添加
runtime\u library\u dirs
-参数到
扩展名
),但Windows上没有这样的选项。Windows的dll搜索算法。简言之,dll是在中搜索的(可能按此处所示的其他顺序)

  • 从中加载模块的目录
  • 当前目录
  • 系统目录(例如
    C:\Windows\System32
  • windows目录(例如
    C:\windows
  • PATH变量中列出的目录
  • 其他
将dll放入系统或windows目录听起来不太吸引人,这给我们留下了以下选项:

  • (最简单的?)复制编译扩展旁边的
    zstd.dll
  • 将zstd路径添加到
    path
    -变量,例如
    set path=“path/to/zstd/lib”;%路径%

另一个选择有点棘手:

如果具有相同模块名的DLL已加载到内存中,则 在解析到之前,系统仅检查重定向和清单 加载的DLL,无论它位于哪个目录。这个系统确实如此 不搜索DLL

我们可以使用
ctypes
来“预加载”正确的dll,在导入包装器模块时将使用该dll(无需在光盘上搜索),即:

import ctypes; 
ctypes.CDLL("path/to/zstd/lib/zstd.dll"); # we preload with the full path

import hello_wrapper  # works now!

如果扩展是在同一系统上构建和使用的(例如,通过
build\u ext--inplace
),则上述规定适用。安装/分发有点麻烦(本节对此进行了介绍),一个想法是:

  • *.h
    -、
    *.lib
    -和
    *.dll
    -文件放入“package\u数据”中(无论如何,这似乎是自动发生的)
  • 可以在
    setup.py
    中设置右侧相对
    library\u路径(或以编程方式设置绝对路径),以便链接器找到
    *.lib
  • dll将放在安装中编译的
    *.pyd
    -文件旁边
例如,下面的示例或多或少是最小的
setup.py
,其中所有内容(pyx文件、h文件、lib文件、dll文件)都放在一个包/文件夹
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

在Windows或UNIX上安装,编译链接和运行链接过程需要相同的目录搜索顺序,以避免获取错误的库,还需要-l编译时选项来调用这些库。对于UNIX,有一个带有路径的环境变量,即冒号分隔的目录列表。因此,除了编译器/项目配置之外,有时在运行时还需要一个包装器脚本来修改环境变量,以允许编译后的代码运行。对于Windows,它是不同的:
>>> 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.
import ctypes; 
ctypes.CDLL("path/to/zstd/lib/zstd.dll"); # we preload with the full path

import hello_wrapper  # works now!
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"},
)