windows.md
1> **Источник:** https://python-all.ru/3.3/extending/windows.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 4. Сборка расширений C и C++ в Windows89В этой главе кратко объясняется, как создать модуль расширения для Windows на Python с помощью Microsoft Visual C++, после чего приводится более подробная справочная информация о том, как это работает. Материал полезен как программисту Windows, осваивающему сборку расширений Python, так и программисту Unix, заинтересованному в создании программного обеспечения, которое успешно собирается как на Unix, так и на Windows.1011Авторам модулей рекомендуется использовать подход distutils для сборки модулей расширения вместо описанного в этом разделе. При этом всё равно понадобится компилятор C, который использовался для сборки Python; обычно это Microsoft Visual C++.1213> **Примечание**14>15> В этой главе упоминается ряд имен файлов, содержащих закодированный номер версии Python. Эти имена файлов представлены с номером версии в виде `XY`; на практике `'X'` – это номер старшей версии, а `'Y'` – номер младшей версии используемого выпуска Python. Например, если вы используете Python 2.2.1, `XY` на самом деле будет `22`.1617## 4.1. Подход «Кулинарная книга»1819В Windows, как и в Unix, есть два подхода к сборке модулей расширения: использовать пакет [`distutils`](https://python-all.ru/3.3/library/distutils.html#module-distutils) для управления процессом сборки или делать всё вручную. Подход distutils хорошо работает для большинства расширений; документация по использованию [`distutils`](https://python-all.ru/3.3/library/distutils.html#module-distutils) для сборки и упаковки модулей расширения доступна в [*Distributing Python Modules*](https://python-all.ru/3.3/distutils/index.html#distutils-index). В этом разделе описывается ручной подход к сборке расширений Python, написанных на C или C++.2021Чтобы собирать расширения по этим инструкциям, нужна копия исходных кодов Python той же версии, что и установленный Python. Потребуется Microsoft Visual C++ «Developer Studio»; файлы проекта предоставляются для VC++ версии 7.1, но можно использовать и более старые версии VC++. Обратите внимание: следует использовать ту же версию VC++, которая использовалась для сборки самого Python. Примеры файлов, описанные здесь, распространяются с исходниками Python в каталоге `PC\example_nt\`.22231. **Скопировать файлы примеров** – Каталог `example_nt` – подкаталог `PC`, чтобы все PC-специфичные файлы находились в одном каталоге в дистрибутиве исходников. Однако использовать каталог `example_nt` непосредственно из этого расположения нельзя. Сначала скопируйте или переместите его на уровень выше, чтобы `example_nt` стал соседним с каталогами `PC` и `Include`. Всю работу выполняйте в этом новом месте.242. **Открыть проект** – В VC++ используйте диалог *File ‣ Open Solution* (не *File ‣ Open*!). Перейдите к `example.sln` в *копии* каталога `example_nt`, созданной на предыдущем шаге, и выберите его. Нажмите Open.253. **Собрать пример DLL** – Чтобы проверить, что всё настроено правильно, попробуйте собрать:264. Выберите конфигурацию. Это необязательный шаг. Выберите *Build ‣ Configuration Manager ‣ Active Solution Configuration*, а затем *Release* или *Debug*. Если пропустить этот шаг, VC++ по умолчанию использует конфигурацию Debug.275. Соберите библиотеку DLL. Выберите *Build ‣ Build Solution*. Будут созданы все промежуточные и результирующие файлы в подкаталоге `Debug` или `Release`, в зависимости от выбранной на предыдущем шаге конфигурации.286. **Тестирование DLL в режиме отладки** – После успешной сборки Debug откройте окно DOS и перейдите в каталог `example_nt\Debug`. Теперь вы сможете повторить следующий сеанс (`C>` – приглашение DOS, `>>>` – приглашение Python; заметьте, что информация о сборке и различный отладочный вывод Python могут не полностью совпадать с этим дампом экрана):2930 ```c31 C>..\..\PCbuild\python_d32 Adding parser accelerators ...33 Done.34 Python 2.2 (#28, Dec 19 2001, 23:26:37) [MSC 32 bit (Intel)] on win3235 Type "copyright", "credits" or "license" for more information.36 >>> import example37 [4897 refs]38 >>> example.foo()39 Hello, world40 [4903 refs]41 >>>42 ```4344 Поздравляем! Вы успешно собрали свой первый модуль расширения Python.457. **Создание собственного проекта** – Выберите имя и создайте для него каталог. Скопируйте в него свои C-исходники. Обратите внимание: имя файла с исходным кодом модуля не обязательно должно совпадать с именем модуля, но имя функции инициализации должно ему соответствовать – модуль `spam` можно импортировать, только если его функция инициализации называется `initspam()`, и она должна вызывать `Py_InitModule()` со строкой `"spam"` в качестве первого аргумента (используйте минимальный `example.c` в этом каталоге как образец). По соглашению, такой файл называется `spam.c` или `spammodule.c`. Выходной файл должен называться `spam.pyd` (в режиме Release) или `spam_d.pyd` (в режиме Debug). Расширение `.pyd` было выбрано, чтобы избежать путаницы с системной библиотекой `spam.dll`, для которой ваш модуль может служить интерфейсом Python.4647 Теперь возможны следующие варианты:488. Скопируйте `example.sln` и `example.vcproj`, переименуйте их в `spam.*` и отредактируйте вручную, или499. Создайте совершенно новый проект; инструкции ниже.5051 В любом случае скопируйте `example_nt\example.def` в `spam\spam.def` и отредактируйте новый `spam.def` так, чтобы его вторая строка содержала строку '`initspam`'. Если вы создали новый проект самостоятельно, добавьте файл `spam.def` в проект сейчас. (Это небольшой файл всего из двух строк. Альтернативный подход – забыть о файле `.def` и добавить параметр */export:initspam* куда-нибудь в настройки компоновщика, вручную отредактировав параметр в диалоге Project Properties).5210. **Создание совершенно нового проекта** – Используйте диалог *File ‣ New ‣ Project*, чтобы создать новую рабочую область проекта. Выберите *Visual C++ Projects/Win32/ Win32 Project*, введите имя (`spam`) и убедитесь, что расположение (Location) указывает на родительский каталог созданного вами каталога `spam` (который должен быть прямым подкаталогом дерева сборки Python, рядом с `Include` и `PC`). Выберите Win32 в качестве платформы (в моей версии это единственный вариант). Убедитесь, что переключатель Create new workspace выбран. Нажмите OK.5354 Теперь создайте файл `spam.def`, как указано в предыдущем разделе. Добавьте исходные файлы в проект с помощью *Project ‣ Add Existing Item*. Установите фильтр `*.*` и выберите `spam.c` и `spam.def`, затем нажмите OK. (Можно добавлять их по одному – тоже подойдет.)5556 Теперь откройте диалог *Project ‣ spam properties*. Нужно изменить лишь несколько настроек. Убедитесь, что в раскрывающемся списке *Settings for:* выбрано *All Configurations*. Перейдите на вкладку C/C++. В выпадающем меню вверху выберите категорию General. В поле ввода с надписью *Additional Include Directories* введите следующий текст:5758 ```c59 ..\Include,..\PC60 ```6162 Затем на вкладке Linker выберите категорию General и введите:6364 ```c65 ..\PCbuild66 ```6768 в текстовое поле с надписью *Additional library Directories*.6970 Теперь нужно добавить некоторые настройки, зависящие от режима:7172 Выберите *Release* в раскрывающемся списке *Configuration*. Перейдите на вкладку *Link*, выберите категорию *Input* и добавьте `pythonXY.lib` в список в поле *Additional Dependencies*.7374 Выберите *Debug* в раскрывающемся списке *Configuration* и добавьте `pythonXY_d.lib` в список в поле *Additional Dependencies*. Затем перейдите на вкладку C/C++, выберите *Code Generation* и в раскрывающемся списке *Runtime library* выберите *Multi-threaded Debug DLL*.7576 Снова выберите *Release* из раскрывающегося списка *Configuration*. В раскрывающемся списке *Runtime library* выберите *Multi-threaded DLL*.7778Если ваш модуль создаёт новый тип, при использовании этой строки могут возникнуть проблемы:7980```c81PyVarObject_HEAD_INIT(&PyType_Type, 0)82```8384Статические инициализаторы объектов типов в модулях расширения могут приводить к ошибке компиляции с сообщением вида «initializer not a constant». Это проявляется при сборке DLL в MSVC. Измените на:8586```c87PyVarObject_HEAD_INIT(NULL, 0)88```8990и добавьте следующее в функцию инициализации модуля:9192```c93if (PyType_Ready(&MyObject_Type) < 0)94 return NULL;95```9697## 4.2. Различия между Unix и Windows9899В Unix и Windows используются совершенно разные парадигмы загрузки кода во время выполнения. Прежде чем пытаться собрать модуль, который может загружаться динамически, следует понимать, как работает ваша система.100101В Unix файл shared object (`.so`) содержит код для использования программой, а также имена функций и данных, которые он ожидает найти в программе. Когда файл присоединяется к программе, все ссылки на эти функции и данные в коде файла изменяются, чтобы указывать на фактические расположения в памяти программы, где размещены функции и данные. По сути, это операция компоновки.102103В Windows файл динамической библиотеки (DLL) (`.dll`) не имеет висячих ссылок. Вместо этого доступ к функциям или данным осуществляется через таблицу поиска. Таким образом, код DLL не нужно корректировать во время выполнения для обращения к памяти программы; вместо этого код уже использует таблицу поиска DLL, а сама таблица изменяется во время выполнения, чтобы указывать на нужные функции и данные.104105В Unix существует только один тип библиотечного файла (`.a`), который содержит код из нескольких объектных файлов (`.o`). На этапе компоновки для создания файла shared object (`.so`) компоновщик может обнаружить, что не знает, где определён идентификатор. Компоновщик будет искать его в объектных файлах библиотек; если найдёт, он включит весь код из этого объектного файла.106107В Windows существует два типа библиотек: статическая библиотека и библиотека импорта (обе называются `.lib`). Статическая библиотека похожа на файл `.a` в Unix; она содержит код, который будет включён по мере необходимости. Библиотека импорта в основном используется только для подтверждения компоновщику, что определённый идентификатор является допустимым и будет присутствовать в программе при загрузке DLL. Таким образом, компоновщик использует информацию из библиотеки импорта для построения таблицы поиска для идентификаторов, которые не включены в DLL. Когда приложение или DLL компонуется, может быть создана библиотека импорта, которая понадобится для всех будущих DLL, зависящих от символов из этого приложения или DLL.108109Предположим, вы собираете два модуля динамической загрузки, B и C, которые должны совместно использовать другой блок кода A. В Unix вы бы *не* передавали `A.a` компоновщику для `B.so` и `C.so`; это привело бы к его включению дважды, так что B и C имели бы по собственной копии. В Windows сборка `A.dll` также создаст `A.lib`. Вы *действительно* передаёте `A.lib` компоновщику для B и C. `A.lib` не содержит код; он содержит только информацию, которая будет использоваться во время выполнения для доступа к коду A.110111В Windows использование библиотеки импорта отчасти похоже на использование `import spam`; это даёт доступ к именам из spam, но не создаёт отдельную копию. В Unix компоновка с библиотекой больше похожа на `from spam import *`; она создаёт отдельную копию.112113## 4.3. Использование DLL на практике114115Python для Windows собирается в Microsoft Visual C++; использование других компиляторов может работать или не работать (хотя Borland, похоже, работает). Остальная часть этого раздела относится только к MSVC++.116117При создании DLL в Windows необходимо передавать `pythonXY.lib` компоновщику. Чтобы собрать две DLL, spam и ni (которая использует функции C из spam), можно использовать следующие команды:118119```c120cl /LD /I/python/include spam.c ../libs/pythonXY.lib121cl /LD /I/python/include ni.c spam.lib ../libs/pythonXY.lib122```123124Первая команда создала три файла: `spam.obj`, `spam.dll` и `spam.lib`. `Spam.dll` не содержит никаких функций Python (таких как [`PyArg_ParseTuple()`](https://python-all.ru/3.3/c-api/arg.html#PyArg_ParseTuple)), но благодаря `pythonXY.lib` он знает, как найти код Python.125126Вторая команда создала `ni.dll` (а также `.obj` и `.lib`), который знает, как найти необходимые функции из spam, а также из исполняемого файла Python.127128Не каждый идентификатор экспортируется в таблицу поиска. Если вы хотите, чтобы другие модули (включая Python) могли видеть ваши идентификаторы, необходимо указать `_declspec(dllexport)`, например `void _declspec(dllexport) initspam(void)` или `PyObject _declspec(dllexport) *NiGetSpamData(void)`.129130Developer Studio добавит много ненужных библиотек импорта, увеличив размер исполняемого файла примерно на 100 КБ. Чтобы избавиться от них, в диалоге Project Settings на вкладке Link укажите *ignore default libraries*. Добавьте правильную `msvcrtxx.lib` в список библиотек.131