В то время как создание статически связанного встроенного 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), поэтому я бы не рекомендовал это.