Pull to refresh

Python, Модули, SWIG, Windows

Reading time 4 min
Views 20K
Эта статья – описание моих экспериментов по сборке модулей для Python. Мне понадобился высокоуровневый интерфейс к библиотеке LibRaw, притом в первую очередь под Windows.

Последний раз модуль для питона на 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.

Как показал только что поставленный опыт, всё прекрасно работает.

Для более въедливых товарищей печенька инструкция на английском. Как всегда, малость устаревшая (или, возможно, слишком новая). Пользуясь ею дословно, мне не удалось добиться профита. Ниже инструкция на русском, сокращённая и проверенная:
  1. Создаём новый проект для создания Windows DLL
  2. Создаём файлы my_module.cxx (или .c) для наших классов/функций, my_module.i для описания интерфейса. И прописываем (но не создаем) my_module_wrap.cxx. Первые два файла, я, естественно, подрезал из тех же примеров, но можно взять их например с википедии)
  3. Выбираем в среде 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
  4. Дальше нужно задать местожительство заголовков питона. В свойствах проекта выбираем Configuration Properties » /C++ и устанавливаем Additional include directories в $(PYTHON_INCLUDE) (Зря его что-ли задавали?)
  5. Дальше по плану свойства линкера (Linker):
    Input » Aditional Dependies: "$(PYTHON_LIB)"
    General » Output File: $(ProjectDir)\_$(ProjectName).pyd
    В первом случае кавычки не помешают, а во втором обратите внимание на подчёркивание. Зачем оно — я расскажу ниже.
  6. Можно собирать проект. Не забудьте переключиться в Release конфигурацию. Чтобы использовать Debug, придется собирать питон из исходников, а оно нам надо?
  7. Профит


Способ второй: 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

Оба способа протестированы и работают. Даже странно.

Любые советы (как можно было сделать проще) приветствуются. Критика тоже (но желательно с указанием как именно надо было сделать). Надеюсь мне не придётся ещё раз переписывать статью более чем наполовину, что произошло после первого же комментария. После которого я копнул чуть глубже, чем было до того.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+18
Comments 5
Comments Comments 5

Articles