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

в 8:34, , рубрики: mingw, python, SWIG, visual c++, windows, модули, метки: , , , , ,

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

Последний раз модуль для питона на C++ я писал в 2004 году, модуль был к мертворожденной (к счастью не мной) библиотеке ( я тупо продавал свои умения за зарплату). Естественно, навыки не закрепились. Помню, что SWIG сильно облегчил мне работу, поскольку нужен был объектный интерфейс, а «ручками» его писать ломало. Память у меня профессиональная – то есть избирательная и короткая, поэтому пришлось прыгать сначала.

Это статья только про настройку SWIG для Python под Windows. Писать же модули с использованием 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 = С:PythonInclude
PYTHON_LIB = C:PythonLibspython27.lib

Дальше можно схалтурить — взять один из готовых примеров, для которых уже созданы солюшен (.sln) и проект. И спокойно, на его базе, сделать свой модуль. Например, открыть SWIGExamplespythonclass. Переименовать 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

Что не может не радовать питон уже заточен под то, чтобы собирать модули использую 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

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

Любые советы (как можно было сделать проще) приветствуются. Критика тоже (но желательно с указанием как именно надо было сделать). Надеюсь мне не придётся ещё раз перписывать статью более чем наполовину, что произошло после первого же комментария. После которого я копнул чуть глубже, чем было до того.

Статья об питоновском интерфейсе к LibRaw и собственно он сам в исходниках воспоследуют, как только интерфейс напишется. Понятно, что установить его можно будет без вышеуказанных колдунств.

Автор: mclander

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js