Содержание страницы
Сборка расширений C и C++ в Windows¶Building C and C++ Extensions on Windows
В этой главе кратко объясняется, как создать модуль расширения для Windows на Python с помощью Microsoft Visual C++, после чего приводится более подробная справочная информация о том, как это работает. Материал полезен как программисту Windows, осваивающему сборку расширений Python, так и программисту Unix, заинтересованному в создании программного обеспечения, которое успешно собирается как на Unix, так и на Windows.
Авторам модулей рекомендуется использовать подход distutils для сборки модулей расширения вместо описанного в этом разделе. При этом всё равно понадобится компилятор C, который использовался для сборки Python; обычно это Microsoft Visual C++.
Примечание
В этой главе упоминается ряд имен файлов, содержащих закодированный номер версии Python. Эти имена файлов представлены с номером версии в виде XY; на практике 'X' – это номер старшей версии, а 'Y' – номер младшей версии используемого выпуска Python. Например, если вы используете Python 2.2.1, XY на самом деле будет 22.
Подход из сборника рецептов¶A Cookbook Approach
В Windows, как и в Unix, есть два подхода к сборке модулей расширения: использовать пакет distutils для управления процессом сборки или делать всё вручную. Подход distutils хорошо работает для большинства расширений; документация по использованию distutils для сборки и упаковки модулей расширения доступна в Distributing Python Modules. В этом разделе описывается ручной подход к сборке расширений Python, написанных на C или C++.
Чтобы собирать расширения по этим инструкциям, нужна копия исходных кодов Python той же версии, что и установленный Python. Потребуется Microsoft Visual C++ «Developer Studio»; файлы проекта предоставляются для VC++ версии 7.1, но можно использовать и более старые версии VC++. Обратите внимание: следует использовать ту же версию VC++, которая использовалась для сборки самого Python. Примеры файлов, описанные здесь, распространяются с исходниками Python в каталоге PC\example_nt\.
Скопировать файлы примеров – Каталог example_nt – подкаталог PC, чтобы все PC-специфичные файлы находились в одном каталоге в дистрибутиве исходников. Однако использовать каталог example_nt непосредственно из этого расположения нельзя. Сначала скопируйте или переместите его на уровень выше, чтобы example_nt стал соседним с каталогами PC и Include. Всю работу выполняйте в этом новом месте.
Открыть проект – В VC++ используйте диалог File ‣ Open Solution (не File ‣ Open!). Перейдите к example.sln в копии каталога example_nt, созданной на предыдущем шаге, и выберите его. Нажмите Open.
Собрать пример DLL – Чтобы проверить, что всё настроено правильно, попробуйте собрать:
Выберите конфигурацию. Это необязательный шаг. Выберите Build ‣ Configuration Manager ‣ Active Solution Configuration, а затем Release или Debug. Если пропустить этот шаг, VC++ по умолчанию использует конфигурацию Debug.
Соберите библиотеку DLL. Выберите Build ‣ Build Solution. Будут созданы все промежуточные и результирующие файлы в подкаталоге Debug или Release, в зависимости от выбранной на предыдущем шаге конфигурации.
Тестирование DLL в режиме отладки – После успешной сборки Debug откройте окно DOS и перейдите в каталог example_nt\Debug. Теперь вы сможете повторить следующий сеанс (C> – приглашение DOS, >>> – приглашение Python; заметьте, что информация о сборке и различный отладочный вывод Python могут не полностью совпадать с этим дампом экрана):
C>..\..\PCbuild\python_d Adding parser accelerators ... Done. Python 2.2 (#28, Dec 19 2001, 23:26:37) [MSC 32 bit (Intel)] on win32 Type "copyright", "credits" or "license" for more information. >>> import example [4897 refs] >>> example.foo() Hello, world [4903 refs] >>>
Поздравляем! Вы успешно собрали свой первый модуль расширения Python.
Создание собственного проекта – выберите имя и создайте для него каталог. Скопируйте в него свои исходники на C. Обратите внимание: имя файла исходного модуля не обязательно должно совпадать с именем модуля, но имя функции инициализации должно ему соответствовать – можно импортировать модуль spam, только если его функция инициализации называется initspam, и она должна вызывать Py_InitModule со строкой "spam" в качестве первого аргумента (воспользуйтесь минимальным example.c в этом каталоге как руководством). По соглашению он находится в файле spam.c или spammodule.c. Выходной файл должен называться spam.pyd (в режиме Release) или spam_d.pyd (в режиме Debug). Расширение .pyd было выбрано, чтобы избежать путаницы с системной библиотекой spam.dll, для которой ваш модуль может быть интерфейсом Python.
Теперь возможны следующие варианты:
- Скопируйте example.sln и example.vcproj, переименуйте их в
spam.*, и отредактируйте их вручную, или
Создайте совершенно новый проект; инструкции ниже.
В любом случае скопируйте example_nt\example.def в spam\spam.def и отредактируйте новый spam.def так, чтобы его вторая строка содержала строку 'initspam'. Если вы создали новый проект самостоятельно, добавьте файл spam.def в проект сейчас. (Это небольшой файл всего из двух строк. Альтернативный подход – забыть о файле .def и добавить параметр /export:initspam куда-нибудь в настройки компоновщика, вручную отредактировав параметр в диалоге Project Properties).
Создание совершенно нового проекта – Используйте диалог File ‣ New ‣ Project, чтобы создать новую рабочую область проекта. Выберите Visual C++ Projects/Win32/ Win32 Project, введите имя (spam) и убедитесь, что расположение (Location) указывает на родительский каталог созданного вами каталога spam (который должен быть прямым подкаталогом дерева сборки Python, рядом с Include и PC). Выберите Win32 в качестве платформы (в моей версии это единственный вариант). Убедитесь, что переключатель Create new workspace выбран. Нажмите OK.
Теперь создайте файл spam.def, как указано в предыдущем разделе. Добавьте исходные файлы в проект с помощью Project ‣ Add Existing Item. Установите фильтр *.* и выберите spam.c и spam.def, затем нажмите OK. (Можно добавлять их по одному – тоже подойдет.)
Теперь откройте диалог Project ‣ spam properties. Нужно изменить лишь несколько настроек. Убедитесь, что в раскрывающемся списке Settings for: выбрано All Configurations. Перейдите на вкладку C/C++. В выпадающем меню вверху выберите категорию General. В поле ввода с надписью Additional Include Directories введите следующий текст:
..\Include,..\PC
Затем на вкладке Linker выберите категорию General и введите:
..\PCbuild
в текстовое поле с надписью Additional library Directories.
Теперь нужно добавить некоторые настройки, зависящие от режима:
Выберите Release в раскрывающемся списке Configuration. Перейдите на вкладку Link, выберите категорию Input и добавьте pythonXY.lib в список в поле Additional Dependencies.
Выберите Debug в раскрывающемся списке Configuration и добавьте pythonXY_d.lib в список в поле Additional Dependencies. Затем перейдите на вкладку C/C++, выберите Code Generation и в раскрывающемся списке Runtime library выберите Multi-threaded Debug DLL.
Снова выберите Release из раскрывающегося списка Configuration. В раскрывающемся списке Runtime library выберите Multi-threaded DLL.
Если ваш модуль создаёт новый тип, при использовании этой строки могут возникнуть проблемы:
PyObject_HEAD_INIT(&PyType_Type)
Измените на:
PyObject_HEAD_INIT(NULL)
и добавьте следующее в функцию инициализации модуля:
MyObject_Type.ob_type = &PyType_Type;
Обратитесь к разделу 3 Python FAQ за подробностями о том, почему это необходимо сделать.
Различия между Unix и Windows¶Differences Between Unix and Windows
В Unix и Windows используются совершенно разные парадигмы загрузки кода во время выполнения. Прежде чем пытаться собрать модуль, который может загружаться динамически, следует понимать, как работает ваша система.
В Unix файл shared object (.so) содержит код для использования программой, а также имена функций и данных, которые он ожидает найти в программе. Когда файл присоединяется к программе, все ссылки на эти функции и данные в коде файла изменяются, чтобы указывать на фактические расположения в памяти программы, где размещены функции и данные. По сути, это операция компоновки.
В Windows файл динамической библиотеки (DLL) (.dll) не имеет висячих ссылок. Вместо этого доступ к функциям или данным осуществляется через таблицу поиска. Таким образом, код DLL не нужно корректировать во время выполнения для обращения к памяти программы; вместо этого код уже использует таблицу поиска DLL, а сама таблица изменяется во время выполнения, чтобы указывать на нужные функции и данные.
В Unix существует только один тип библиотечного файла (.a), который содержит код из нескольких объектных файлов (.o). На этапе компоновки для создания файла shared object (.so) компоновщик может обнаружить, что не знает, где определён идентификатор. Компоновщик будет искать его в объектных файлах библиотек; если найдёт, он включит весь код из этого объектного файла.
В Windows существует два типа библиотек: статическая библиотека и библиотека импорта (обе называются .lib). Статическая библиотека похожа на файл .a в Unix; она содержит код, который будет включён по мере необходимости. Библиотека импорта в основном используется только для подтверждения компоновщику, что определённый идентификатор является допустимым и будет присутствовать в программе при загрузке DLL. Таким образом, компоновщик использует информацию из библиотеки импорта для построения таблицы поиска для идентификаторов, которые не включены в DLL. Когда приложение или DLL компонуется, может быть создана библиотека импорта, которая понадобится для всех будущих DLL, зависящих от символов из этого приложения или DLL.
Предположим, вы собираете два модуля динамической загрузки, B и C, которые должны совместно использовать другой блок кода A. В Unix вы бы не передавали A.a компоновщику для B.so и C.so; это привело бы к его включению дважды, так что B и C имели бы по собственной копии. В Windows сборка A.dll также создаст A.lib. Вы действительно передаёте A.lib компоновщику для B и C. A.lib не содержит код; он содержит только информацию, которая будет использоваться во время выполнения для доступа к коду A.
В Windows использование библиотеки импорта отчасти похоже на использование import spam; это даёт доступ к именам из spam, но не создаёт отдельную копию. В Unix компоновка с библиотекой больше похожа на from spam import *; она создаёт отдельную копию.
Использование DLL на практике¶Using DLLs in Practice
Python для Windows собирается в Microsoft Visual C++; использование других компиляторов может работать или не работать (хотя Borland, похоже, работает). Остальная часть этого раздела относится только к MSVC++.
При создании DLL в Windows необходимо передавать pythonXY.lib компоновщику. Чтобы собрать две DLL, spam и ni (которая использует функции C из spam), можно использовать следующие команды:
cl /LD /I/python/include spam.c ../libs/pythonXY.lib
cl /LD /I/python/include ni.c spam.lib ../libs/pythonXY.lib
Первая команда создала три файла: spam.obj, spam.dll и spam.lib. Spam.dll не содержит никаких функций Python (таких как PyArg_ParseTuple), но он умеет находить код Python благодаря pythonXY.lib.
Вторая команда создала ni.dll (а также .obj и .lib), который знает, как найти необходимые функции из spam, а также из исполняемого файла Python.
Не каждый идентификатор экспортируется в таблицу поиска. Если вы хотите, чтобы другие модули (включая Python) могли видеть ваши идентификаторы, необходимо указать _declspec(dllexport), например void _declspec(dllexport) initspam(void) или PyObject _declspec(dllexport) *NiGetSpamData(void).
Developer Studio добавит много ненужных библиотек импорта, увеличив размер исполняемого файла примерно на 100 КБ. Чтобы избавиться от них, в диалоге Project Settings на вкладке Link укажите ignore default libraries. Добавьте правильную msvcrtxx.lib в список библиотек.