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 扩展捆绑在一起:
- 在您的包中创建一个目录,其中将包含您的扩展程序将使用的所有必需的 DLL
- 修改您的构建过程以包含此目录以及
sdist
和 wheel 发行版
- 用户导入您的包后,您要做的第一件事就是动态修改搜索 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
带来了令人难以置信的令人沮丧的体验。
我的项目向 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
建议的答案 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 扩展捆绑在一起:
- 在您的包中创建一个目录,其中将包含您的扩展程序将使用的所有必需的 DLL
- 修改您的构建过程以包含此目录以及
sdist
和 wheel 发行版 - 用户导入您的包后,您要做的第一件事就是动态修改搜索 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
带来了令人难以置信的令人沮丧的体验。