|
Просмотр темы |
Главная -->
Компьютеры -->
Интернет -->
Программирование -->
perl -->
|
|
« Предыдущий контент
|
На уровень выше
|
Посмотреть подразделы
|
|
|
 Дата: 1276570129
 Новое: 1276570509
|
|
Посмотреть комментарии этой темы
|
|
|
|
HOWTO по работе с С++ классами из Perl'a
|
|
| Описаний, как импортировать функции из С, достаточно много, а вот как использовать С++ классы, я увидел в виде кратких описаний только в XS Cookbook [1, 2] и небольшой статье «Gluing C++ And Perl Together» [4]. Пример использования С++ класса в Perl'е из XS Cookbook в сокращенном варианте перекочевал в perlxstut. Кроме того, на CPAN можно найти модули, импортирующие С++ классы и имеющие файлы импорта, которые можно использовать в качестве примера, такие, как Boost-Graph, Lucene, Search-Xapian, Однако они не покрывают многих вариантов подключения классов.
Чтобы не умножать сущности без надобности, т.е. не писать новых классов, которые потом нигде не пригодятся, воспользуемся уже готовой библиотекой QtCore из Qt4. Для удобства будут приведены части заголовочных файлов этой библиотеки, но все примеры будут работоспособны при подключении реальной библиотеки. Кроме того, использование файла perlobject.map [3] позволяет не писать заново описание объектов.
Преимущества:
1) Быстрая скорость разработки, так как perl язык с динамической типизацией (dynamically typed), C++ и в какой-то мере Java — вы можете очень просто порождать ошибки, связанные со статическим типом переменной, в которую вы помещаете динамическое значение совместимого, но не идентичного типа.
2) Быстрая инициализация классов, загрузка кода на этапе компиляции (require), так же наследование (parent) и методов класса (SelfLoader и AutoLoader etc).
3) Кроссплатформенность программ, вероятность запуска на любой машине.
4) Самое большое количество модулей на cpan.org (16292 modules).
5) Автоматическая сборка мусора.
Начальные данные для любого модуля
Начальные данные для любого модуля можно найти в уже упомянутой статье [4], однако они столь ценны и необходимы для раскрытия темы, что заслуживают отдельного рассмотрения. Скелет любого модуля можно написать вручную, но легче и быстрее это сделать командой h2xs -An имя_модуля. В результате будет создан каталог для модуля с необходимыми файлами, содержимое которых детально описано в "Программировании на Perl" [5]. Дав команду h2xs -An QtCore, получим скелет модуля. В полученный каталог QtCore необходимо скопировать perobject.map (названия всех файлов приводятся отностительно каталога QtCore). Созданный файл Makefile.PL надо привести к следующему виду:
Выделенные жирным строки надо добавить именно для того, чтобы Perl заработал с С++.
Кроме того, важно исправить файл QtCore.xs, который будет содержать импортируемые в Perl функции:
Для наглядного примера создадим в этом файле класс, который будет хранить, допустим, версию программы. Для этого добавим класс после подключенных заголовочных файлов перед строкой "MODULE = QtCore PACKAGE = QtCore":
Работа с обычными функциями, конструктором и деструктором уже предусмотрена в Perl XS, поэтому после объявления модуля и пакета можно использовать краткие объявления функций (также возможны комментарии в perl-стиле, которые мы и будет использовать):
Кроме того, для вызова класса следует указать Perl'у, чем является класс QtCore, т.е. как работать с этим типом данных, для чего создадим файл typemap со следующим содержимым:
Описания встроенных типов данных можно посмотреть в typemap.xs [6], а описание O_OBJECT находится в файле perlobject.map. Если не добавлять этот файл, то придется все дополнительные типы данных полностью описывать самим в файле typemap (пример полного описания приводится ниже). После этого остается внести изменения в файл lib/QtCore.pm, который и будет подключаться в конечных скриптах. Поскольку QtCore.pm будет объектом и ничего экспортироваться из него не будет, то следует из этого файла убрать всё относящееся к модулю Exporter. Для импорта внешних функций можно использовать как XSLoader, так и более старый DynaLoader (здесь используется второй, т.к. к нему чаще обращаются).
Чтобы собрать полученный модуль, выполним команды perl Makefile.PL && make.
Всё сделанное необходимо протестировать. В модуле уже есть каталог t/ для тестовых скриптов, которые, однако, расчитаны только на то, чтоб по команде make test вывести "имя_скрипта.....ok". Этого явно недостаточно, чтобы подробно просмотреть работоспособность написанного модуля. Поэтому создадим каталог test/ со скриптом qtcore.pl и следующим содержимым:
В результате запуска скрипта должна появиться указанная нами версия 4.001.
Импортирование нескольких классов
Редко можно найти библиотеку, состоящую только из одного класса. Когда классов не много, их можно описать в одном xs-файле. Или последовать примеру модуля Search-Xapian, в котором один большой файл разбит на несколько, импортируемых в один командой
Однако главным недостатком такого подхода является необходимость подключения в одном месте всех заголовочных файлов, содержимое которых будет находиться в одной области видимости. Третий вариант, особенно удобный для такой большой библиотеки, как 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 раскомментируем строку
Вследствие этого будут отрабатываться все xs-файлы, найденные в каталоге модуля (во вложенных каталогах поиск не ведётся). Подключим библиотеку QtCore.so, для чего в строку
пропишем её:
Для примера импортирования нескольких классов выберем небольшой класс QSize (здесь часть файла с функциями, которые будут использованы ниже):
Создадим файл QSize.xs (оставив на потом некоторые функции):
Далее создаем для класса QSize свой pm-файл lib/QtCore/QSize.pm
В дальнейшем файлы QtCore.xs и lib/QtCore.pm будут нужны только для вызова bootstrap модуля QtCore.pm. Заметим, что класс в QtCore.xs можно удалить, но тогда придется добавить хотя бы одну внешнюю функцию, иначе в файле QtCore.c, который создается на основе QtCore.xs, не будет всех нужных объявлений. Вообще все boot-функции и объявления в них можно прописать и вручную, но вряд ли это целесообразно, если компилятор XS делает всё сам. Теперь следует добавить в lib/QtCore.pm функцию, которая будет выполнять роль bootstrap для остальных модулей:
Остался последний шаг в нашем примере импортирования нескольких классов. Класс следует описать в файле typemap, добавив в конце:
Вот теперь уже можно запустить perl Makefile.PL && make и потестировать, что получилось.
Создадим файл test/qsize.pl для теста:
Использование нескольких конструкторов
Класс QSize содержит два конструктора, а компилятор XS знает только про new. Поэтому второй конструктор реализуем сами. Чтобы увидеть, что для этого надо, достаточно посмотреть в файл QSize.c, автоматически сгенерированный компилятором XS из файла QSize.xs:
Иными словами, благодаря "QSize::", расположенному перед конструктором "new", в функцию передается строковый параметр CLASS с наименованием класса, после чего создается класс и используется bless для полученной ссылки. Для примера импорта конструктора в QSize.xs создадим конструктор "new1" с явным указанием компилятору на код и возвращаемый параметр:
Теперь запустим make. Получный в QSize.c код для "new1" будет идентичен автоматически созданному коду для конструктора "new". Однако появятся две пометки о том, что код взят из QSize.xs. Аналогично создадим второй конструктор, но уже с параметрами инициализации:
Заметим, что в Perl'е удобнее было бы использовать индификатор "new" для вызова любого конструктора, не запоминая их номера. Для реализации этого удалим из QSize.xs вызов QSize::new(), после чего добавим в lib/QtCore/QSize.pm функцию с таким же названием. Данная функция в зависимости от содержимого сама будет выбирать, что ей вызвать. При неверном количестве параметров функция выведет сообщение об ошибке:
Далее даем команду make и проверяем, как всё работает, для чего добавим в test/qsize.pl строку
Сложение классов ( operator+ )
Если в исходном классе, написанном на С++, содержатся операторы "арифметических" и "логических" действий c классами, то данные функции желательно тоже импортировать в Perl.
Сначала рассмотрим, что добавить в QSize.xs для
Оператор возвращает тот же класс, к которому осуществляется прибавление, поэтому возвратить QSize можно и в функции на Perl'e. Поскольку Perl внутри работает только с указателями, то перед передачей функции прибавляемого класса otherSize его (указатель) следует разыменовать:
Или, например, другой оператор:
Несмотря на то что фунция operator+ не является внутренней для класса, это не мешает получить указатель на первый класс предыдущим способом. В то же время operator+ возвращает новый объект QSize, который будет жить только в пределах С-функции. Нам же необходимо вернуть указатель на новый объект QSize. Поэтому создадим новый экземпляр класса QSize и ему приравняем возвращенный класс. Класс QSize простой, поэтому конструктор копий создается компилятором автоматически.
В файле lib/QtCore/QSize.pm следует сделать для данных функций оболочку, используя overload (подробности использования overload смотрите в perldoc или "Программировании на Perl" [5, стр 397]):
В заключение осталось проверить работоспособность операторов. Добавим в test/qsize.pl такие строки:
И, запустив, увидим как это работает.
Особенности использования enum
Работа с enum предусмотрена в Perl XS, однако с C++ появляется одна неприятность. Во время обработки xs-файла компилятором XS обращения в другие классы за определёнными в них enum, как, например, Qt::AspectRatioMode, в с-файле Qt::AspectRatioMode превращается в Qt__AspectRatioMode. И выдается ошибка компилятора о том, что не найден тип Qt__AspectRatioMode. К сожалению, нет никакой возможности избежать этого преобразования, ибо таким способом создаются все функции с целью не допустить дублирования названий функций с другими классами. Чтобы компилятор правильно увидел используемый нами enum, переопределим его в исходный облик. В C-части xs-файла после подключения qsize.h добавим:
Теперь можно функцию с этим типом данных:
Обратите внимание, что важно не забыть добавить в typemap новый тип данных:
Для того чтобы не запоминать числовые значения всех enum-параметров, добавим модуль lib/Qt.pm со всеми значениями AspectRatioMode:
После добавления или удаления любого файла, следует полностью очитить библиотеку, удалив каталог blib, файлы *.c, *.o & Co. После данных манипуляций и выполнения команд perl Makefile.PL && make можно тестировать программу. Для этого после use blib в файле qsize.pl следует добавить
а также добавить новую функцию в конце этого файла:
Взаимодействие с STL или её аналогами
В Perl'e STL практически не нужна, поскольку большинство возможностей STL уже поддерживаются массивами и хешами Perl'a. Поэтому рассмотрим только передачу данных из шаблона list в массив Perl'a и обратно. Библиотека Qt4 инкапсулирует в себя STL, добавляя некоторые возможности. Мы подробно рассмотрим работу с шаблоном QList, ибо методы некоторых классов возвращают списки классов, используя именно его. Для получения массива обратимся к классу QByteArray, в нём есть такой конструктор:
В файле QByteArray.xs перед использованием шаблонов STL необходимо убрать определения do_open и do_close, иначе они начнут конфликтовать с аналогичными из Perl'a.
Иными словами, в описании _split создается анонимный массив, указатель на который будет передан в программу. Затем вызывается функция split класса на C++, которая возвращает список классов QByteArray. Этот список обходится в цикле, в котором по одному указателю на класс заносится в массив RETVAL. Поскольку массив принимает только тип данных SV*, то на каждой итерации цикла создается новая переменная. Затем в нее копируется ссылка на класс из списка, приведенная к типу данных Perl функцией sv_setref_pv. Подробно работа с массивами в Perl XS описана в perlguts, а примеры использования массива со строками можно посмотреть в "XS Cookbook" [2].
Следующий шаг состоит в добавлении в typemap нового класса
и создании для этого класса модуля lib/QtCore/QByteArray.pm. Функция _split возвращает указатель на массив, однако в программе удобнее пользоваться обычным массивом. С этой целью напишем простейшую оболочку для этой функции. Кроме того, в Perl'e есть своя функция split, поэтому ее надо в пакете переопределить, используя use subs.
Пересобираем пакет и тестируем (файл test/qbytearray.pl):
Аналогичным способом массив превращается в шаблон QList. Для примера приведём конструктор класса QStringList, получающий для инициализации массив классов QString. В файле QtCore/qstrinlist.h конструктор объявлен как
В xs-файле для него необходимо создать класс QList <QString> и заполнить его классами QString, полученными из массива. av является указателем на копию этого массива. Копия используется, поскольку функция av_pop(), которой мы здесь воспользуемся, удаляет считанные элементы из массива.
Описание типа данных, отсутствующего в typemap.xs и perlobject.map
В том случае, если нужно добавить новый тип данных, необходимо описать, как компилятору с ним работать, т.е. читать из него данные и записывать. Данная тема описана в документации Perl, но для полноты картины приведём пример, иллюстрирующий работу с отсутствующим в С типом string. Для этого типа в typemap следует добавить:
Ниже в разделах INPUT и OUTPUT необходимо описать, как перевести string из
внутреннего типа данных Perl'а (переменная $arg) в C++ (переменная $var) и обратно.
Приведение классов к привычному для Perl'a виду
В вышеприведенных способах создания объектов используется указатель на скаляр, в то время как наиболее привычным указателем для создания классов является указатель на хеш, в котором можно будет хранить переменные класса, если мы захотим что-то дописать на Perl'e:
Для того чтобы привести класс к привычному для Perl'a виду, создадим хеш в XS-файле, а скаляр, который является указателем на С++ класс, сохраним в этом же хеше. Бывают случаи, что класс создаётся не в XS файле, а функция получает указатель на него, и/или конструктор копии этого класса находится в секции private. Тогда при уничтожении Perl-объекта С++ класс удаляться не должен. Чтобы класс не удалился, нужно сделать пометку в хеше. Помимо этого, необходимо осуществить и обратную операцию – получить указатель на класс из хеша для работы с вызываемыми функциями.
Поскольку описанные выше операции придётся делать для большинства функций, то удобнее вынести этот код наружу – во внешний подключаемый файл, который назовём common.xs. Обратите внимание, что не cpp, а xs. Данное расширение используется, чтобы XS-компилятор сам подхватил common.xs файл.
Для подключения common.xs нужен заголовочный файл common.h:
Теперь посмотрим, какими станут конструктор и деструктор в XS-файле:
Поскольку вызовы объекта были изменены, то теперь необходимо указать Perl'у, как работать с новыми типами данных, т. е. поменять описание O_OBJECT в файле perlobject.map:
Обратите внимание, что в коде уже учтено использование библиотеки Qt. Для использования кода с другой библиотекой его необходимо исправить.
Post Scriptum
В начале этой статьи мы писали проверку на количество параметров для перегруженных функций в Perl-коде. Однако параметры могут различаться и по типу переменных, чего на уровне Perl-кода не проверишь, а вот на уровне Perl XS можно проверить, в каком контексте последний раз использовалась переменная. Воспользуемся этим и заодно перенесём на уровень Perl XS обработку перегруженных функций. В результате получаем конструктор такого вида:
Подобным образом можно сделать разбор и других перегруженных функций, а не только конструкторов.
Таким образом, в данной статье были рассмотрены все основные варианты использования C++ и Perl XS. За пределами рассмотрения остались только прямое использование шаблонных классов (но, как было указано выше, использовать их нецелесообразно, т. к. STL покрывается возможностями самого Perl'a) и использование lvalue-функций из классов С++ в Perl'e (когда разрабатывался Perl XS для 5-ой версии, lvalue изначально не был выписан и в самом Perl5, а более поздних описаний расширений Perl XS на данный момент, по моим сведениям, не существует).
Несколько примеров:
main.cpp
main.pl
main.cpp
screenshot.h
screenshot.cpp
main.pl
|
|
|
|
|
|
|
|
|
|
| Фотоальбомы:
|
|
фотоальбомов не найдено
|
|
|
|
|
|
| Видеоальбомы:
|
|
Видеоальбомов не найдено
|
|
|
|
Комментарии |
|
|
|
test01
:: :: :: 1276570273 :: ссылка :: ::
|
.... ПРОДОЛЖЕНИЕ:
screenshot.cpp
main.cpp
mainwindow.h
mainwindow.cpp
main.pl
MainWindow.pl
main.cpp
dialog.cpp
dialog.h
main.pl
|
test01
:: :: :: 1276570372 :: ссылка :: ::
|
.... ПРОДОЛЖЕНИЕ:
dialog.pm
main.cpp
main.pl
Литература:
0.Документация Perl (perlxs, perlxstut, perlguts)
1. Dean's Extension-Building Cookbook in two parts:
Part A: http://www.cpan.org/authors/Dean_Roehrich/CookBookA-19960430.tar.gz
2. Dean's Extension-Building Cookbook in two parts:
Part B: http://www.cpan.org/authors/Dean_Roehrich/CookBookB-19960430.tar.gz
3. http://www.cpan.org/authors/Dean_Roehrich/perlobject.map-19960302.gz
4. John Keiser. Gluing C++ And Perl Together. — 2001. — http://www.johnkeiser.com/perl-xs-c++.html
5. Уолл Л., Кристиансен Т., Орвант Д. Программирование на Perl. — СПб.: Символ-плюс, 2005. — 1152 с.
6. http://search.cpan.org/~nwclark/perl-5.8.8/ext/XS/Typemap/Typemap.xs
- Рассмотренные в конце статьи файлы common.xs и perlobject.map могут быть взяты на CPAN'е из http://search.cpan.org/CPAN/authors/id/V/VA/VADIML/QtCore-4.004.tar.gz и остальные примеры тут http://search.cpan.org/CPAN/authors/id/V/VA/VADIML/QtGui-4.004.tar.gz
взято тут |
|
|
|
|
|
|