LXF98:Perl и C++
Belogorie (обсуждение | вклад) (Новая: ==<font color=darkred>Как работать</font> с классами С++ из Perl== ''Огорчены, что PerlQt застрял на версии 3.008?Не беспокой...) |
Yaleks (обсуждение | вклад) м (→Взаимодействие с STL или ее аналогами) |
||
(не показана 1 промежуточная версия 1 участника) | |||
Строка 3: | Строка 3: | ||
''Огорчены, что PerlQt застрял на версии 3.008?Не беспокойтесь – '''Вадим Лихота''' расскажет, как решить подобную задачу своими силами. Если, конечно, хватит терпения.'' | ''Огорчены, что PerlQt застрял на версии 3.008?Не беспокойтесь – '''Вадим Лихота''' расскажет, как решить подобную задачу своими силами. Если, конечно, хватит терпения.'' | ||
− | Описаний того, как импортировать в Perl функции из С, достаточно много, а вот информацию об использовании классов C++ я встречал в виде кратких описаний только в | + | Описаний того, как импортировать в Perl функции из С, достаточно много, а вот информацию об использовании классов C++ я встречал в виде кратких описаний только в "''XS Cookbook''" <font color=darkblue>[1, 2]</font> и небольшой статье <font color=darkblue>[4]</font>. Пример использования класса С++ в Perl'е из "''XS Cookbook''" в сокращенном варианте перекочевал в perlxstut. Кроме того, на CPAN можно найти модули, импортирующие классы С++ и имеющие файлы импорта, которые можно использовать в качестве примера, такие, как Boost-Graph, Lucene, Search-Xapian,Однако они не покрывают многих вариантов подключения классов. |
Чтобы не умножать сущности без надобности, т.е. не писать новых классов, которые потом нигде не пригодятся, воспользуемся уже готовой библиотекой ''QtCore'' из состава ''Qt4''. Для удобства я буду приводить части заголовочных файлов этой библиотеки, но все примеры будут работоспособны при подключении реальной библиотеки. Кроме того, использование файла '''perlobject.map''' <font color=darkblue>[3]</font> позволит не писать заново описание объектов. | Чтобы не умножать сущности без надобности, т.е. не писать новых классов, которые потом нигде не пригодятся, воспользуемся уже готовой библиотекой ''QtCore'' из состава ''Qt4''. Для удобства я буду приводить части заголовочных файлов этой библиотеки, но все примеры будут работоспособны при подключении реальной библиотеки. Кроме того, использование файла '''perlobject.map''' <font color=darkblue>[3]</font> позволит не писать заново описание объектов. | ||
Строка 14: | Строка 14: | ||
use 5.008; | use 5.008; | ||
use ExtUtils::MakeMaker; | use ExtUtils::MakeMaker; | ||
− | $CC = | + | $CC = 'g++'; |
WriteMakefile( | WriteMakefile( | ||
− | NAME => | + | NAME => 'QtCore', |
− | VERSION_FROM => | + | VERSION_FROM => 'lib/QtCore.pm', |
PREREQ_PM => {}, # e.g., Module::Name => 1.1 | PREREQ_PM => {}, # e.g., Module::Name => 1.1 | ||
($] >= 5.005 ? | ($] >= 5.005 ? | ||
− | (ABSTRACT_FROM => | + | (ABSTRACT_FROM => 'lib/QtCore.pm', |
− | AUTHOR => | + | AUTHOR => 'A. U. Thor <author@localdomain>') : ()), |
− | LIBS => [ | + | LIBS => [''], |
− | DEFINE => | + | DEFINE => '', |
CC => $CC, | CC => $CC, | ||
− | LD => | + | LD => '$(CC)', |
− | INC => | + | INC => '', |
− | # OBJECT => | + | # OBJECT => '$(O_FILES)', |
− | XSOPT => | + | XSOPT => '-C++', |
− | TYPEMAPS => [ | + | TYPEMAPS => ['perlobject.map'], |
); | ); | ||
</source> | </source> | ||
Строка 37: | Строка 37: | ||
<source lang="c"> | <source lang="c"> | ||
#ifdef __cplusplus | #ifdef __cplusplus | ||
− | extern | + | extern "C" { |
#endif | #endif | ||
− | #include | + | #include "EXTERN.h" |
− | #include | + | #include "perl.h" |
− | #include | + | #include "XSUB.h" |
#ifdef __cplusplus | #ifdef __cplusplus | ||
} | } | ||
Строка 68: | Строка 68: | ||
нее хранятся в массиве @aa, то ее необходимо вызывать как | нее хранятся в массиве @aa, то ее необходимо вызывать как | ||
my_func($aa[0], $aa[1]). | my_func($aa[0], $aa[1]). | ||
− | Тогда как при указании | + | Тогда как при указании "PROTOTYPES: DISABLE" можно эту функцию |
вызвать как my_func(@aa). | вызвать как my_func(@aa). | ||
=cut | =cut | ||
PROTOTYPES: ENABLE | PROTOTYPES: ENABLE | ||
=comment | =comment | ||
− | XS распознает только один конструктор -- | + | XS распознает только один конструктор -- "new". Если их будет |
больше, то каждый нуждается в подробном описании. | больше, то каждый нуждается в подробном описании. | ||
=cut | =cut | ||
Строка 96: | Строка 96: | ||
QtCore::DESTROY() | QtCore::DESTROY() | ||
</source> | </source> | ||
− | Кроме того, для вызова класса следует указать | + | Кроме того, для вызова класса следует указать Perl'у, чем является класс <font color=darkred>QtCore</font>, т.е. как работать с этим типом данных, для чего создадим файл '''typemap''' со следующим содержимым: |
<source lang="perl"> | <source lang="perl"> | ||
TYPEMAP | TYPEMAP | ||
Строка 109: | Строка 109: | ||
require DynaLoader; | require DynaLoader; | ||
our @ISA = qw(DynaLoader); | our @ISA = qw(DynaLoader); | ||
− | our $VERSION = | + | our $VERSION = '0.01'; |
bootstrap QtCore $VERSION; | bootstrap QtCore $VERSION; | ||
1; | 1; | ||
Строка 115: | Строка 115: | ||
Чтобы собрать полученный модуль, выполните команды '''perl Makefile.PL && make'''. | Чтобы собрать полученный модуль, выполните команды '''perl Makefile.PL && make'''. | ||
− | Все сделанное необходимо протестировать. В модуле уже есть каталог '''t/''' для тестовых скриптов, которые, однако, расчитаны только на то, чтоб по команде '''make test''' вывести | + | Все сделанное необходимо протестировать. В модуле уже есть каталог '''t/''' для тестовых скриптов, которые, однако, расчитаны только на то, чтоб по команде '''make test''' вывести "'''имя_скрипта.....ok'''". Этого явно недостаточно, чтобы подробно просмотреть работоспособность написанного модуля. Поэтому создадим каталог '''test/''' со скриптом '''qtcore.pl''' и следующим содержимым: |
<source lang="perl"> | <source lang="perl"> | ||
#!/usr/bin/perl -w | #!/usr/bin/perl -w | ||
Строка 122: | Строка 122: | ||
my $q = new QtCore; | my $q = new QtCore; | ||
$q->setVer(4.001); | $q->setVer(4.001); | ||
− | print $q->ver(), | + | print $q->ver(), "\n"; |
</source> | </source> | ||
Строка 134: | Строка 134: | ||
</source> | </source> | ||
− | Однако главным недостатком такого подхода является необходимость подключения всех заголовочных файлов в одном месте, содержимое которых будет находиться в одной области видимости. Третий вариант, особенно удобный для такой большой библиотеки, как ''QtCore'', заключается в том, чтобы сделать каждый '''xs'''-файл относительно независимым и в каждом из них подключать только заголовочный файл, описывающий нужный класс. Это обычно делается двумя способами. Первый состоит в том, чтобы в главном '''xs'''-файле прописать импорт boot-функций всех файлов и выполнять их в boot-функции основного '''xs'''-файла, вызываемого функцией <font color=darkred>bootstrap</font>. Примеры реализации данного способа можно увидеть в библиотеках ''perl-Glib/Gtk2'', ''Perl-RPM'' (в каждой немного по-своему). Другой способ заключается в том, чтобы все вызовы сделать из главного модуля, но уже на | + | Однако главным недостатком такого подхода является необходимость подключения всех заголовочных файлов в одном месте, содержимое которых будет находиться в одной области видимости. Третий вариант, особенно удобный для такой большой библиотеки, как ''QtCore'', заключается в том, чтобы сделать каждый '''xs'''-файл относительно независимым и в каждом из них подключать только заголовочный файл, описывающий нужный класс. Это обычно делается двумя способами. Первый состоит в том, чтобы в главном '''xs'''-файле прописать импорт boot-функций всех файлов и выполнять их в boot-функции основного '''xs'''-файла, вызываемого функцией <font color=darkred>bootstrap</font>. Примеры реализации данного способа можно увидеть в библиотеках ''perl-Glib/Gtk2'', ''Perl-RPM'' (в каждой немного по-своему). Другой способ заключается в том, чтобы все вызовы сделать из главного модуля, но уже на Perl'e. Данный вариант реализован в <font color=darkred>Win32::Gui</font>. На мой взгляд, он более удобен и обладает большей переносимостью. |
Опишем последний вариант подробнее. Прежде всего следует удалить оставшиеся файлы предыдущей сборки, а именно: каталог '''blib''' и файлы '''Makefile''', '''pm_to_blib''', '''QtCore.bs''', '''*.c''', '''*.o'''. | Опишем последний вариант подробнее. Прежде всего следует удалить оставшиеся файлы предыдущей сборки, а именно: каталог '''blib''' и файлы '''Makefile''', '''pm_to_blib''', '''QtCore.bs''', '''*.c''', '''*.o'''. | ||
Строка 140: | Строка 140: | ||
Далее настроим обработку нескольких xs-файлов, для чего в '''Makefile.PL''' раскомментируем строку | Далее настроим обработку нескольких xs-файлов, для чего в '''Makefile.PL''' раскомментируем строку | ||
<source lang="perl"> | <source lang="perl"> | ||
− | OBJECT => | + | OBJECT => '$(O_FILES)'. |
</source> | </source> | ||
Вследствие этого будут отрабатываться все '''xs'''-файлы, найденные в каталоге модуля (во вложенных каталогах поиск не ведется). Подключим библиотеку '''QtCore.so''', для чего в строку | Вследствие этого будут отрабатываться все '''xs'''-файлы, найденные в каталоге модуля (во вложенных каталогах поиск не ведется). Подключим библиотеку '''QtCore.so''', для чего в строку | ||
<source lang="perl"> | <source lang="perl"> | ||
− | LIBS => [ | + | LIBS => [''], |
</source> | </source> | ||
пропишем ее: | пропишем ее: | ||
<source lang="perl"> | <source lang="perl"> | ||
− | LIBS => [ | + | LIBS => ['-L/usr/lib -lQtCore '], |
</source> | </source> | ||
Для примера импортирования нескольких классов выберем небольшой класс <font color=darkred>QSize</font> (если у вас не установлен ''Qt4'', файл '''qsize.h''' можно найти на диске). | Для примера импортирования нескольких классов выберем небольшой класс <font color=darkred>QSize</font> (если у вас не установлен ''Qt4'', файл '''qsize.h''' можно найти на диске). | ||
Строка 155: | Строка 155: | ||
<source lang="c"> | <source lang="c"> | ||
#ifdef __cplusplus | #ifdef __cplusplus | ||
− | extern | + | extern "C" { |
#endif | #endif | ||
− | #include | + | #include "EXTERN.h" |
− | #include | + | #include "perl.h" |
− | #include | + | #include "XSUB.h" |
#ifdef __cplusplus | #ifdef __cplusplus | ||
} | } | ||
Строка 193: | Строка 193: | ||
use warnings; | use warnings; | ||
use QtCore; # необходимо для вызыва bootstarp, находящегося в файле QtCore | use QtCore; # необходимо для вызыва bootstarp, находящегося в файле QtCore | ||
− | QtCore::bootstrap_subpackage | + | QtCore::bootstrap_subpackage 'QSize'; |
1; | 1; | ||
</source> | </source> | ||
Строка 200: | Строка 200: | ||
sub bootstrap_subpackage { | sub bootstrap_subpackage { | ||
my($package) = @_; | my($package) = @_; | ||
− | $package = | + | $package = 'QtCore::'.$package; |
my $symbol = $package; | my $symbol = $package; | ||
$symbol =~ s/\W/_/g; | $symbol =~ s/\W/_/g; | ||
− | no strict | + | no strict 'refs'; |
DynaLoader::dl_install_xsub( | DynaLoader::dl_install_xsub( | ||
− | + | "${package}::bootstrap", | |
− | DynaLoader::dl_find_symbol_anywhere( | + | DynaLoader::dl_find_symbol_anywhere("boot_$symbol") |
); | ); | ||
− | &{ | + | &{ "${package}::bootstrap" }; |
} | } | ||
</source> | </source> | ||
Строка 220: | Строка 220: | ||
use blib; | use blib; | ||
use QtCore::QSize; | use QtCore::QSize; | ||
− | use Carp | + | use Carp 'croak'; |
my $q = new QtCore::QSize; # создать класс | my $q = new QtCore::QSize; # создать класс | ||
− | print | + | print "q is empty\n" if $q->isEmpty(); |
$q->setWidth(2); # присвоить параметр | $q->setWidth(2); # присвоить параметр | ||
− | print $q->width(), | + | print $q->width(), "\n"; # проверить |
$q->setHeight(3); | $q->setHeight(3); | ||
− | print | + | print "q isn't empty\n" unless $q->isEmpty(); |
</source> | </source> | ||
Строка 261: | Строка 261: | ||
RETVAL | RETVAL | ||
</source> | </source> | ||
− | Заметим, что в | + | Заметим, что в Perl'е удобнее было бы использовать идентификатор <font color=darkred>new</font> для вызова любого конструктора, не различая их по номерам. |
Для реализации этой идеи удалим из '''QSize.xs''' вызов <font color=darkred>QSize::new()</font>, после чего добавим в '''lib/QtCore/QSize.pm''' функцию с таким же названием. В зависимости от содержимого, она сама будет выбирать, что ей вызвать. При неверном количестве параметров функция выведет сообщение об ошибке: | Для реализации этой идеи удалим из '''QSize.xs''' вызов <font color=darkred>QSize::new()</font>, после чего добавим в '''lib/QtCore/QSize.pm''' функцию с таким же названием. В зависимости от содержимого, она сама будет выбирать, что ей вызвать. При неверном количестве параметров функция выведет сообщение об ошибке: | ||
<source lang="perl"> | <source lang="perl"> | ||
Строка 267: | Строка 267: | ||
return new1($_[0]) if ( scalar(@_) == 1 ); | return new1($_[0]) if ( scalar(@_) == 1 ); | ||
return new2($_[0], $_[1], $_[2]) if ( scalar(@_) == 3 ); | return new2($_[0], $_[1], $_[2]) if ( scalar(@_) == 3 ); | ||
− | croak( | + | croak("ожидалось 0 или 2 параметра\n"); |
} | } | ||
</source> | </source> | ||
Строка 283: | Строка 283: | ||
QSize &operator+=(const QSize &); | QSize &operator+=(const QSize &); | ||
</source> | </source> | ||
− | Оператор возвращает тот же класс, к которому осуществляется прибавление, поэтому возвратить <font color=darkred>QSize</font> можно и в функции на | + | Оператор возвращает тот же класс, к которому осуществляется прибавление, поэтому возвратить <font color=darkred>QSize</font> можно и в функции на Perl'e. Поскольку Perl по своей сути работает только с указателями, то перед передачей функции прибавляемого класса <font color=darkred>otherSize</font> его (указатель) следует разыменовать: |
<source lang="c"> | <source lang="c"> | ||
void | void | ||
Строка 301: | Строка 301: | ||
QSize * qsize2 | QSize * qsize2 | ||
PREINIT: | PREINIT: | ||
− | char * CLASS = | + | char * CLASS = "QtCore::QSize"; |
CODE: | CODE: | ||
RETVAL = new QSize(); | RETVAL = new QSize(); | ||
Строка 311: | Строка 311: | ||
<source lang="perl"> | <source lang="perl"> | ||
use overload | use overload | ||
− | + | '+' => \&_plus, | |
− | + | '+=' => \&_plus_eq, | |
− | + | '""' => sub { $_[0] }; | |
sub _plus_eq { | sub _plus_eq { | ||
unless ( ref($_[1]) ) { | unless ( ref($_[1]) ) { | ||
− | croak( | + | croak("need QSize += QSize\n"); |
return; | return; | ||
} | } | ||
Строка 327: | Строка 327: | ||
return operator_plus($_[0], $_[1]); | return operator_plus($_[0], $_[1]); | ||
} | } | ||
− | croak( | + | croak("Need QSize1 = QSize2 + QSize3\n"); |
} | } | ||
</source> | </source> | ||
Строка 333: | Строка 333: | ||
<source lang="perl"> | <source lang="perl"> | ||
$w += $q; | $w += $q; | ||
− | print | + | print "w (h, w) == ", $w->height(), " ", $w->width(), "\n"; |
my $e = $w + $q; | my $e = $w + $q; | ||
− | print | + | print "e (h, w) == ", $e->height(), " ", $e->width(), "\n"; |
</source> | </source> | ||
И, запустив, убедитесь, что это работает. | И, запустив, убедитесь, что это работает. | ||
Строка 376: | Строка 376: | ||
<source lang="perl"> | <source lang="perl"> | ||
$e->scale(20, 20, Qt::IgnoreAspectRatio); | $e->scale(20, 20, Qt::IgnoreAspectRatio); | ||
− | print | + | print "scale e (h, w) == ", $e->height(), " ", $e->width(), "\n"; |
</source> | </source> | ||
===Взаимодействие с STL или ее аналогами=== | ===Взаимодействие с STL или ее аналогами=== | ||
− | В | + | В Perl'e STL практически не нужна, поскольку большинство возможностей STL уже поддерживаются массивами и хэшами самого языка. Поэтому рассмотрим только передачу данных из шаблона <font color=darkred>list</font> в массив Perl'a и обратно. Библиотека ''Qt4'' инкапсулирует в себе STL, добавляя некоторые возможности. Мы подробно рассмотрим работу с шаблоном <font color=darkred>QList</font>, ибо методы некоторых классов возвращают списки классов, используя именно его. Для получения массива обратимся к классу <font color=darkred>QbyteArray</font>. В нем есть такой конструктор: |
<source lang="c"> | <source lang="c"> | ||
QList<QByteArray> split(char sep) const; | QList<QByteArray> split(char sep) const; | ||
</source> | </source> | ||
− | В файле '''QByteArray.xs''' перед использованием шаблонов STL необходимо убрать определения <font color=darkred>do_open</font> и <font color=darkred>do_close</font>, иначе они начнут конфликтовать с аналогичными из | + | В файле '''QByteArray.xs''' перед использованием шаблонов STL необходимо убрать определения <font color=darkred>do_open</font> и <font color=darkred>do_close</font>, иначе они начнут конфликтовать с аналогичными из Perl'a. |
<source lang="c"> | <source lang="c"> | ||
... | ... | ||
Строка 403: | Строка 403: | ||
*ba = lba.at(i); | *ba = lba.at(i); | ||
SV * rv = newSV(0); | SV * rv = newSV(0); | ||
− | sv_setref_pv( rv, | + | sv_setref_pv( rv, "QtCore::QByteArray", (void *)ba ); |
av_push(RETVAL, rv); | av_push(RETVAL, rv); | ||
}; | }; | ||
Строка 412: | Строка 412: | ||
... | ... | ||
</source> | </source> | ||
− | Иными словами, в описании <font color=darkred>_split</font> создается анонимный массив, указатель на который будет передан в программу. Затем вызывается функция <font color=darkred>split</font> класса на C++, которая возвращает список объектов <font color=darkred>QByteArray</font>. Этот список обходится в цикле, в котором по одному указателю на объект заносится в массив <font color=darkred>RETVAL</font>. Поскольку массив принимает только тип данных <font color=darkred>SV*</font>, то на каждой итерации цикла создается новая переменная. Затем в нее копируется ссылка на объект из списка, приведенная к типу данных Perl функцией <font color=darkred>sv_setref_pv</font>. Подробно работа с массивами в Perl XS описана в <font color=darkred>perlguts</font>, а примеры использования массива со строками можно посмотреть в | + | Иными словами, в описании <font color=darkred>_split</font> создается анонимный массив, указатель на который будет передан в программу. Затем вызывается функция <font color=darkred>split</font> класса на C++, которая возвращает список объектов <font color=darkred>QByteArray</font>. Этот список обходится в цикле, в котором по одному указателю на объект заносится в массив <font color=darkred>RETVAL</font>. Поскольку массив принимает только тип данных <font color=darkred>SV*</font>, то на каждой итерации цикла создается новая переменная. Затем в нее копируется ссылка на объект из списка, приведенная к типу данных Perl функцией <font color=darkred>sv_setref_pv</font>. Подробно работа с массивами в Perl XS описана в <font color=darkred>perlguts</font>, а примеры использования массива со строками можно посмотреть в "''XS Cookbook''" <font color=darkblue>[2]</font>. |
Следующий шаг состоит в добавлении в '''typemap''' нового класса | Следующий шаг состоит в добавлении в '''typemap''' нового класса | ||
<source lang="c"> | <source lang="c"> | ||
QByteArray * O_OBJECT | QByteArray * O_OBJECT | ||
− | </ | + | </source> |
− | и создании для этого класса модуля '''lib/QtCore/QByteArray.pm'''. Функция <font color=darkred>_split</font> возвращает указатель на массив, однако в программе удобнее пользоваться обычным массивом. С этой целью напишем простейшую оболочку для этой функции. Кроме того, в | + | и создании для этого класса модуля '''lib/QtCore/QByteArray.pm'''. Функция <font color=darkred>_split</font> возвращает указатель на массив, однако в программе удобнее пользоваться обычным массивом. С этой целью напишем простейшую оболочку для этой функции. Кроме того, в Perl'e есть своя функция <font color=darkred>split</font>, поэтому ее надо переопределить в пакете, используя <font color=darkred>use subs</font>. |
<source lang="perl"> | <source lang="perl"> | ||
package QtCore::QByteArray; | package QtCore::QByteArray; | ||
Строка 426: | Строка 426: | ||
use Carp qw/carp croak/; | use Carp qw/carp croak/; | ||
use QtCore; # bootstraps QtCore.xs | use QtCore; # bootstraps QtCore.xs | ||
− | QtCore::bootstrap_subpackage | + | QtCore::bootstrap_subpackage 'QByteArray'; |
use subs qw(split); | use subs qw(split); | ||
sub split { | sub split { | ||
− | croak( | + | croak("split: нет разделителя\n") unless $_[1]; |
return @{ _split($_[0], $_[1]) }; | return @{ _split($_[0], $_[1]) }; | ||
} | } | ||
Строка 464: | Строка 464: | ||
string STRING | string STRING | ||
</source> | </source> | ||
− | Ниже в разделах <font color=darkred>INPUT</font> и <font color=darkred>OUTPUT</font> необходимо описать, как перевести <font color=darkred>string</font> из внутреннего типа данных | + | Ниже в разделах <font color=darkred>INPUT</font> и <font color=darkred>OUTPUT</font> необходимо описать, как перевести <font color=darkred>string</font> из внутреннего типа данных Perl'а (переменная <font color=darkred>$arg</font>) в C++ (переменная <font color=darkred>$var</font>) и обратно. |
<source lang="perl"> | <source lang="perl"> | ||
INPUT | INPUT | ||
Строка 477: | Строка 477: | ||
sv_setpvn((SV*)$arg, (char *) ($var.data()), ($var.size())); | sv_setpvn((SV*)$arg, (char *) ($var.data()), ($var.size())); | ||
</source> | </source> | ||
− | Таким образом, в данной статье были рассмотрены все основные варианты использования C++ и Perl XS. За ее пределами остались только прямое использование шаблонных классов (но, как было указано выше, использовать их нецелесообразно, т. к. STL покрывается возможностями самого | + | Таким образом, в данной статье были рассмотрены все основные варианты использования C++ и Perl XS. За ее пределами остались только прямое использование шаблонных классов (но, как было указано выше, использовать их нецелесообразно, т. к. STL покрывается возможностями самого Perl'a) и использование lvalue-функций из классов С++ в Perl'e (когда разрабатывался Perl XS для 5-й версии, lvalue изначально не был реализован и в самом Perl5, а более поздних описаний расширений Perl XS на данный момент, по моим сведениям, не существует). |
==Литература== | ==Литература== | ||
#Документация Perl (perlxs, perlxstut, perlguts). | #Документация Perl (perlxs, perlxstut, perlguts). | ||
− | # | + | #Dean's Extension-Building Cookbook in two parts. Part A: http://www.cpan.org/authors/Dean_Roehrich/CookBookA-19960430.tar.gz. |
− | # | + | #Dean's Extension-Building Cookbook in two parts. Part B: http://www.cpan.org/authors/Dean_Roehrich/CookBookB-19960430.tar.gz. |
#http://www.cpan.org/authors/Dean_Roehrich/perlobject.map-19960302.gz. | #http://www.cpan.org/authors/Dean_Roehrich/perlobject.map-19960302.gz. | ||
#John Keiser. Gluing C++ And Perl Together. – 2001. – http://www.johnkeiser.com/perl-xs-c++.html. | #John Keiser. Gluing C++ And Perl Together. – 2001. – http://www.johnkeiser.com/perl-xs-c++.html. | ||
#Уолл Л., Кристиансен Т., Орвант Д. Программирование на Perl. – СПб.: Символ-плюс, 2005. – 1152 с. | #Уолл Л., Кристиансен Т., Орвант Д. Программирование на Perl. – СПб.: Символ-плюс, 2005. – 1152 с. | ||
#http://search.cpan.org/~nwclark/perl-5.8.8/ext/XS/Typemap/Typemap.xs. | #http://search.cpan.org/~nwclark/perl-5.8.8/ext/XS/Typemap/Typemap.xs. |
Текущая версия на 15:30, 7 января 2009
|
|
|
[править] Как работать с классами С++ из Perl
Огорчены, что PerlQt застрял на версии 3.008?Не беспокойтесь – Вадим Лихота расскажет, как решить подобную задачу своими силами. Если, конечно, хватит терпения.
Описаний того, как импортировать в Perl функции из С, достаточно много, а вот информацию об использовании классов C++ я встречал в виде кратких описаний только в "XS Cookbook" [1, 2] и небольшой статье [4]. Пример использования класса С++ в Perl'е из "XS Cookbook" в сокращенном варианте перекочевал в perlxstut. Кроме того, на CPAN можно найти модули, импортирующие классы С++ и имеющие файлы импорта, которые можно использовать в качестве примера, такие, как Boost-Graph, Lucene, Search-Xapian,Однако они не покрывают многих вариантов подключения классов.
Чтобы не умножать сущности без надобности, т.е. не писать новых классов, которые потом нигде не пригодятся, воспользуемся уже готовой библиотекой QtCore из состава Qt4. Для удобства я буду приводить части заголовочных файлов этой библиотеки, но все примеры будут работоспособны при подключении реальной библиотеки. Кроме того, использование файла perlobject.map [3] позволит не писать заново описание объектов.
[править] Начальные данные для любого модуля
Начальные данные для любого модуля можно найти в уже упомянутой статье [4], однако они столь ценны и необходимы для раскрытия темы, что заслуживают отдельного рассмотрения. Скелет любого модуля можно написать вручную, но легче и быстрее это делается командой h2xs -An имя_модуля. В результате будет создан каталог для модуля с необходимыми файлами, содержимое которых детально описано в «Программировании на Perl» [5]. Дав команду h2xs -An QtCore, вы получите скелет модуля. В созданный каталог QtCore необходимо скопировать perobject.map (названия всех файлов приводятся относительно каталога QtCore). Созданный файл Makefile.PL надо привести к следующему виду:
use 5.008; use ExtUtils::MakeMaker; $CC = 'g++'; WriteMakefile( NAME => 'QtCore', VERSION_FROM => 'lib/QtCore.pm', PREREQ_PM => {}, # e.g., Module::Name => 1.1 ($] >= 5.005 ? (ABSTRACT_FROM => 'lib/QtCore.pm', AUTHOR => 'A. U. Thor <author@localdomain>') : ()), LIBS => [''], DEFINE => '', CC => $CC, LD => '$(CC)', INC => '', # OBJECT => '$(O_FILES)', XSOPT => '-C++', TYPEMAPS => ['perlobject.map'], );
Выделенные жирным строки необходимо добавить именно для того, чтобы Perl заработал с С++.
Кроме того, важно исправить файл QtCore.xs, который будет содержать импортируемые в Perl функции:
#ifdef __cplusplus extern "C" { #endif #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #ifdef __cplusplus } #endif
Для наглядного примера создадим в этом файле класс, который будет хранить, допустим, версию программы. Для этого добавим класс после подключенных заголовочных файлов перед строкой MODULE = QtCore PACKAGE = QtCore:
class QtCore { public: QtCore(){ vers = 0.001; }; ~QtCore(){}; double ver(){ return vers; }; void setVer(double v){ vers = v; }; private: double vers; };
Работа с обычными функциями, конструктором и деструктором уже предусмотрена в Perl XS, поэтому после объявления модуля и пакета можно использовать краткие объявления функций (также возможны комментарии в perl-стиле):
MODULE = QtCore PACKAGE = QtCore =comment явное указание использовать прототипы функций позволяет избежать некоторых ошибок при передаче параметров в функции, но в тоже время не дает упростить использование этих функций. Например, если функция получает два параметра, а ваши данные для нее хранятся в массиве @aa, то ее необходимо вызывать как my_func($aa[0], $aa[1]). Тогда как при указании "PROTOTYPES: DISABLE" можно эту функцию вызвать как my_func(@aa). =cut PROTOTYPES: ENABLE =comment XS распознает только один конструктор -- "new". Если их будет больше, то каждый нуждается в подробном описании. =cut QtCore * QtCore::new() =comment методы класса =cut double QtCore::ver() void QtCore::setVer(v) double v =comment В подавляющем большинстве случаев такого вызова деструктора хватает. Однако если вы хотите явно освободить память, уничтожить зависимые объекты и т.п., то пример вызова деструктора найдете в XS Cookbook [2, ArrayOfStruct]. =cut void QtCore::DESTROY()
Кроме того, для вызова класса следует указать Perl'у, чем является класс QtCore, т.е. как работать с этим типом данных, для чего создадим файл typemap со следующим содержимым:
TYPEMAP
QtCore * O_OBJECT
Описания встроенных типов данных представлены в typemap.xs [6], а описание O_OBJECT находится в файле perlobject.map. Если не добавлять этот файл, то придется самостоятельно полностью описывать все дополнительные типы данных в файле typemap (пример подобного описания приводится ниже). После этого остается внести изменения в файл lib/QtCore.pm, который и будет подключаться в конечных скриптах. Поскольку QtCore.pm будет объектом, и ничего экспортироваться из него не будет, то следует убрать из этого файла все относящееся к модулю Exporter. Для импорта внешних функций можно использовать как XSLoader, так и более старый DynaLoader (я использовал второй, т.к. к нему чаще обращаются).
package QtCore; use 5.008; use strict; use warnings; require DynaLoader; our @ISA = qw(DynaLoader); our $VERSION = '0.01'; bootstrap QtCore $VERSION; 1;
Чтобы собрать полученный модуль, выполните команды perl Makefile.PL && make.
Все сделанное необходимо протестировать. В модуле уже есть каталог t/ для тестовых скриптов, которые, однако, расчитаны только на то, чтоб по команде make test вывести "имя_скрипта.....ok". Этого явно недостаточно, чтобы подробно просмотреть работоспособность написанного модуля. Поэтому создадим каталог test/ со скриптом qtcore.pl и следующим содержимым:
#!/usr/bin/perl -w use blib; use QtCore; my $q = new QtCore; $q->setVer(4.001); print $q->ver(), "\n";
В результате запуска скрипта должна появиться указанная нами версия 4.001.
[править] Импортирование нескольких классов
Едва ли можно найти библиотеку, состоящую из одного класса. Когда классов немного, их можно описать в одном xs-файле, или последовать примеру модуля Search-Xapian, в котором один большой файл разбит на несколько, объединяемых командой
INCLUDE: подключаемый_файл.xs
Однако главным недостатком такого подхода является необходимость подключения всех заголовочных файлов в одном месте, содержимое которых будет находиться в одной области видимости. Третий вариант, особенно удобный для такой большой библиотеки, как QtCore, заключается в том, чтобы сделать каждый xs-файл относительно независимым и в каждом из них подключать только заголовочный файл, описывающий нужный класс. Это обычно делается двумя способами. Первый состоит в том, чтобы в главном xs-файле прописать импорт boot-функций всех файлов и выполнять их в boot-функции основного xs-файла, вызываемого функцией bootstrap. Примеры реализации данного способа можно увидеть в библиотеках perl-Glib/Gtk2, Perl-RPM (в каждой немного по-своему). Другой способ заключается в том, чтобы все вызовы сделать из главного модуля, но уже на Perl'e. Данный вариант реализован в Win32::Gui. На мой взгляд, он более удобен и обладает большей переносимостью.
Опишем последний вариант подробнее. Прежде всего следует удалить оставшиеся файлы предыдущей сборки, а именно: каталог blib и файлы Makefile, pm_to_blib, QtCore.bs, *.c, *.o.
Далее настроим обработку нескольких xs-файлов, для чего в Makefile.PL раскомментируем строку
OBJECT => '$(O_FILES)'.
Вследствие этого будут отрабатываться все xs-файлы, найденные в каталоге модуля (во вложенных каталогах поиск не ведется). Подключим библиотеку QtCore.so, для чего в строку
LIBS => [''],
пропишем ее:
LIBS => ['-L/usr/lib -lQtCore '],
Для примера импортирования нескольких классов выберем небольшой класс QSize (если у вас не установлен Qt4, файл qsize.h можно найти на диске).
Создадим файл QSize.xs:
#ifdef __cplusplus extern "C" { #endif #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #ifdef __cplusplus } #endif #include <QtCore/qsize.h> MODULE = QtCore::QSize PACKAGE = QtCore::QSize =comment QSize входит в состав QtCore =cut PROTOTYPES: ENABLE QSize * QSize::new() bool QSize::isEmpty() int QSize::width() int QSize::height() void QSize::setWidth(w) int w void QSize::setHeight(h) int h void QSize::DESTROY()
Далее создадим для класса QSize собственный pm-файл lib/QtCore/QSize.pm.
package QtCore::QSize; use 5.008; use strict; use warnings; use QtCore; # необходимо для вызыва bootstarp, находящегося в файле QtCore QtCore::bootstrap_subpackage 'QSize'; 1;
В дальнейшем файлы QtCore.xs и lib/QtCore.pm будут нужны только для вызова bootstrap модуля QtCore.pm. Заметим, что класс в QtCore.xs можно удалить, но тогда придется добавить хотя бы одну внешнюю функцию, иначе в файле QtCore.c, который создается на основе QtCore.xs, не будет всех нужных объявлений. Вообще все boot-функции и объявления в них можно прописать и вручную, но вряд ли это целесообразно, если компилятор XS делает все сам. Теперь следует добавить в lib/QtCore.pm функцию, которая будет выполнять роль bootstrap для остальных модулей:
sub bootstrap_subpackage { my($package) = @_; $package = 'QtCore::'.$package; my $symbol = $package; $symbol =~ s/\W/_/g; no strict 'refs'; DynaLoader::dl_install_xsub( "${package}::bootstrap", DynaLoader::dl_find_symbol_anywhere("boot_$symbol") ); &{ "${package}::bootstrap" }; }
И последний шаг в нашем примере импортирования нескольких классов. Класс следует описать в файле typemap, добавив в конце
QSize * O_OBJECT
Вот теперь уже можно запустить perl Makefile.PL && make и потестировать то, что получилось. Для проверки можно создать файл test/qsize.pl:
#!/usr/bin/perl -w use blib; use QtCore::QSize; use Carp 'croak'; my $q = new QtCore::QSize; # создать класс print "q is empty\n" if $q->isEmpty(); $q->setWidth(2); # присвоить параметр print $q->width(), "\n"; # проверить $q->setHeight(3); print "q isn't empty\n" unless $q->isEmpty();
[править] Использование нескольких конструкторов
Класс QSize содержит два конструктора, а компилятор XS знает только про new. Поэтому второй конструктор мы реализуем сами. Чтобы увидеть, что для этого надо, достаточно посмотреть в файл QSize.c, автоматически сгенерированный компилятором XS из файла QSize.xs:
char * CLASS = (char *)SvPV_nolen(ST(0)); QSize * RETVAL; RETVAL = new QSize(); ST(0) = sv_newmortal(); sv_setref_pv( ST(0), CLASS, (void*)RETVAL );
Иными словами, благодаря QSize::, расположенному перед конструктором new, в функцию передается строковый параметр CLASS с названием класса, после чего создается объект и используется bless для полученной ссылки. Для примера импорта конструктора в QSize.xs создадим конструктор new1 с явным указанием компилятору на код и возвращаемый параметр:
QSize * new1(CLASS) char * CLASS CODE: RETVAL = new QSize(); OUTPUT: RETVAL
Теперь запустим make. Получный в QSize.c код для new1 будет идентичен автоматически созданному коду для конструктора new. Однако появятся две пометки о том, что код взят из QSize.xs. Аналогично создадим второй конструктор, но уже с параметрами инициализации:
QSize * new2(CLASS, w, h); int w int h char * CLASS CODE: RETVAL = new QSize(w, h); OUTPUT: RETVAL
Заметим, что в Perl'е удобнее было бы использовать идентификатор new для вызова любого конструктора, не различая их по номерам. Для реализации этой идеи удалим из QSize.xs вызов QSize::new(), после чего добавим в lib/QtCore/QSize.pm функцию с таким же названием. В зависимости от содержимого, она сама будет выбирать, что ей вызвать. При неверном количестве параметров функция выведет сообщение об ошибке:
sub new { return new1($_[0]) if ( scalar(@_) == 1 ); return new2($_[0], $_[1], $_[2]) if ( scalar(@_) == 3 ); croak("ожидалось 0 или 2 параметра\n"); }
Далее дайте команду make и проверьте, как все работает, для чего добавьте в test/qsize.pl строку
my $w = QtCore::QSize->new(5,6);
[править] Сложение классов ( operator+ )
Если в исходном классе, написанном на С++, содержатся операторы «арифметических» и «логических» действий c классами, то данные функции желательно импортировать в Perl.
Сначала рассмотрим, что добавить в QSize.xs для
QSize &operator+=(const QSize &);
Оператор возвращает тот же класс, к которому осуществляется прибавление, поэтому возвратить QSize можно и в функции на Perl'e. Поскольку Perl по своей сути работает только с указателями, то перед передачей функции прибавляемого класса otherSize его (указатель) следует разыменовать:
void QSize::operator_plus_eq(otherSize) QSize * otherSize CODE: THIS->operator+= (*otherSize);
Или, например, другой оператор:
friend inline const QSize operator+(const QSize &, const QSize &);
Несмотря на то, что фунция operator+ не является внутренней для QSize, это не мешает получить указатель на первый класс указанным выше способом. В то же время operator+ возвращает новый объект QSize, который будет жить только в пределах С-функции. Нам же необходимо вернуть указатель на новый объект QSize. Поэтому создадим новый экземпляр класса QSize и присвоим ему результат. Класс QSize простой, поэтому конструктор копий создается компилятором автоматически.
QSize * QSize::operator_plus(qsize2) QSize * qsize2 PREINIT: char * CLASS = "QtCore::QSize"; CODE: RETVAL = new QSize(); *RETVAL = (operator+ ( *THIS, *qsize2 )); OUTPUT: RETVAL
В файле lib/QtCore/QSize.pm следует сделать оболочку для данных функций, используя overload (подробности использования overload смотрите в perldoc или «Программировании на Perl» [5, стр. 397]):
use overload '+' => \&_plus, '+=' => \&_plus_eq, '""' => sub { $_[0] }; sub _plus_eq { unless ( ref($_[1]) ) { croak("need QSize += QSize\n"); return; } operator_plus_eq($_[0], $_[1]); return $_[0]; # возвращается указатель на тот же экземпляр класса } sub _plus { if ( ref($_[0]) and ref($_[1]) ) { return operator_plus($_[0], $_[1]); } croak("Need QSize1 = QSize2 + QSize3\n"); }
В заключение осталось проверить работоспособность операторов. Добавьте в test/qsize.pl такие строки:
$w += $q; print "w (h, w) == ", $w->height(), " ", $w->width(), "\n"; my $e = $w + $q; print "e (h, w) == ", $e->height(), " ", $e->width(), "\n";
И, запустив, убедитесь, что это работает.
[править] Особенности использования enum
Работа с enum предусмотрена в Perl XS, однако с C++ появляется одна неприятность. В время обработки xs-файла компилятором XS обращения в другие классы за определенными в них enum, как, например, Qt::AspectRatioMode, в с-файле Qt::AspectRatioMode превращается в Qt__AspectRatioMode, и выдается ошибка компилятора о несуществующем типе. К сожалению, нет никакой возможности избежать этого преобразования, ибо таким способом создаются все функции с целью не допустить дублирования названий функций с другими классами. Чтобы компилятор правильно увидел используемый нами enum, переопределим его в исходный облик. В C-части xs-файла после подключения qsize.h добавим:
#define Qt__AspectRatioMode Qt::AspectRatioMode
Теперь можно описать функцию с этим типом данных:
void QSize::scale(w, h, mode) int w int h Qt::AspectRatioMode mode CODE: THIS->scale(w, h, mode);
Не забудьте добавить в typemap новый тип данных:
Qt::AspectRatioMode T_ENUM
Чтобы не запоминать числовые значения всех enum-параметров, добавим модуль lib/Qt.pm со всеми значениями AspectRatioMode:
package Qt; # enum AspectRatioMode use constant IgnoreAspectRatio => 0; use constant KeepAspectRatio => 1; use constant KeepAspectRatioByExpanding => 2; 1;
После добавления или удаления любого файла, следует полностью очистить библиотеку, удалив каталог blib, файлы *.c, *.o и т.д. После данных манипуляций и выполнения команд perl Makefile.PL && make можно тестировать программу. Для этого после use blib в файле qsize.pl следует добавить
use Qt;
а также дописать новую функцию в конце этого файла:
$e->scale(20, 20, Qt::IgnoreAspectRatio); print "scale e (h, w) == ", $e->height(), " ", $e->width(), "\n";
[править] Взаимодействие с STL или ее аналогами
В Perl'e STL практически не нужна, поскольку большинство возможностей STL уже поддерживаются массивами и хэшами самого языка. Поэтому рассмотрим только передачу данных из шаблона list в массив Perl'a и обратно. Библиотека Qt4 инкапсулирует в себе STL, добавляя некоторые возможности. Мы подробно рассмотрим работу с шаблоном QList, ибо методы некоторых классов возвращают списки классов, используя именно его. Для получения массива обратимся к классу QbyteArray. В нем есть такой конструктор:
QList<QByteArray> split(char sep) const;
В файле QByteArray.xs перед использованием шаблонов STL необходимо убрать определения do_open и do_close, иначе они начнут конфликтовать с аналогичными из Perl'a.
... #undef do_open #undef do_close #include <QtCore/qlist.h> #include <QtCore/qbytearray.h> ... AV * QByteArray::_split(c) char c CODE: RETVAL = newAV(); QList<QByteArray> lba = THIS->split(c); for ( int i = 0 ; i < lba.size() ; ++i ) { QByteArray * ba = new QByteArray(); *ba = lba.at(i); SV * rv = newSV(0); sv_setref_pv( rv, "QtCore::QByteArray", (void *)ba ); av_push(RETVAL, rv); }; OUTPUT: RETVAL CLEANUP: SvREFCNT_dec( RETVAL ); ...
Иными словами, в описании _split создается анонимный массив, указатель на который будет передан в программу. Затем вызывается функция split класса на C++, которая возвращает список объектов QByteArray. Этот список обходится в цикле, в котором по одному указателю на объект заносится в массив RETVAL. Поскольку массив принимает только тип данных SV*, то на каждой итерации цикла создается новая переменная. Затем в нее копируется ссылка на объект из списка, приведенная к типу данных Perl функцией sv_setref_pv. Подробно работа с массивами в Perl XS описана в perlguts, а примеры использования массива со строками можно посмотреть в "XS Cookbook" [2].
Следующий шаг состоит в добавлении в typemap нового класса
QByteArray * O_OBJECT
и создании для этого класса модуля lib/QtCore/QByteArray.pm. Функция _split возвращает указатель на массив, однако в программе удобнее пользоваться обычным массивом. С этой целью напишем простейшую оболочку для этой функции. Кроме того, в Perl'e есть своя функция split, поэтому ее надо переопределить в пакете, используя use subs.
package QtCore::QByteArray; use 5.008; use strict; use warnings; use Carp qw/carp croak/; use QtCore; # bootstraps QtCore.xs QtCore::bootstrap_subpackage 'QByteArray'; use subs qw(split); sub split { croak("split: нет разделителя\n") unless $_[1]; return @{ _split($_[0], $_[1]) }; } 1;
Пересоберите пакет и протестируйте его (файл test/qbytearray.pl).
Аналогичным способом массив превращается в шаблон QList. Для примера приведем конструктор класса QStringList, получающий для инициализации массив объектов QString. В файле QtCore/qstrinlist.h конструктор объявлен как
inline QStringList(const QStringList &l) : QList<QString>(l) { }
В xs-файле для него необходимо создать класс QList<QString> и заполнить его объектами QString, полученными из массива. av является указателем на копию этого массива. Копия используется, поскольку функция av_pop() удаляет из массива считанные элементы.
QStringList * new3(CLASS, av) char * CLASS AV * av CODE: QList<QString> qls; while ( av_len(av) > -1 ) { SV * rv = av_pop(av); QString * str = (QString *)SvIV((SV*)SvRV( rv )); qls << *str; } RETVAL = new QStringList(qls); OUTPUT: RETVAL
[править] Описание типа данных, отсутствующего в typemap.xs и perlobject.map
В том случае, если нужно добавить новый тип данных, необходимо описать, как компилятору работать с ним, т.е. читать из него данные и записывать. Данная тема описана в документации Perl, но для полноты картины приведем пример, иллюстрирующий работу с отсутствующим в С типом string. Для этого типа в typemap следует добавить:
string STRING
Ниже в разделах INPUT и OUTPUT необходимо описать, как перевести string из внутреннего типа данных Perl'а (переменная $arg) в C++ (переменная $var) и обратно.
INPUT STRING { STRLEN len; const char * tmp = SvPV($arg, len); $var.assign(tmp, len); } OUTPUT STRING sv_setpvn((SV*)$arg, (char *) ($var.data()), ($var.size()));
Таким образом, в данной статье были рассмотрены все основные варианты использования C++ и Perl XS. За ее пределами остались только прямое использование шаблонных классов (но, как было указано выше, использовать их нецелесообразно, т. к. STL покрывается возможностями самого Perl'a) и использование lvalue-функций из классов С++ в Perl'e (когда разрабатывался Perl XS для 5-й версии, lvalue изначально не был реализован и в самом Perl5, а более поздних описаний расширений Perl XS на данный момент, по моим сведениям, не существует).
[править] Литература
- Документация Perl (perlxs, perlxstut, perlguts).
- Dean's Extension-Building Cookbook in two parts. Part A: http://www.cpan.org/authors/Dean_Roehrich/CookBookA-19960430.tar.gz.
- Dean's Extension-Building Cookbook in two parts. Part B: http://www.cpan.org/authors/Dean_Roehrich/CookBookB-19960430.tar.gz.
- http://www.cpan.org/authors/Dean_Roehrich/perlobject.map-19960302.gz.
- John Keiser. Gluing C++ And Perl Together. – 2001. – http://www.johnkeiser.com/perl-xs-c++.html.
- Уолл Л., Кристиансен Т., Орвант Д. Программирование на Perl. – СПб.: Символ-плюс, 2005. – 1152 с.
- http://search.cpan.org/~nwclark/perl-5.8.8/ext/XS/Typemap/Typemap.xs.