Тонкости использования языка Python: Часть 8. Особенности взаимодействия с C++. Проект SWIG и обратная интеграция Python в С/C++ приложения

Статья является частью миницикла, посвященного тонким вопросам применения Python и разработки приложений на этом языке. В статье рассматривается проект SWIG для интеграции Python-приложений c C/С++ и обратная задача по интеграции Python в C/C++ приложения.

Олег Цилюрик, преподаватель тренингового отделения, Global Logic

Фото автораОлег Иванович Цилюрик, много лет был разработчиком программного обеспечения в крупных центрах разработки: ВНИИ РТ, НПО "Дельта", КБ ПМ. Последние годы работал над проектами в области промышленной автоматики, IP телефонии и коммуникаций. Автор нескольких книг. Преподаватель тренингового отделения международной софтверной компании Global Logic.



20.12.2013

Введение

Проект SWIG предоставляет универсальный интерфейс к C/C++ не только из языка Python, но и других языков. В его документации говорится, что SWIG – это инструмент разработки ПО, позволяющий интегрировать программы, написанные на языках C и С++, с другими высокоуровневыми языками программирования. SWIG может использоваться с различными типами языков, включая скриптовые языки, например, Perl, PHP, Python, Tcl и Ruby. Также поддерживаются такие языки как C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), D, Go, язык Java, включая платформу Android и др.


Архитектура SWIG

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

Этот проект активно развивается, так его последняя версия 2.0.11 была выпущена 15 сентября 2013 года. Так как SWIG является мультиплатформенным проектом, то пользователям предлагаются версии для платформ Windows и Linux (но в дальнейших примерах будет использоваться версия для ОС Linux). Актуальную версию SWIG можно собрать из исходных кодов (см. ссылку в разделе "Ресурсы"). Также его можно найти практически в любом дистрибутиве и установить с помощью менеджера пакетной системы Linux, как показано ниже:

$ sudo yum install swig*
...
---> Пакет swig.i686 0:2.0.8-1.fc17 помечен для установки
---> Пакет swig-doc.noarch 0:2.0.8-1.fc17 помечен для установки
...
Объем загрузки: 3.7 M
Объем изменений: 12 M
...

Базовая идея SWIG заключается в том, что:

  1. готовится реализация будущего модуля на языке C (.c) или C++ (.cc);
  2. создаётся интерфейсный файл (.i) на специальном макроязыке SWIG;
  3. SWIG генерирует код для интерфейсного файла (.c), который далее собирается вместе с реализацией модуля (.c) в разделяемую динамическую библиотеку модуля;

При этом язык программирования (например, Python), для которого генерируется интерфейсный файл, указывается в качестве входного параметра команды SWIG, а вид интерфейсного файла (.i) не зависит от того, для какого конкретного языка будет генерироваться интерфейс.

Далее будет представлен простой пример использования SWIG, который может послужить отправной точкой для последующих разработок. В листинге 1 представлен исходный модуль на языке С, реализующий требуемую функцию (см. файл ownmod.c в архиве python_c_interaction.tgz в разделе "Материалы для скачивания").

Листинг 1. Код реализации модуля
#include <stdio.h>

void echo( void ) {
   printf( "вывод из экспортированного кода!\n" );
}

К этому модулю следует подготовить файл с описанием интерфейса на макроязыке SWIG. В листинге 2 представлен пример такого файла для нашего модуля (см. файл ownmod.i из архива python_c_interaction.tgz):

Листинг 2. Определение интерфейса
%module ownmod
extern void echo( void );

Этих двух файлов достаточно для начала работы с SWIG. Далее выполняются следующие шаги:

  1. генерация кода интерфейса (по умолчанию это будет файл ownmod_wrap.c, но название файла можно переопределить опциями команды swig):
    $ swig -python -module ownmod ownmod.i
  2. компиляция всех исходных файлов .c в соответствующие объектные файлы .o:
    $ gcc -c -fpic ownmod_wrap.c ownmod.c -DHAVE_CONFIG_H -I/usr/include/python2.7
  3. сборка их в единую библиотеку:
    $ gcc -shared ownmod.o ownmod_wrap.o -o _ownmod.so
  4. также сборку в библиотеку можно выполнить следующей командой
    $ ld -shared ownmod.o ownmod_wrap.o -o _ownmod.so

Теперь мы можем использовать созданный модуль в Python, как показано в файле test.py в листинге 3:

Листинг 3. Тестовое приложение
#!/usr/bin/python
# -*- coding: utf-8 -*-

import ownmod
ownmod.echo()

Запустим тестовый Python-сценарий:

$ python test.py
вывод из экспортированного кода!

Хотя показанный пример крайне схематичен, но он показывает общую логику использования SWIG для разработки модулей любой степени сложности. Однако, в отдельных случаях описание интерфейсов (.i) на макроязыке SWIG может оказаться сложной задачей. В архив python_c_interaction.tgz включён пример функции (файл freq.c), подсчитывающей число вхождений каждого из 256 символов ASCII в тексте, переданном ей в качестве параметра.

Листинг 4. Исходная функция на языке С, требующая динамического выделения буфера
#include <stdlib.h>

int* frequency( char s[] ) {
   int *freq;
   char *ptr;
   freq = (int*)( calloc( 256, sizeof( int ) ) );
   if( freq != NULL )
      for( ptr = s; *ptr; ptr++ )
         freq[ *ptr ] += 1;
   return freq;
}

Подобная функция потребует более сложного файла с описанием интерфейса, так как она динамически выделяет память буфера, которая должна освобождаться вызывающим кодом Python. В листинге 5 представлен пример такого файла (см. файл freq.i в архиве python_c_interactions.tgz).

Листинг 5. Описание интерфейса для функции frequency()
%module freq
%typemap(out) int* {
   int i;
   $result = PyTuple_New( 256 );
   for( i = 0; i < 256; i++ )
      PyTuple_SetItem( $result, i, PyLong_FromLong( $1[ i ] ) );
   free( $1 );
}

extern int* frequency( char s[] );

Сборка модуля выполняется также как и в предыдущем примере. В листинге 6 представлено Python-приложение, использующее модуль с данной функцией.

Листинг 6. Программа подсчёта частоты вхождений символов
#!/usr/bin/python
# -*- coding: utf-8 -*-

from sys import argv
import freq

sarg = lambda : ( len( argv ) > 1 and argv[ 1 ] ) or str( input( "string?: " ) )

f = freq.frequency( sarg() )

s = ""
for i in range( 256 ):
    if f[ i ] != 0:
        s = s + "'%c'[0x%x]:%d " % ( i, i, f[ i ] )

print s

Запустим эту программу:

$ python test.py "1234123121 abc ab a"
' '[0x20]:3 '1'[0x31]:4 '2'[0x32]:3 '3'[0x33]:2 '4'[0x34]:1 'a'[0x61]:3 'b'[0x62]:2 ...

Ещё одним преимуществом использования SWIG является простота создания интерфейсных модулей к уже существующим DLL библиотекам, в частности, к системным библиотекам и библиотекам сторонних проектов GNU. В этом случае не требуется создавать никаких реализаций на С (они уже имеются), достаточно будет подготовить только файл описания интерфейса, как показано в листинге 10 (файл fileio.i из архива python_c_interaction.tgz):

Листинг 7. Интерфейс к файловым операциям
%module fileio
extern FILE *fopen(char *, char *);
extern int fclose(FILE *);
extern unsigned fread(void *ptr, unsigned size, unsigned nobj, FILE *);
extern unsigned fwrite(void *ptr, unsigned size, unsigned nobj, FILE *);
extern void *malloc(int nbytes);
extern void free(void *);

Остаётся выполнить сборку разделяемой библиотеки _fileio.so, реализующей интерфейс нового модуля. Обратите внимание, что в сборку включена стандартная C-библиотека libc.so, в которой и находится реализация:

 $ swig -python fileio.i 
 $ gcc -c -fpic fileio_wrap.c -DHAVE_CONFIG_H -I/usr/include/python2.7 \ 
      -I/usr/lib/python2.7/config 
 $ ld -shared fileio_wrap.o -lc -o _fileio.so 

Теперь в коде Python можно напрямую использовать файловые операции POSIX, как показано в листинге 8 (файл fiotest.py из архива python_c_interaction.tgz):

Листинг 8. Копирование файлов из Python с использованием POSIX операций
#!/usr/bin/python
# -*- coding: utf-8 -*-

from fileio import *
from sys import argv

def filecopy( source, target ):          # копирование файла 
    sum = long( 0 )
    f1 = fopen( source, "r" )
    if f1 == None: return -1
    f2 = fopen( target, "w" )
    buffer = malloc( 8192 )
    nbytes = fread( buffer, 1, 8192, f1 )
    while( nbytes > 0 ):
        fwrite( buffer, 1, nbytes, f2 )
        sum = sum + nbytes
        nbytes = fread( buffer, 1, 8192, f1 )
    free( buffer )
    fclose( f1 )
    fclose( f2 )
    return sum

n = filecopy( ( len( argv ) > 1 and argv[ 1 ] ) or "in.txt", \
              ( len( argv ) > 2 and argv[ 2 ] ) or "out.txt" )
print "скопировано %d байт" % n

Запустим данное приложение и посмотрим на результаты его работы:

$ python fiotest.py in.txt out.txt
скопировано 178 байт
$ ls -l *.txt
-rw-rw-r-- 1 olej olej 178 июня  21 23:48 in.txt
-rw-rw-r-- 1 olej olej 178 июня  22 00:14 out.txt

Интеграция Python-кода в C/C++ приложения

Интеграция Python-кода в проект, написанный на C или C++, представляет собой обратную задачу, которая также может возникнуть в различных областях. Многие крупные проекты (например, телефонные коммутаторы для IP-телефонии - Asterisk и FreeSWITCH) используют встраивание одного или нескольких интерпретирующих языков (Python, JavaScript, Lua, ...) для быстрой реализации сценариев, позволяющих управлять основной функциональностью. Также подобные языки могут применяться для конфигурации проекта без необходимости его повторной компиляции. В листинге 9 представлен простейший интерпретатор, встроенный в C-приложение, (см. файл interpr.c в разделе python_c_interaction.tgz):

Листинг 9. Интерпретатор Python, встроенный в C-приложение
/* Пример встраивания интерпретатора Python в другую программу */
#include "Python.h"

main( int argc, char **argv ) {
   Py_SetProgramName( argv[ 0 ] ); // Передает argv[0] интерпретатору Python
   Py_Initialize();                // Инициализация интерпретатора
   /* ... */
   while( 1 ) {             // Выполнение операторов Python
      char buf[ 120 ];
      fprintf( stdout, ">>> " );
      fflush( stdout );
      fgets( buf, sizeof( buf ) - 2, stdin );
      buf[ strlen( buf ) - 1 ] = '\n';
      buf[ strlen( buf ) ] = '\0';
      if( 0 == strcmp( buf, "quit\n" ) || 0 == strcmp( buf, "exit\n" ) ) break;
      PyRun_SimpleString( buf );
   }
   Py_Finalize();           // Завершение работы интерпретатора
}

Пример запуска данного приложения:

$ ./interpr
>>> print 2+2
4
>>> import sys
>>> print sys.version_info
sys.version_info(major=2, minor=7, micro=3, releaselevel='final', serial=0)
>>> quit

Хотя этот пример и похож на использование стандартного интерпретатора CPython, но на самом деле это самостоятельное приложение, так как в нём не используются клавиши редактирования вводимой строки, и в тоже время из него можно выйти, введя команду quit.

Важное преимущество подобного встраивания Python-функций в С-приложения — это возможность доступа к переменным и объектам C-кода из Python. Эта возможность обеспечивается специальным API для встраивания, входящим в состав Python, (дополнительную информацию об этом API можно найти в разделе "Ресурсы").


Заключение

В этой статье мы завершаем знакомство с различными технологиями расширения возможностей Python-проектов c помощью кода, написанного на языках C/C++. Выбор способа, подходящего к конкретной ситуации, как обычно остаётся за разработчиком проекта. Но при всех различиях и особенностях, каждая из этих технологий позволяет достичь одного и того же конечного результата — подключить фрагменты C/C++ кода к проекту на Python. Также в рамках этой статьи была рассмотрена и обратная задача: интеграция Python-кода в проект, написанный на C/C++.

В следующей статье, завершающей цикл "Тонкости использования Python", мы поговорим об особенностях применения Python для разработки GUI-приложений.


Загрузка

ОписаниеИмяРазмер
интеграция Python и С/С++python_с_interaction.tgz26KB

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • developerWorks Premium

    Эксклюзивные инструменты для построения вашего приложения. Узнать больше.

  • Библиотека документов

    Более трех тысяч статей, обзоров, руководств и других полезных материалов.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=958131
ArticleTitle=Тонкости использования языка Python: Часть 8. Особенности взаимодействия с C++. Проект SWIG и обратная интеграция Python в С/C++ приложения
publish-date=12202013