Статически связать python37.dll и vcruntime140.dll при использовании cython --embed - PullRequest
1 голос
/ 21 июня 2020

Допустим, я "цитонизирую" это test.py:

import json
print(json.dumps({'key': 'hello world'}))

с помощью:

cython test.py --embed
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
cl test.c /I C:\Python37\include /link C:\Python37\libs\python37.lib

Как указано в Минимальный набор файлов, необходимых для распространения embed-Cython-скомпилированный код и заставить его работать на любой машине , необходимо распространять python37.dll и vcruntime140.dll и содержимое Lib\ (либо как Lib\, либо упакованное в python37.zip) а также в файле test.exe.

Вопрос: Как изменить команду cl.exe ..., чтобы компилятор статически связывал python37.dll и vcruntime140.dll внутри файла test.exe?

(поэтому пересылка python37.dll и vcruntime140.dll по отдельности больше не требуется)

1 Ответ

1 голос
/ 23 июня 2020

В то время как создание статически связанного встроенного Python -исполняемого файла относительно просто на Linux (см., Например, этот SO-post ), это намного сложнее на Windows. И вы, вероятно, не захотите этого делать. * -версия не сможет использовать / загружать какие-либо другие c -расширения, как то, которое было загружено во время компиляции / компоновки.

Я также не рекомендую переходить с vcruntime-dll на его stati c версия - это имеет смысл только тогда, когда все (exe, c -расширения, другие dll, которые зависят от vcruntime) статически связаны в один огромный исполняемый файл.

Первый камень преткновения: в то время как в Linux python дистрибутивах часто уже поставлена ​​stati c Python -библиотека, в Windows дистрибутивах есть только dll, с которой нельзя статически связать.

Таким образом, нужно собрать c библиотеку на Windows. Хорошей отправной точкой является эта ссылка .

После загрузки исходного кода для правильной Python версии (git clone --depth=1 --branch v3.8.0 https://github.com/python/cpython.git) вы можете go получить cpython\PCBuild и собрать c python, как описано в документации (которая может отличаться от версии к версии).

В моем случае это была

cd cpython/PCbuild
.\build.bat -e -p x64 

Нет, у нас есть работающая установка Python3 .8, которая можно найти в cpython/PCbuild/amd64. Создайте папку cpython/PCbuild/static_amd64 и добавьте следующий pyx-файл:

#hello.pyx
print("I'm standalone")

пока скопируйте python38.dll в static_amd64.

Теперь давайте соберем нашу программу со встроенным python интерпретатор:

cython --embed -3 hello.pyx
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
cl /c hello.c /Fohello.obj  /nologo /Ox /W3 /GL /DNDEBUG /MD -I<path_to_code>\cpython\include -I<path_to_code>\cpython\PC
link hello.obj python38.lib  /OUT:hello_prog.exe /nologo "/LIBPATH:<path_to_code>\cpython\PCbuild\amd64"

После запуска hello_prog.exe лжет нам, так как на самом деле он не является автономным. Хорошая новость: он находит Python -инсталляцию, которая необходима, как описано, например, здесь .

Теперь давайте создадим stati c python38-library. Для этого мы открываем pcbuild.sln в cpython / PCbuild-folder и меняем настройку pythoncore -project для создания библиотеки stati c в PCbuild\amd64_static -folder. Перестройте его.

Теперь мы можем собрать встроенный- python -exe:

cl /c hello.c /Fohello.obj /D "Py_NO_ENABLE_SHARED" /nologo /Ox /W3 /GL /DNDEBUG /MD -I<path_to_code>\cpython\include -I<path_to_code>\cpython\PC
link hello.obj python38.lib "version.lib" "shlwapi.lib" "ws2_32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /OUT:hello_prog.exe /nologo "/LIBPATH:<path_to_code>\cpython\PCbuild\static_amd64"

По сравнению со сборкой на основе dll нам пришлось изменить следующее:

  • Py_NO_ENABLE_SHARED (т.е. /D "Py_NO_ENABLE_SHARED") добавляется к определениям препроцессора, иначе компоновщик будет искать неправильные символы.
  • зависимости Windows (например, version.lib и и так далее), которые были переданы python -dll, теперь необходимо явно передать компоновщику (это можно найти в командной строке компоновщика проекта pythoncore).
  • путь к библиотеке показать в папку stati c, т.е. "/LIBPATH:<path_to_code>\cpython\PCbuild\static_amd64" сейчас.
  • могут быть другие более мелкие проблемы (разные уровни оптимизации, генерация кода времени ссылки, отключение оптимизации всей программы и т. д.) в зависимости от на вашей точной цепочке инструментов.

Теперь мы можем удалить python38.dll из static_amd64, а hello_prog.exe все еще работает.

На Linux это будет " миссия выполнена », на Windows мы только в начале ...

Убедитесь, что cpython -folde r имеет DLLs -папку со всеми нужными pyd-файлами, в противном случае создайте и скопируйте все pyd-файлы из PCbuild/amd64 -папки.

Давайте немного усложним наш pyx-файл:

import _decimal
print("I'm standalone")

_decimal - это быстрая реализация модуля decimal, который является расширением C и может быть найден в папке DLL.

После цитонизации и при его построении запуск hello_prog.exe приводит к следующему сообщению об ошибке:

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

Проблему легко найти:

dumpbin /DEPENDENTS ../amd64/_decimal.pyd
...
python38.dll
... 

Расширения нашей установки по-прежнему зависят от python -dll. Давайте перестроим их для библиотеки stati c - нам нужно изменить путь к библиотеке с amd64 на static_amd64, чтобы добавить определение препроцессора Py_NO_ENABLE_SHARED и все отсутствующие windows -библиотеки (например, «version.lib» & Co.) и добавив /EXPORT:PyInit__decimal к параметрам ссылки, иначе из-за Py_NO_ENABLE_SHARED он станет невидимым . Результат не зависит от python -dll! Мы копируем его в библиотеки DLL- папка и ...

hello_prog.exe
# crash/stopped worked

Что происходит? Мы нарушили одно правило определения (ODR) и получили два Python -интерпретатора: один из hello_prog.exe, который инициализирован, и один из _decimal.pyd, который не инициализирован. _decimal.pyd «говорит» со своим интерпретатором, который не инициализирован, и случаются неприятности.

Отличие от Linux заключается в различии между разделяемыми объектами и dll: в то время как общие объекты могут использовать символы из exe (если exe собран с правильными параметрами) dll не может и, следовательно, должна зависеть от библиотеки dll (которая нам не нужна) или должна иметь свою собственную версию.

Чтобы избежать нарушения ODR, у нас есть Выход только один: его надо напрямую связать с нашим hello_word -исполняемым файлом. Итак, давайте изменим проект для _decimal на библиотеку stati c и перестроим его в static_amd64 -папку. Удаление pyd из папки «DLLs» и добавление /WHOLEARCHIVE:_decimal.lib в командную строку компоновщика (весь архив, в противном случае компоновщик просто отбросит _decimal.lib, поскольку ни один из его символов где-то не упоминается) приводит к исполняемому файлу, который имеет следующую ошибку:

ModuleNotFoundError: No module named '_decimal'

Это ожидается - нам нужно сообщить интерпретатору, что модуль _decimal заархивирован и его не следует искать по пути python.

Обычное решение этой проблемы - использовать PyImport_AppendInittab непосредственно перед Py_Initialize, это означает, что нам нужно изменить c -файл, сгенерированный cython (могут быть обходные пути , но из-за многофазной инициализации это не так просто. Так что, вероятно, более разумный способ встроить Python - это тот, который представлен здесь или здесь были main написаны не Cython). c -файл должен выглядеть следующим образом:

//decalare init-functions
extern  PyObject* PyInit__decimal();
...
int main(int argc, char** argv) {
...
    if (argc && argv)
        Py_SetProgramName(argv[0]);
    PyImport_AppendInittab("_decimal", PyInit__decimal); //  HERE WE GO
                                                         //  BEFORE Py_Initialize
    
    Py_Initialize();

Сборка всего приводит к запуску exe, который печатает

I'm standalone

, и на этот раз он нам не лжет!

Теперь нам нужно повторить последние шаги для всех остальных встроенных расширений, которые нам нужны.

Вышеупомянутое означает, что существуют некоторые ограничения для статически построенного python -интерпретатора: все встроенные модули должны быть поддержаны в исполняемом файле, и мы не можем расширить интерпретатор последним библиотеками, такими как numpy / scipy (но можем сделать это прямо во время компиляции / компоновки).

Избавиться от vcruntime-dll проще: все вышеперечисленные шаги должны быть выполнены с опцией /MT вместо MD -option . Однако могут возникнуть некоторые проблемы из-за использования других dll (например, _ctypes требуется ffi -dll), которые были построены с использованием dll-версии (и, таким образом, у нас снова есть нарушения ODR), поэтому я бы не рекомендовал это.

...