DLL для упаковки расширений Python C вместе с pyd

Мой проект предоставляет статическую библиотеку (назовем ее static.lib) интерпретатору CPython (3.8). Он состоит из статической библиотеки, которая, в свою очередь, зависит от драйвера DLL FTDI. После прочтения этой темы кажется, что оптимальным решением для предоставления сторонних 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.

Также по теме: stackoverflow.com/q/62662816/5769463

ead 17.12.2020 10:08

Это отличные ответы, однако я все еще сталкиваюсь с проблемами, описанными в моем исходном посте. Пробовал размещать required.dll в разных каталогах - результат тот же. Я отредактирую ответ, чтобы также включить макет каталога моего пакета.

keyermoond 17.12.2020 16:05

Откуда PIP знает, как собрать library.pyd? Я не вижу ссылки на него в setup.py.

CristiFati 22.12.2020 17:26

@CristiFati library.pyd является продуктом setup.py, замените library.pyd любым именем, которое в конечном итоге будет присвоено двоичному файлу. Чтобы соответствовать сценарию, который я предоставил, это должно быть package.pyd или его вариант. Извиняюсь за путаницу, я пытался скрыть настоящие имена библиотек, не относящиеся к этому вопросу, это просто несоответствие.

keyermoond 22.12.2020 18:50
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
5
965
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Немного покопавшись, я нашел способ, который сработал для меня. Эта тема пролила свет на проблемы с загрузкой DLL в Windows и самые последние (Python 3.8) разработки по этому вопросу.

Решение, которое я выбрал, было позаимствовано у numpy. Чтобы правильно связать библиотеки DLL с вашим расширением C:

  1. Создайте каталог в вашем пакете, который будет содержать все необходимые библиотеки DLL, которые будет использовать ваше расширение.
  2. Измените процедуру сборки, чтобы включить этот каталог вместе с sdist и дистрибутивами колес.
  3. После того, как пользователь импортирует ваш пакет, первое, что вы делаете, это динамически изменяете пути для поиска DLL (два разных метода в зависимости от того, используете ли вы версию 3.8 или ниже)

Грубо говоря, добавление package_data к вашему setup.py должно помочь (минус махинации с файлами МАНИФЕСТА и использование package_data, подробнее читайте здесь)

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

Чтобы реализовать № 3, в качестве опции в вашем __init__.py для пакета добавьте следующее (взято на 99% построчно из numpy__config__.py, которое автоматически генерируется их очень сложной системой сборки.

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 вызывали невероятное разочарование.

Другие вопросы по теме