Языки 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.
В качестве входных данных 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 представляет собой расширение языка C. На самом деле SWIG обрабатывает входной файл с помощью специального препроцессора C. Кроме того, работа SWIG в интерфейсном файле контролируется специальными директивами (%module, %constant и т.п.), которым предшествует знак процента (%). Также интерфейсы SWIG позволяют определять блоки, которые начинаются с конструкции %{ и заканчиваются конструкцией %}. Все, что расположено между %{ и %}, дословно копируется в создаваемый файл обертки.
Интерфейсные файлы 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 |
Существует несколько способов определить константы 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 нет эквивалента указателей, возникает вопрос, что происходит с методами 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.
Научиться
-
Оригинал статьи: SWIG for developers in a hurry.
-
Ознакомьтесь с документацией SWIG.
-
Раздел AIX и UNIX на developerWorks: в разделе AIX и UNIX вы найдете большое количество информации обо всех аспектах администрирования AIX и о системах UNIX.
Получить продукты и технологии
-
Загрузите последнюю версию SWIG.
Обсудить
- Подпишитесь на developerWorks в Твиттере.
Арпан Сен (Arpan Sen) – ведущий инженер, работающий над разработкой программного обеспечения в области автоматизации электронного проектирования. На протяжении нескольких лет он работал над некоторыми функциями UNIX, в том числе Solaris, SunOS, HP-UX и IRIX, а также Linux и Microsoft Windows. Он проявляет живой интерес к методикам оптимизации производительности программного обеспечения, теории графов и параллельным вычислениям. Арпан является аспирантов в области программных систем.