Последний раз модуль для питона на C++ я писал в 2004 году. Модуль к мертворожденной (к счастью не мной) библиотеке ( я тупо продавал свои умения за зарплату). Естественно, навыки не закрепились. Помню, что SWIG сильно облегчил мне работу, поскольку нужен был объектный интерфейс, а «ручками» его писать ломало. Память у меня профессиональная – то есть избирательная и короткая, поэтому пришлось прыгать сначала.
Это статья только про настройку SWIG для Python под Windows. Писать же модули на C/C++ с использованием SWIG гораздо проще, чем всё настроить (кстати, у меня такое впечатление, что это парадигма современного программирования).
Первым делом я полез в яндексогугл. Epic fail — все советы и воркарраунды устарели года на три. Следуя им, мне пришлось совершать абсолютно ненужные шаги по сборке питона из исходников. Лишних шагов в статье нет, хотя питон собирается без проблем. Но если будете собирать, то полезный совет: возьмите из «коробочной» поставки файл Include/pycohfig.h и тихо положите его в собранную версию — пригодится.
Хорошие новости. Для сборки модулей можно использовать Microsoft Visual C++ Express Edition 2008, поскольку Питон 2.6+ собирается им. До этого приходилось либо компилировать питон из исходников, либо откапывать покрытый мхом VC++ 7.0, а я то и 6.0. Ещё более хорошие новости — модули можно компилировать MinGW. Кстати? тестовый модуль я откомпилировал gcc «включенным» в Strawberry Perl (довольно цинично если вспомнить древние питоно-перловые войны). Просто потому что это первый gcc в Path.
Итак, нам нужно взять пример из SWIG и превратить его в модуль. Для этого есть два способа.
Но, сперва, нужно установить собранный под Windows SWIG (swigwin-2.0.4) и прописать его в PATH. Примеры использования поставляются с ним же.
Способ первый: Visual C++
Нам понадобиться установить переменные среды окружения.
PYTHON_INCLUDE = С:\Python\Include PYTHON_LIB = C:\Python\Libs\python27.lib
Дальше можно схалтурить — взять один из готовых примеров, для которых уже созданы солюшен (.sln) и проект. И спокойно, на его базе, сделать свой модуль. Например, открыть
SWIG\Examples\python\class\exmple.sln. Переименовать examples.i, examples.cxx и examples_wrap.cxx, соответственно, в mymodule.i, mymodule.cxx и mymodule_wrap.cxx.Как показал только что поставленный опыт, всё прекрасно работает.
Для более въедливых товарищей
- Создаём новый проект для создания Windows DLL
- Создаём файлы
my_module.cxx(или .c) для наших классов/функций,my_module.iдля описания интерфейса. И прописываем (но не создаем)my_module_wrap.cxx. Первые два файла, я, естественно, подрезал из тех же примеров, но можно взять их например с википедии)
- Выбираем в среде
my_module.i, в свойствах (Custom build setup) устанавливаем:
— Command Line:swig.exe -c++ -python $(InputPath)
— Outputs:$(InputName)_wrap.cxx
(Для C проектов, не нужно указывать ключ-c++в первом случае. И расширение.cxx(достаточно .c) во втором.)
В итоге, мы создадим файлmy_module_wrap.c(xx)из интерфейсного файлаmy_module.i
- Дальше нужно задать местожительство заголовков питона. В свойствах проекта выбираем Configuration Properties » /C++ и устанавливаем Additional include directories в
$(PYTHON_INCLUDE)(Зря его что-ли задавали?)
- Дальше по плану свойства линкера (Linker):
— Input » Aditional Dependies:"$(PYTHON_LIB)"
— General » Output File:$(ProjectDir)\_$(ProjectName).pyd
В первом случае кавычки не помешают, а во втором обратите внимание на подчёркивание. Зачем оно — я расскажу ниже.
- Можно собирать проект. Не забудьте переключиться в Release конфигурацию. Чтобы использовать Debug, придется собирать питон из исходников, а оно нам надо?
- Профит
Способ второй: distutils
Что не может не радовать, питон 2.7 уже заточен под то, чтобы собирать модули используя distutils (в предыдущих версиях требовалось 3 килограмма шаманства ). При этом, мы можем использовать как и вышеупомянутый (не к ночи) MVC 2008, так и свободный MinGW.
О маленьких граблях поджидающих нас на этом пути, я, поглаживая лоб, сейчас расскажу.
Опять же, бесчеловечные эксперименты будем ставить на примере из SWIG. Итак, в директории
SWIG/examples/classes создаём файл setup.py# -*- coding: utf-8 -*- import distutils from distutils.core import setup, Extension setup(name = "Simple example from the SWIG website", version = "0.007", ext_modules = [Extension( "_example", # грабель первый: не забываем подчеркивание ["example.i","example.cxx"], swig_opts=['-threads', '-c++'], # грабель второй: опции swig тут )] );
Грабель первый.
Подчеркивание. Если его не указать получим ошибку
LINK : error LNK2001: unresolved external symbol initexample. Откуда она растёт?
Python, при создании модуля «руками» ожидает, что в скомпилированном модуле (бинарной части) будет функция с имением
init<имя_модуля>. Сделано чтобы модуль, написанный на C/C++ мог вкурить её при загрузке бинарного модуля (и сообщить питону, что всё ок). Враппер (SWIG) функцию честно создаёт. Но SWIG создаёт два(!) файла для нашего модуля:
<имя_модуля>.py — питоновская обертка, содержащая нативные вызовы функций будущего модуля и _<имя_модуля>.pyd (обратите внимание на подчеркивание) — бинарная библиотека. Дело в том, что бинарную библиотеку можно загрузить «в питон» той же самой инструкцией import, что и нативную (подчёркивание «подразумевается»). Соответственно, имена нативной обертки и бинарной библиотеки должны различаться. Они и различаются — на подчеркивание. SWIG ожидает от нас, что бинарная библиотека будет с подчеркиванием, поэтому создал функцию с именем init_<имя_модуля>, про которую линкер ничего не знает. Вот и ошибка.
Грабель второй
Мне потребовалось некоторое время лазания по исходникам, чтобы понять как передавать опции командной строки в вызов командной строки swig. По умолчанию
distutil ожидает, что мы работаем с C. Поэтому потребовалось указать параметр -c++ (параметр -threads добавлен «для понтов», но может кому окажется не бесполезным).Сборка
Теперь, когда мы разобрались, собрать модуль можно двумя способами:
Собрать, использую MVC:
python setup.py buildСобрать, используя MinGW:
python setup.py build -cmingw32Оба способа протестированы и работают. Даже странно.
Любые советы (как можно было сделать проще) приветствуются. Критика тоже (но желательно с указанием как именно надо было сделать). Надеюсь мне не придётся ещё раз переписывать статью более чем наполовину, что произошло после первого же комментария. После которого я копнул чуть глубже, чем было до того.