SWIG — популярный инструмент с открытым исходным кодом, позволяющий интегрировать код C/C++ практически с любым распространенным языком сценариев. Помимо прочего, он облегчает доступ к коду на C/C++ , улучшает тестируемость и позволяет перенести часть вашего кода Ruby на высокопроизводительные модули C/C++.

Арпан Сен, технический директор, Synapti Computer Aided Design Pvt Ltd

Арпан Сен (Arpan Sen) – ведущий инженер, работающий над разработкой программного обеспечения в области автоматизации электронного проектирования. На протяжении нескольких лет он работал над некоторыми функциями UNIX, в том числе Solaris, SunOS, HP-UX и IRIX, а также Linux и Microsoft Windows. Он проявляет живой интерес к методикам оптимизации производительности программного обеспечения, теории графов и параллельным вычислениям. Арпан является аспирантов в области программных систем.



13.06.2012

Установка SWIG

В этой статье используется SWIG версии 2.0.4 (ссылка на сайт, где его можно скачать, приведена в Ресурсах). Сборка и установка SWIG выполняется традиционно для ПО с открытым исходным кодом. Введите в командной строке следующие команды:

tar xvzf swig-2.0.4.tar.gz
./configure –prefix=
/your/swig/install/path
make
make install

Обратите внимание, что переданный в префикс путь должен быть абсолютным.

Языки C и C++ широко применяются (и совершенно справедливо) в качестве предпочтительной платформы для создания высокопроизводительного кода. Распространенным требованием к разработчикам является открытие доступа к коду C/C++ через интерфейсы языков сценариев. Именно здесь вступает в игру SWIG (Simplified Wrapper and Interface Generator). SWIG позволяет открыть доступ к вашему коду на C/C++ из широкого набора языков сценариев, включая Ruby, Perl, Tcl и Python. В этой статье в качестве языка сценариев, обращающегося к функциональности C/C++, выступает Ruby. Для работы с материалами этой статьи от вас потребуется умеренное знание языков C/C++ и Ruby.

SWIG прекрасно работает в нескольких сценариях, позволяя в том числе:

  • предоставить интерфейс сценариев для кода C/C++ для упрощения работы пользователей;
  • добавить расширения в код Ruby или заменить существующие модули высокопроизводительными альтернативными версиями;
  • проводить модульное и интеграционное тестирование кода в среде исполнения сценариев;
  • выполнять разработку графических интерфейсов пользователя, скажем, в TK, и интегрировать их с серверными приложениями C/C++.

К тому же значительно проще отлаживать SWIG, чем каждый раз запускать отладчик GNU.

Переменные среды Ruby

SWIG генерирует код оболочки C/C++, которому для правильной компиляции необходим файл ruby.h. Проверьте свою установку Ruby и убедитесь в присутствии файла ruby.h: рекомендуется также иметь переменную среды RUBY_INCLUDE, указывающую на папку с файлом ruby.h, и RUBY_LIB, указывающую на папку с библиотекой Ruby.

Hello World на SWIG

В качестве входных данных SWIG принимает файл с объявлениями ANSI C/C++ и директивами SWIG. Я называю этот файл интерфейсным файлом SWIG. Важно помнить, что SWIG нужна лишь та информация, которая необходима для генерации кода оболочки. Обычно интерфейсный файл имеет расширение *.i или *.swg. Вот первый пример интерфейсного файла test.i:

%module test
%constant char* Text = "Hello World with SWIG"

Запустите этот код с помощью SWIG:

swig –ruby test.i

Командная строка во втором фрагменте кода создает в текущей папке файл test_wrap.c. Теперь на основе этого файла C надо создать общую библиотеку. Для этого используется следующая командная строка:

bash$ gcc –fPIC –c test_wrap.c –I$RUBY_INCLUDE
bash$ gcc –shared test_wrap.o –o test_wrap.so –lruby –L$RUBY_LIB

Вот и все. Все готово, и вам осталось только запустить интерактивную оболочку Ruby (IRB) и ввести команду 'test_wrap', чтобы проверить модуль Test и его содержимое. Вот что происходит на стороне Ruby:

irb(main):001:0> require 'test_wrap'
=> true
irb(main):002:0> Test.constants
=> ["Text"]
irb(main):003:0> Test:: Text
=> "Hello World with SWIG"

SWIG можно использовать для генерации расширений для различных языков. Чтобы узнать все доступные возможности, просто подайте команду swig –help. Для Ruby введите swig –ruby <иинтерфейсный файл>; для Perl используйте команду swig –perl <интерфейсный файл>.

SWIG также можно использовать для генерации кода C++. Для этого укажите в командной строке ключ –c++. В предыдущем примере команда swig –c++ –ruby test.i создает в текущей папке файл с именем test_wrap.cxx.


Основы SWIG

Синтаксис интерфейсного файла SWIG представляет собой расширение языка C. На самом деле SWIG обрабатывает входной файл с помощью специального препроцессора C. Кроме того, работа SWIG в интерфейсном файле контролируется специальными директивами (%module, %constant и т.п.), которым предшествует знак процента (%). Также интерфейсы SWIG позволяют определять блоки, которые начинаются с конструкции %{ и заканчиваются конструкцией %}. Все, что расположено между %{ и %}, дословно копируется в создаваемый файл обертки.

Подробнее об именах модулей

Можно определить глубоко вложенный модуль типа rubytest::test34::example, указав его в виде %module "rubytest::test34::example". Другой вариант заключается в добавлении %module example в код интерфейса с префиксом rubytest::test34 из командной строки, как в следующем примере:

bash$ swig –c++ –ruby –prefix “rubytest::test34” test.i

Интерфейсные файлы SWIG должны начинаться с объявления %module— например, %module имя-модуля, где имя-модуля это имя модуля расширения целевого языка. Если целевым языком является Ruby, то это похоже на создание модуля Ruby. Можно заменить имя модуля, использовав в командной строке опцию –module новое-имя-модуля: В этом случае именем модуля на целевом языке будет (как вы уже догадались) новое-имя-модуля. Теперь давайте перейдем к константам.

Функция инициализации модуля

SWIG имеет специальную директиву %init, которая используется для инициализации модуля. Код, определенный внутри блока %{ … %}, следующего за %init, вызывается при загрузке модуля. Вот пример такого кода:

%module test
%constant char* Text = “Hello World от SWIG”
%init %{ 
printf(“Здесь выполняется инициализация и т.п.\n”);
%}

Теперь перезапустите IRB. Вот что вы получите после загрузки модуля:

irb(main):001:0> require 'test'
Здесь выполняется инициализация и т.п.
=> true

Константы SWIG

Существует несколько способов определить константы C/C++ в интерфейсном файле. Чтобы проверить, раскрываются ли эти константы модулю Ruby, напечатайте в командной строке IRB <module-name>.constants при загрузке общей библиотеки. Константы можно определить любым из следующих способов:

  • С помощью #define в интерфейсном файле
  • С помощью enums
  • С помощью директивы %constant

Обратите внимание, что константы Ruby должны начинаться с заглавной буквы. Так что если ваш интерфейсный файл содержит определение вроде #define pi 3.1415, SWIG автоматически исправит его на #define Pi 3.1415 и выдаст при этом предупреждение:

bash$ swig –c++ –ruby test.i
test.i(3) : Warning 801: Wrong constant name (corrected to 'Pi')
(Неверное имя константы (исправлено на 'Pi'))

Следующий пример содержит несколько констант. Запустите его командой swig –ruby test.i:

%module test
#define S_Hello "Hello World"
%constant double PI = 3.1415
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};

В листинге 1 показан результат исполнения SWIG.

Листинг 1. Раскрытие перечисления C для Ruby: что-то не так?
test_wrap.c: In function `Init_test':
test_wrap.c:2147: error: `Sunday' undeclared (first use in this function)
test_wrap.c:2147: error: (Each undeclared identifier is reported only once
test_wrap.c:2147: error: for each function it appears in.)
test_wrap.c:2148: error: `Monday' undeclared (first use in this function)
test_wrap.c:2149: error: `Tuesday' undeclared (first use in this function)
test_wrap.c:2150: error: `Wednesday' undeclared (first use in this function)
test_wrap.c:2151: error: `Thursday' undeclared (first use in this function)
test_wrap.c:2152: error: `Friday' undeclared (first use in this function)
test_wrap.c:2153: error: `Saturday' undeclared (first use in this function)

Что случилось? Если вы откроете test_wrap.c (листинг 2), то поймете, в чем проблема.

Листинг 2. Код, созданный в SWIG для перечисления
  rb_define_const(mTest, "Sunday", SWIG_From_int((int)(Sunday)));
  rb_define_const(mTest, "Monday", SWIG_From_int((int)(Monday)));
  rb_define_const(mTest, "Tuesday", SWIG_From_int((int)(Tuesday)));
  rb_define_const(mTest, "Wednesday", SWIG_From_int((int)(Wednesday)));
  rb_define_const(mTest, "Thursday", SWIG_From_int((int)(Thursday)));
  rb_define_const(mTest, "Friday", SWIG_From_int((int)(Friday)));
  rb_define_const(mTest, "Saturday", SWIG_From_int((int)(Saturday)));

SWIG создает константы Ruby из Sunday, Monday и т.п., но при этом исходное объявление enum для day в созданном файле отсутствует. Простейшее решение этой проблемы — разместить код enum внутри блока %{ … %}, чтобы создаваемый файл знал о константах перечислимого типа, как показано в листинге 3.

Листинг 3. Правильное раскрытие перечисления C для Ruby
%module test
#define S_Hello "Hello World"
%constant double PI = 3.1415
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}; 
%{
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}; 
%}

Обратите внимание, что само по себе объявление enum не делает константы перечислимого типа доступными в среде исполнения сценария: интерфейсный файл должен содержать и код C внутри блока %{ … %}, и объявление enum.

Представляем специальную директиву %inline

Листинг 3 выглядит ужасно — из-за неуместного дублирования кода enum. Чтобы устранить это дублирование, нужно использовать директиву SWIG %inline. Директива %inline вставляет весь код следующего за ней блока %{ … %} в интерфейсный файл, что полезно как для предпроцессора SWIG, так и для компилятора C. В листинге 4 показан переделанный код, в котором enum используется с директивой %inline.

Листинг 4. Применение директивы %inline для устранения дублирования кода
%module test
#define S_Hello "Hello World"
%constant double PI = 3.1415
%inline %{
enum days {Sunday = 1, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}; 
%}

Еще более аккуратный код — %include

Скорее всего в сложной корпоративной среде будут присутствовать заголовки C/C++, определяющие глобальные переменные и константы, которые вы хотите раскрыть для системы исполнения cценариев. Применение в интерфейсном файле директив %include <header.h> и %{ #include <header.h> %} решает проблему повторного определения всех элементов заголовка. Пример такого кода показан в листинге 5.

Листинг 5. Применение директивы %include
%module test
%include "header.h"

%{
#include "header.h"
%}

Директива %include работает и в исходных файлах на языке C/C++. При использовании в исходных файлах SWIG автоматически объявляет все функции как extern.


Хватит констант: давайте раскроем какие-нибудь функции

Проще всего начать изучение SWIG с того, чтобы объявить некоторую функцию C в интерфейсном файле, определить ее в некотором исходном файле и прикомпоновать соответствующий объектный файл в процессе создания общей библиотеки. В этом первом примере показана функция, вычисляющая факториал числа:

%module test
unsigned long factorial(unsigned long);

Вот код C, который я скомпилировал в factorial.o и скомпоновал в процессе создания test.so:

unsigned long factorial(unsigned long n) {
   return n == 1 ? 1 : n * factorial(n - 1);
}

В листинге 6 показан интерфейс Ruby.

Листинг 6. Тестирование кода из Ruby
irb(main):001:0> require 'test'
=> true
irb(main):002:0> Test.factorial(11)
=> 39916800
irb(main):003:0> Test.factorial(34)
=> 0

Факториал 34 вычислить не удалось, так как он не помещается в беззнаковое long.

Преобразование переменных Ruby в C/C++

Начнем с простых глобальных переменных. Обратите внимание, что глобальные переменные C/C++ не являются глобальными для Ruby: вы можете получить к ним доступ только как к атрибутам модуля. Добавьте следующие глобальные переменные в файл C и скомпонуйте исходный файл примерно так, как вы делали это для функций. SWIG автоматически генерирует методы установки и получения этих переменных. Соответствующий код на C выглядит так:

int global_int1;
long global_long1;
float global_float1; 
double global_double1;

В листинге 7 показан интерфейс для этого кода.

Листинг 7. Раскрытие интерфейса C для Ruby
%module test
%inline %{
extern int global_int1;
extern long global_long1;
extern float global_float1; 
extern double global_double1; 
%}

Теперь загрузите соответствующий модуль Ruby и проверьте добавление методов установки и получения:

irb(main):003:0> Test.methods
[…"global_float1", "global_float1=", "global_int1", "global_int1=", "global_long1", 
   "global_long1=", "global_double1", "global_double1=", …]

Теперь доступ к переменным стал проще:

irb(main):004:0> Test.global_long1 = 4327911
=> 4327911
irb(main):005:0> puts Test.global_long1
=> 4327911

Особенно интересно узнать, во что Ruby преобразовал типы int, long, float и double. См. листинг 8.

Листинг 8. Преобразование типов между Ruby и C/C++
irb(main):009:0> Test::global_long1.class
=> Fixnum
irb(main):010:0> Test::global_int1.class
=> Fixnum
irb(main):011:0> Test::global_double1.class
=> Float
irb(main):012:0> Test::global_float1.class
=> Float

Преобразование структур и классов из C++ в Ruby

Раскрытие структур и классов для Ruby выполняется так же, как и для простых типов данных C/C++. Вам нужно просто определить в интерфейсном файле структуру и соответствующие методы. Листинг 9 объявляет простую структуру Point (точка) и функцию для расчета расстояния между точками. На стороне Ruby вы создаете новую точку Point как Test::Point.new и выполняете расчет расстояния как Test.distance_between. Функция distance_between определена в отдельном исходном файле C++, который компонуется с общей библиотекой модуля. Ниже приведен пример кода для интерфейса SWIG:

Листинг 9. Раскрытие структур и соответствующего интерфейса для Ruby
%module test

%inline %{
typedef struct Point {
  int x;
  int y;
};
extern float distance_between(Point& p1, Point& p2);
%}

В листинге 10 показано, как применять эти функции в Ruby.

Листинг 10. Проверка функций C/C++ из Ruby
irb(main):002:0> a = Test::Point.new
=> #<Test::Point:0x2d04260>
irb(main):003:0> a.x = 10
=> 10
irb(main):004:0> a.y = 20
=> 20
irb(main):005:0> b = Test::Point.new
=> #<Test::Point:0x2cce668>
irb(main):006:0> b.x = 20
=> 20
irb(main):007:0> b.y = 10
=> 10
irb(main):008:0> Test.distance_between(a, b)
=> 14.1421356201172

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

%defaultctor и другие атрибуты

Если вы проверите значения по умолчанию координат точки x и y, то увидите, что они равны 0. Это не случайно. SWIG генерирует для вашей структуры стандартные конструкторы. Эту функцию можно отключить, добавив в интерфейсный файл %nodefaultctor Point;, как показано в листинге 11.

Листинг 11. Отключение стандартного конструктора для структур C++
%module test
%nodefaultctor Point;
%inline %{
typedef struct Point {
  int x;
  int y;
};
%}

Теперь нужно создать явный конструктор для структуры Point. В противном случае вы увидите следующий код:

irb(main):005:0> a = Test::Point.new
TypeError: allocator undefined for Test::Point
        from (irb):5:in `new'
        from (irb):5

Можно сделать так, чтобы каждая структура явно определяла свой конструктор, указав в интерфейсном файле %nodefaultctor;. Кроме того, SWIG имеет директиву %nodefaultdtor для реализации аналогичной функциональности для деструкторов.


Наследование свойств C++ и интерфейс Ruby

Простоты ради давайте предположим, что в интерфейсном файле у нас есть два класса C++Base и Derived. SWIG точно знает, что Derived получен из Base. С точки зрения Ruby вы можете просто использовать Derived.new и быть уверенным, что созданный объект знает о том, что он произошел от Base. В листинге 12 показан тестовый код на Ruby; на стороне C++ или интерфейса SWIG ничего особенного делать не надо.

Листинг 12. Интерфейс SWIG обрабатывает наследование свойств C++
irb(main):003:0> a = Test::Derived.new
=> #<Test::Derived:0x2d06270>
irb(main):004:0> a.instance_of? Test::Derived
=> true
irb(main):005:0> a.instance_of? Test::Base
=> false
irb(main):006:0> Test::Derived < Test::Base
=> true
irb(main):007:0> Test::Derived > Test::Base
=> false
irb(main):008:0> a.is_a? Test::Derived
=> true
irb(main):009:0> a.is_a? Test::Base
=> true

Однако с множественным наследованием C++ дела обстоят не так гладко. Если Derived был наследован из Base1 и Base2, то по умолчанию SWIG просто проигнорирует Base2. При этом вы получите от SWIG такое сообщение:

Warning 802: Warning for Derived d: base Base2 ignored. 
   Multiple inheritance is not supported in Ruby.
   (Предупреждение для Derived d: base Base2 игнорируется. 
   Множественное наследование в Ruby не поддерживается.)

Если честно, то SWIG здесь ни при чем, поскольку Ruby не поддерживает множественное наследование. Чтобы SWIG работал, нужно передать в командную строку опцию –minherit:

bash$ swig -ruby -minherit -c++ test.i

Важно понимать, каким образом SWIG обрабатывает множественное наследование. Производный класс в C++ соответствует классу в Ruby, который был получен не из Base1 или Base2. Вместо этого код Base1 и Base2 преобразуется в модули и включается в Derived. В терминах Ruby это называется примесью. Получившийся псевдокод показан в листинге 13.

Листинг 13. Имитация множественного наследования в Ruby
class Base1
  module Impl
  # Define Base1 methods here
  end
  include Impl
end

class Base2
  module Impl
  # Define Base2 methods here
  end
  include Impl
end

class Derived
  module Impl
  include Base1::Impl
  include Base2::Impl
  # Define Derived methods here
  end
  include Impl
end

Давайте проверим это утверждение из интерфейса Ruby с помощью метода included_modules, как показано в листинге 14.

Листинг 14. Несколько модулей как часть класса Ruby
irb> Test::Derived.included_modules
=> [Test::Derived::Impl, Test::Base::Impl, Test::Base2::Impl, Kernel]

irb> Test::Derived < Test::Base
=> nil

irb> Test::Derived < Test::Base2
=> nil

Обратите внимание, что тест иерархии классов не пройден (что и следовало ожидать), но функциональность Base и Base2 по-прежнему доступна разработчику приложения через класс Derived.


Указатели и интерфейс Ruby

Поскольку в Ruby нет эквивалента указателей, возникает вопрос, что происходит с методами C/C++, которые принимают или возвращают указатели? Это ставит нас перед одной из наиболее сложных проблем для систем, подобных SWIG, основная задача которых заключается в преобразовании (маршилизации) типов данных между исходным и целевым языками. Давайте рассмотрим следующую функцию:

void addition(const int* n1, const int* n2, int* result) { 
  *result = *n1 + *n2;
}

Для решения этой проблемы в SWIG вводится понятие отображений типов —typemaps. Вы можете легко определять, какой тип Ruby вы хотите привязать к int*, float* и т.п. К счастью, SWIG уже проделал за вас большую часть рутинной работы. Вот, например, один из самых простых возможных интерфейсов для добавления:

%module Test
%include typemaps.i
void addition (int* INPUT, int* INPUT, int* OUTPUT);

%{ extern void addition(int*, int*, int*); %}

Теперь опробуйте этот код из Ruby как Test::addition(1, 2). Он должен сработать. Чтобы лучше понять происходящее, загляните в папку lib/ruby. SWIG использует синтаксис int* INPUT для преобразования указателя в объект. Для преобразования типа из Ruby в C/C++ SWIG использует следующий синтаксис:

%typemap(in) int* {
  … type conversion code from Ruby to C/C++
}

Аналогично, преобразование кода из C/C++ в Ruby выполняется так:

%typemap(out) int* {
  … type convesion code from C/C++ to Ruby
}

Преобразование типов удобно не только для указателей: его можно использовать практически для любого преобразования данных между Ruby и C/C++.


Заключение

Из этой статьи вы узнали, как раскрывать константы; переменные, включая структуры и классы; функции и перечислимые типы C/C++ в интерфейсе для Ruby. Попутно вы познакомились с такими директивами SWIG, как %module, %init, %constant, %inline, %include и %nodefaultctor. На самом деле возможности SWIG гораздо шире; дополнительную информацию вы найдете в превосходном PDF-документе, который входит в комплект поставки SWIG.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

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


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


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

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

 


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

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

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



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

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

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

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

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

 


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


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=821046
ArticleTitle=SWIG для тех, кто торопится
publish-date=06132012