Inline::CPP — замечательный инструмент для того, чтобы с легкостью собирать вместе компоненты, написанные на C++ и Perl.
Не то, чтобы мне потребовалось радикально оптимизировать программу, переписав критические куски на C++. Мне просто захотелось сделать на этом языке часть проекта, написанного на Perl. Мне потребовался грамматический парсер, который легко удалось написать, используя библиотеку Spirit из комплекта Boost.
Что у нас есть для вызова функции из программы на Perl? У нас есть XS, но он в оригинале пригоден для связки с C, а не C++. Даунгрейдить программу ради промежуточного инструмента — не выход. В любом случае писать приходится на синтетическом языке (я пробовал).
Существуют несколько рецептов того, как используя XS подключить фрагменты программы на C++. Но даже те методы, которые аккуратно и доступно изложены, неадекватно сложны для вызова calc(«2+2»). Например, по одному из старых рецептов требуется выполнить с десяток шагов, найти специальный файл perlobject.map, а в каталоге с проектом возникает месиво из файлов:
blib lib MANIFEST MyPackage.o perlobject.map ppport.h typemap
Changes Makefile MyPackage.bs MyPackage.xs perl-xs-c++.html README
_howto.txt Makefile.PL MyPackage.c MyPackage.xsc pm_to_blib t
Мой выбор — Inline::CPP. Вся программа сводится к подключению этого модуля и вызову нужной функции:
use Modern::Perl;
use Inline 'CPP' => "./calc.cpp";
say calc($ARGV[0]);
Магия компиляции и сборки остается за горизонтом; единственное видимое проявление — каталог _Inline в рабочей директории (но и его можно перенести в /tmp или куда угодно). И, разумеется, замедление первой загрузки программы. При повторных запусках задержек нет, потому что используются готовые .so-библиотеки.
Замечательно, что C++-файл остается C++-программой: никаких дополнительных инструкций не требуется. Еще более важно, что такая программа, не будучи специально предназначенной для работы с перлом, может быть частью обычной программы на C++. Например, функцию можно вызвать для тестирования:
#include<iostream>
using namespace std;
double calc(char*);
int main() {
cout << calc("1+2*3") << "\n";
}
У Inline::CPP пара мелких недостатков (которые, в прочем, никак не омрачают впечатление, удобство и пользу модуля). Во-первых, вывод сообщений об ошибках, который в моем случае по умолчанию попадал в логи Апача, оказывается месивом из строк и литерализированных переводов строк. Вместе с сообщением о самой ошибке (а в C++, когда используешь шаблонные классы, легко получить многостраничное сообщение даже при небольшой ошибке) сюда же попадает командная строка, с которой вызывался компилятор C++:
/usr/bin/perl /usr/share/perl/5.10/ExtUtils/xsubpp -typemap /usr/share/perl/5.10/ExtUtils/typemap Calculator_f4e3.xs > Calculator_f4e3.xsc && mv Calculator_f4e3.xsc Calculator_f4e3.c\ng++ -c -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g -DVERSION=\\"0.00\\" -DXS_VERSION=\\"0.00\\" -fPIC "-I/usr/lib/perl/5.10/CORE" Calculator_f4e3.c\nIn file included from Calculator_f4e3.xs:15:\n/usr/include/boost/spirit/core.hpp:18:4: warning: #warning "This header is deprecated. Please use: boost/spirit/include/classic_core.hpp"\nCalculator_f4e3.xs: In function 'double eval_expr()':\nCalculator_f4e3.xs:154: error: 'info' was not declared in this scope\nCalculator_f4e3.xs: In function 'double calc(const char*)':\nCalculator_f4e3.xs:190: error: 'exal_expr' was not declared in this scope\nCalculator_f4e3.c: In function 'void boot_WHL__Parser__Calculator_f4e3(PerlInterpreter*, CV*)':\nCalculator_f4e3.c:354: warning: deprecated conversion from string constant to 'char*'\nmake: *** [Calculator_f4e3.o] Error 1\n\nA problem was encountered while attempting to compile and install your Inline\nCPP code. The command that failed was:\n make > out.make 2>&1\n\nThe build directory was:\n/_Inline/build/WHL/Parser/Calculator_f4e3\n\nTo debug the problem, cd to the build directory, and inspect the output files.\n\n at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nCompilation failed in require at (eval 39) line 3.\n\t...propagated at /usr/share/perl/5.10/base.pm line 92.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Tinyurl/Worker.pm line 12.\nCompilation failed in require at /usr/local/apache2-dev/conf/vhosts/ash.dev line 480.\nBEGIN failed--compilation aborted\t(in cleanup) /usr/bin/perl /usr/share/perl/5.10/ExtUtils/xsubpp -typemap /usr/share/perl/5.10/ExtUtils/typemap Calculator_f4e3.xs > Calculator_f4e3.xsc && mv Calculator_f4e3.xsc Calculator_f4e3.c\ng++ -c -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g -DVERSION=\\"0.00\\" -DXS_VERSION=\\"0.00\\" -fPIC "-I/usr/lib/perl/5.10/CORE" Calculator_f4e3.c\nIn file included from Calculator_f4e3.xs:15:\n/usr/include/boost/spirit/core.hpp:18:4: warning: #warning "This header is deprecated. Please use: boost/spirit/include/classic_core.hpp"\nCalculator_f4e3.xs: In function 'double eval_expr()':\nCalculator_f4e3.xs:154: error: 'info' was not declared in this scope\nCalculator_f4e3.xs: In function 'double calc(const char*)':\nCalculator_f4e3.xs:190: error: 'exal_expr' was not declared in this scope\nCalculator_f4e3.c: In function 'void boot_WHL__Parser__Calculator_f4e3(PerlInterpreter*, CV*)':\nCalculator_f4e3.c:354: warning: deprecated conversion from string constant to 'char*'\nmake: *** [Calculator_f4e3.o] Error 1\n\nA problem was encountered while attempting to compile and install your Inline\nCPP code. The command that failed was:\n make > out.make 2>&1\n\nThe build directory was:\n/_Inline/build/WHL/Parser/Calculator_f4e3\n\nTo debug the problem, cd to the build directory, and inspect the output files.\n\n at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nCompilation failed in require at (eval 39) line 3.\n\t...propagated at /usr/share/perl/5.10/base.pm line 92.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Tinyurl/Worker.pm line 12.\nCompilation failed in require at /usr/local/apache2-dev/conf/vhosts/ash.dev line 480.\nBEGIN failed--compilation aborted at /usr/local/apache2-dev/conf/vhosts/ash.dev line 480.\n
Во-вторых, из документации не сразу понимаешь, как обеспечить прозрачную компиляцию, когда хочется компилировать внешний файл (а не писать код прямо в Perl-файле после __DATA__). А поскольку мне нужен вывод через поток iostream из стандартной библиотеки STL, приходится конфигурировать Inline::CPP для его поддержки. Несколько впадаешь в ступор, когда понимаешь, что одновременно эти пожелания выполнить не получится. Вместо этого, оказывается, нужно записать use Inline::CPP дважды:
use Modern::Perl; use utf8; use Inline 'CPP' => Config => ENABLE => 'STD_IOSTREAM', DIRECTORY => '/tmp'; use Inline 'CPP' => "$ENV{PROJECT_ROOT}/lib/WHL/Parser/calculator.cpp";
Резюме: пользуйтесь Inline::CPP, это облегчит жизнь.