Python C扩展打包DLL连同pyd

Python C extension packaging DLL along with pyd

我的项目向 CPython (3.8) 解释器公开了一个静态库(称之为 static.lib)。它包含一个静态库,该库又依赖于 DLL FTDI 驱动程序。在阅读 this thread 后,提供第三方 DLL 的最佳解决方案似乎是将它们与 Python 包捆绑在一起 - 以确保 DLL 与 .pyd 二进制文件位于同一目录中。

我遇到的问题是,在我的包 运行 pip install . 之后,所需的 DLL(称为 required.dll)被放置在 site-packages/package/required.dll 中,而实际C扩展库(称之为package.pyd)放在site-packages/package.pyd.

因为当我尝试使用 Python 中的库时它不在同一个目录中,所以我得到

ImportError: DLL load failed while importing package: The specified module could not be found.

下面是我的setup.py

setuptools.setup(
        name="package",
        version="1.0.0",
        packages=setuptools.find_packages(where="src"),
        package_dir={"": "src"},
        py_modules=[splitext(basename(path))[0] for path in glob("src/*.py")],
        use_scm_version=True,
        package_data={
            "package": [
                "_clibs/libs/required.dll",
            ],
        },
        ext_modules=[
            setuptools.Extension(
                "package",
                include_dirs=["src/package/_clibs/inc"],
                sources=[
                    "src/package/_clibs/src/api.cpp",
                    "src/package/_clibs/src/utils.cpp",
                ],
                library_dirs=[
                    "src/package/_clibs/libs",
                ],
                libraries=["static", "User32"],
                language="c++"
            ),
        ],
    )

项目目录布局如下:

/
setup.py
.tox
src/
...package/
......wrapper.py
......__init__.py
......_clibs/
.........inc/
.........src/
............api.cpp
............utils.cpp
.........libs/
............required.dll
............static.lib

虚拟环境管理我也用tox

建议的答案 and 概述了非常相似的 setup.py 和包含 DLL 的相同方法 - 通过 package_data 选项。答案似乎表明 DLL 和 .pyd 然后被放置在同一级别,这对我来说不会发生。我不太清楚我缺少什么才能获得相同的行为。

python 3.8.6
setuptools 51.0.0
pip 20.3.1

TL;DR DLL 被放置在与 .pyd 二进制文件不同的目录中,因此它对 Windows 加载器

不可见

经过一番挖掘,我找到了一种适合我的方法。 This thread 阐明了 Windows 上的 DLL 加载问题以及该问题的最新 (Python 3.8) 发展。

我使用的解决方案是从 numpy 那里借来的。 为了正确地将 DLL 与您的 C 扩展捆绑在一起:

  1. 在您的包中创建一个目录,其中将包含您的扩展程序将使用的所有必需的 DLL
  2. 修改您的构建过程以包含此目录以及 sdist 和 wheel 发行版
  3. 用户导入您的包后,您要做的第一件事就是动态修改搜索 DLL 的路径(两种不同的方法,具体取决于您使用的是 3.8 还是更低版本)

粗略地说,将 package_data 添加到您的 setup.py 应该可以解决问题(减去 MANIFEST 文件附带的恶作剧并使用 package_data,阅读更多 here

 package_data={"your package name": ["path_to_DLLs/*"]},

要实施#3,作为包的 __init__.py 中的一个选项,添加以下内容(从 numpy __config__.py 中逐行提取 99%,由他们非常复杂的构建系统。

import os
import sys

PATH_TO_DLL = "YOUR DLL DIRECTORY IN YOUR PACKAGE"
extra_dll_dir = os.path.join(os.path.dirname(__file__), PATH_TO_DLL)

if sys.version_info >= (3, 8):
    os.add_dll_directory(extra_dll_dir)
else:
    # legacy DLL loading mechanism through PATH env variable manipulations
    os.environ.setdefault("PATH", "")
    os.environ["PATH"] += os.pathsep + extra_dll_dir

非常感谢任何反馈。链接到此票证的线程谈到需要更好的 C 扩展入职文档,但我找不到任何文档。到目前为止,C 扩展 + Windows + setuptools 带来了令人难以置信的令人沮丧的体验。