perlembed
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ОПИСАНИЕ
- ПРЕДИСЛОВИЕ
- ПЛАН РАБОТЫ
- Компиляция вашей C-программы
- Добавление Perl-интерпретатора в вашу C-программу
- Вызов Perl-подпрограммы из вашей C-программы
- Вычисление Perl-выражения из вашей C-программы
- Выполнение Perl-поиска по шаблону и подстановок из вашей C-программы
- Работа со стеком Perl из вашей C-программы
- Поддержание постоянного интерпретатора
- Выполнение блоков END
- $0-присваивания
- Поддержание нескольких экземпляров интерпретатора
- Использование Perl-модулей, которые сами используют C-библиотеки, из вашей C-программы
- Использование встроенного Perl с POSIX-локализациями
- Скрытие Perl_
- МОРАЛЬ
- АВТОР
- АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlembed - как встроить Perl в вашу C-программу
ОПИСАНИЕ
ПРЕДИСЛОВИЕ
Вы хотите:
- Использовать C из Perl?
- Использовать программу Unix из Perl?
-
Прочитайте о знаках обратной кавычки и о
systemиexecв perlfunc. - Использовать Perl из Perl?
-
Прочитайте о "do" в perlfunc, "eval" в perlfunc, "require" в perlfunc и "use" в perlfunc.
- Использовать C из C?
-
Пересмотрите свой дизайн.
- Использовать Perl из C?
-
Продолжайте чтение...
ПЛАН РАБОТЫ
-
Компиляция вашей C-программы
-
Добавление Perl-интерпретатора в вашу C-программу
-
Вызов Perl-подпрограммы из вашей C-программы
-
Вычисление Perl-выражения из вашей C-программы
-
Выполнение Perl-поиска по шаблону и подстановок из вашей C-программы
-
Работа со стеком Perl из вашей C-программы
-
Поддержание постоянного интерпретатора
-
Поддержание нескольких экземпляров интерпретатора
-
Использование Perl-модулей, которые сами используют C-библиотеки, из вашей C-программы
-
Встраивание Perl под Win32
Компиляция вашей C-программы
Если у вас возникли проблемы с компиляцией скриптов в этой документации, вы не одиноки. Основное правило: СКОМПИЛЮЙТЕ ПРОГРАММЫ ТОЧНО ТАК ЖЕ, КАК БЫЛ СКОМПИЛИРОВАН ВАШ PERL. (Извините за крик.)
Кроме того, каждая C-программа, использующая Perl, должна быть связана с библиотекой Perl. Что это такое, спросите вы? Perl сам написан на C; библиотека Perl представляет собой набор скомпилированных C-программ, которые были использованы для создания вашего исполняемого файла perl (/usr/bin/perl или эквивалента). (Следствие: вы не можете использовать Perl из вашей C-программы, если Perl не был скомпилирован на вашей машине или не был установлен должным образом — вот почему вы не должны бездумно копировать исполняемые файлы Perl с одной машины на другую без копирования также директории lib.)
Когда вы используете Perl из C, ваша C-программа обычно выделяет, «выполняет» и освобождает объект PerlInterpreter, который определён библиотекой perl.
Если ваша копия Perl достаточно современная, чтобы содержать эту документацию (версия 5.002 или выше), то библиотека Perl (и EXTERN.h и perl.h, которые вам также понадобятся) будут находиться в каталоге, который выглядит примерно так:
/usr/local/lib/perl5/your_architecture_here/CORE или, возможно, просто
/usr/local/lib/perl5/CORE или, может быть, что-то вроде
/usr/opt/perl5/CORE Выполните эту команду для подсказки о том, где найти CORE:
perl -MConfig -e 'print $Config{archlib}' Вот как вы бы скомпилировали пример в следующем разделе, "Добавление Perl-интерпретатора в вашу C-программу", на моей Linux-машине:
% gcc -O2 -Dbool=char -DHAS_BOOL -I/usr/local/include
-I/usr/local/lib/perl5/i586-linux/5.003/CORE
-L/usr/local/lib/perl5/i586-linux/5.003/CORE
-o interp interp.c -lperl -lm (Это одна строка.) На моей DEC Alpha с устаревшей версией 5.003_05 заклинание немного отличается:
% cc -O2 -Olimit 2900 -I/usr/local/include
-I/usr/local/lib/perl5/alpha-dec_osf/5.00305/CORE
-L/usr/local/lib/perl5/alpha-dec_osf/5.00305/CORE -L/usr/local/lib
-D__LANGUAGE_C__ -D_NO_PROTO -o interp interp.c -lperl -lm Как вы можете выяснить, что добавить? Предполагая, что ваш Perl после версии 5.001, выполните команду perl -V и обратите особое внимание на информацию "cc" и "ccflags".
Вам нужно будет выбрать соответствующий компилятор (cc, gcc и т. д.) для вашей машины: perl -MConfig -e 'print $Config{cc}' подскажет вам, какой использовать.
Вам также нужно будет выбрать соответствующий каталог библиотеки (/usr/local/lib/...) для вашей машины. Если ваш компилятор жалуется, что некоторые функции не определены или что он не может найти -lperl, то вам нужно изменить путь после -L. Если он жалуется, что не может найти EXTERN.h и perl.h, вам нужно изменить путь после -I.
Возможно, вам также придётся добавить дополнительные библиотеки. Какие? Возможно, те, что выведены
perl -MConfig -e 'print $Config{libs}' При условии, что ваш двоичный файл perl был должным образом сконфигурирован и установлен модуль ExtUtils::Embed, он определит всю эту информацию за вас:
% cc -o interp interp.c `perl -MExtUtils::Embed -e ccopts -e ldopts` Если модуль ExtUtils::Embed не входит в вашу дистрибуцию Perl, вы можете получить его с http://www.perl.com/perl/CPAN/modules/by-module/ExtUtils/ (Если эта документация пришла из вашей дистрибуции Perl, то вы используете версию 5.004 или выше, и у вас она уже есть.)
Набор ExtUtils::Embed на CPAN также содержит весь исходный код примеров в этом документе, тесты, дополнительные примеры и другую полезную информацию.
Добавление Perl-интерпретатора в вашу C-программу
В некотором смысле, perl (C-программа) является хорошим примером встраивания Perl (языка), поэтому я продемонстрирую встраивание с помощью miniperlmain.c, включенного в дистрибутив исходного кода. Вот искажённая, непереносимая версия miniperlmain.c, содержащая основные моменты встраивания:
#include <EXTERN.h> /* from the Perl distribution */
#include <perl.h> /* from the Perl distribution */
static PerlInterpreter *my_perl; /*** The Perl interpreter ***/
int main(int argc, char **argv, char **env)
{
PERL_SYS_INIT3(&argc,&argv,&env);
my_perl = perl_alloc();
perl_construct(my_perl);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
perl_parse(my_perl, NULL, argc, argv, (char **)NULL);
perl_run(my_perl);
perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
exit(EXIT_SUCCESS);
} Обратите внимание, что мы не используем указатель env. Обычно переданный perl_parse в качестве последнего аргумента, env здесь заменён на NULL, что означает, что будет использован текущий окружение.
Макросы PERL_SYS_INIT3() и PERL_SYS_TERM() обеспечивают настройку среды выполнения C, специфичную для системы, необходимую для запуска Perl-интерпретаторов; они должны вызываться только один раз независимо от того, сколько интерпретаторов вы создаёте или уничтожаете. Вызовите PERL_SYS_INIT3() перед созданием вашего первого интерпретатора и PERL_SYS_TERM() после освобождения вашего последнего интерпретатора.
Поскольку PERL_SYS_INIT3() может изменить env, может быть уместнее передать env в качестве аргумента функции perl_parse().
Также обратите внимание, что независимо от того, какие аргументы вы передаёте в perl_parse(), PERL_SYS_INIT3() должен быть вызван для C main() argc, argv и env и только один раз.
Обратите внимание, что argv[argc] должен быть NULL, также как и те, которые передаются в функцию main в C.
Теперь скомпилируйте эту программу (назову её interp.c) в исполняемый файл:
% cc -o interp interp.c `perl -MExtUtils::Embed -e ccopts -e ldopts` После успешной компиляции вы сможете использовать interp так же, как и сам perl:
% interp
print "Pretty Good Perl \n";
print "10890 - 9801 is ", 10890 - 9801;
<CTRL-D>
Pretty Good Perl
10890 - 9801 is 1089 или
% interp -e 'printf("%x", 3735928559)'
deadbeef Вы также можете читать и выполнять Perl-команды из файла во время работы вашей C-программы, поместив имя файла в argv[1] перед вызовом perl_run.
Вызов Perl-подпрограммы из вашей C-программы
Для вызова отдельных Perl-подпрограмм можно использовать любые функции call_*, описанные в perlcall. В этом примере мы будем использовать call_argv.
Это показано ниже в программе, которую я назову showtime.c.
#include <EXTERN.h>
#include <perl.h>
static PerlInterpreter *my_perl;
int main(int argc, char **argv, char **env)
{
char *args[] = { NULL };
PERL_SYS_INIT3(&argc,&argv,&env);
my_perl = perl_alloc();
perl_construct(my_perl);
perl_parse(my_perl, NULL, argc, argv, NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
/*** skipping perl_run() ***/
call_argv("showtime", G_DISCARD | G_NOARGS, args);
perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
exit(EXIT_SUCCESS);
} где showtime — это Perl-подпрограмма, не принимающая аргументов (это G_NOARGS), и я проигнорирую возвращаемое значение (это G_DISCARD). Эти и другие флаги обсуждаются в perlcall.
Я определю подпрограмму showtime в файле showtime.pl:
print "I shan't be printed.";
sub showtime {
print time;
} Достаточно просто. Теперь скомпилируем и запустим:
% cc -o showtime showtime.c \
`perl -MExtUtils::Embed -e ccopts -e ldopts`
% showtime showtime.pl
818284590 что даст количество секунд, прошедших с 1 января 1970 года (начало эпохи Unix) до момента, когда я начал писать эту фразу.
В этом конкретном случае нам не нужно вызывать perl_run, так как мы установили PL_exit_flag PERL_EXIT_DESTRUCT_END, который выполняет блоки END в perl_destruct.
Если вы хотите передать аргументы Perl-подпрограмме, вы можете добавить строки в NULL-терминированный список args , переданный в call_argv. Для других типов данных или для проверки возвращаемых значений вам потребуется манипулирование стеком Perl. Это показано в разделе "Работа со стеком Perl из вашей C-программы".
Вычисление Perl-выражения из вашей C-программы
Perl предоставляет две функции API для вычисления фрагментов Perl-кода. Это "eval_sv" в perlapi и "eval_pv" в perlapi.
Пожалуй, это единственные функции, которые вам понадобятся для выполнения фрагментов Perl-кода внутри вашей C-программы. Ваш код может быть сколь угодно длинным; он может содержать несколько операторов; он может использовать "use" в perlfunc, "require" в perlfunc и "do" в perlfunc для включения внешних Perl-файлов.
eval_pv позволяет нам вычислить отдельные Perl-строки, а затем извлечь переменные для приведения к типам C. Следующая программа, string.c, выполняет три Perl-строки, извлекая int из первой, float из второй и char * из третьей.
#include <EXTERN.h>
#include <perl.h>
static PerlInterpreter *my_perl;
main (int argc, char **argv, char **env)
{
char *embedding[] = { "", "-e", "0", NULL };
PERL_SYS_INIT3(&argc,&argv,&env);
my_perl = perl_alloc();
perl_construct( my_perl );
perl_parse(my_perl, NULL, 3, embedding, NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
perl_run(my_perl);
/** Treat $a as an integer **/
eval_pv("$a = 3; $a **= 2", TRUE);
printf("a = %d\n", SvIV(get_sv("a", 0)));
/** Treat $a as a float **/
eval_pv("$a = 3.14; $a **= 2", TRUE);
printf("a = %f\n", SvNV(get_sv("a", 0)));
/** Treat $a as a string **/
eval_pv(
"$a = 'rekcaH lreP rehtonA tsuJ'; $a = reverse($a);", TRUE);
printf("a = %s\n", SvPV_nolen(get_sv("a", 0)));
perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
} Все эти странные функции с sv в их именах помогают преобразовать Perl-скаляры в типы C. Они описаны в perlguts и perlapi.
Если вы скомпилируете и запустите string.c, вы увидите результаты использования SvIV() для создания int, SvNV() для создания float, и SvPV() для создания строки:
a = 9
a = 9.859600
a = Just Another Perl Hacker В примере выше мы создали глобальную переменную для временного хранения вычисленного значения нашего выражения, которое было вычислено с помощью eval. Также возможно, и во многих случаях это более эффективный подход, получить возвращаемое значение из eval_pv(). Пример:
...
SV *val = eval_pv("reverse 'rekcaH lreP rehtonA tsuJ'", TRUE);
printf("%s\n", SvPV_nolen(val));
... Таким образом, мы избегаем загрязнения пространства имён, не создавая глобальных переменных, и упростили наш код.
Выполнение сопоставлений по шаблонам Perl и подстановок из вашей C-программы
Функция eval_sv() позволяет нам оценивать строки Perl-кода, поэтому мы можем определить некоторые функции, которые используют её для «специализации» на сопоставлениях и подстановках: match(), substitute() и matches().
I32 match(SV *string, char *pattern); Принимая строку и шаблон (например, m/clasp/ или /\b\w*\b/, который в вашей C-программе может выглядеть как "/\\b\\w*\\b/"), функция match() возвращает 1, если строка соответствует шаблону, и 0 в противном случае.
int substitute(SV **string, char *pattern); Принимая указатель на SV и операцию =~ (например, s/bob/robert/g или tr[A-Z][a-z]), функция substitute() изменяет строку внутри SV в соответствии с операцией, возвращая количество выполненных подстановок.
SSize_t matches(SV *string, char *pattern, AV **matches); Принимая SV, шаблон и указатель на пустой AV, функция matches() оценивает $string =~ $pattern в контексте списка и заполняет matches элементами массива, возвращая количество найденных совпадений.
Вот пример программы match.c, которая использует все три (длинные строки были здесь разбиты на несколько строк):
#include <EXTERN.h>
#include <perl.h>
static PerlInterpreter *my_perl;
/** my_eval_sv(code, error_check)
** kinda like eval_sv(),
** but we pop the return value off the stack
**/
SV* my_eval_sv(SV *sv, I32 croak_on_error)
{
dSP;
SV* retval;
PUSHMARK(SP);
eval_sv(sv, G_SCALAR);
SPAGAIN;
retval = POPs;
PUTBACK;
if (croak_on_error && SvTRUE(ERRSV))
croak_sv(ERRSV);
return retval;
}
/** match(string, pattern)
**
** Used for matches in a scalar context.
**
** Returns 1 if the match was successful; 0 otherwise.
**/
I32 match(SV *string, char *pattern)
{
SV *command = newSV(0), *retval;
sv_setpvf(command, "my $string = '%s'; $string =~ %s",
SvPV_nolen(string), pattern);
retval = my_eval_sv(command, TRUE);
SvREFCNT_dec(command);
return SvIV(retval);
}
/** substitute(string, pattern)
**
** Used for =~ operations that
** modify their left-hand side (s/// and tr///)
**
** Returns the number of successful matches, and
** modifies the input string if there were any.
**/
I32 substitute(SV **string, char *pattern)
{
SV *command = newSV(0), *retval;
sv_setpvf(command, "$string = '%s'; ($string =~ %s)",
SvPV_nolen(*string), pattern);
retval = my_eval_sv(command, TRUE);
SvREFCNT_dec(command);
*string = get_sv("string", 0);
return SvIV(retval);
}
/** matches(string, pattern, matches)
**
** Used for matches in a list context.
**
** Returns the number of matches,
** and fills in **matches with the matching substrings
**/
SSize_t matches(SV *string, char *pattern, AV **match_list)
{
SV *command = newSV(0);
SSize_t num_matches;
sv_setpvf(command, "my $string = '%s'; @array = ($string =~ %s)",
SvPV_nolen(string), pattern);
my_eval_sv(command, TRUE);
SvREFCNT_dec(command);
*match_list = get_av("array", 0);
num_matches = av_top_index(*match_list) + 1;
return num_matches;
}
main (int argc, char **argv, char **env)
{
char *embedding[] = { "", "-e", "0", NULL };
AV *match_list;
I32 num_matches, i;
SV *text;
PERL_SYS_INIT3(&argc,&argv,&env);
my_perl = perl_alloc();
perl_construct(my_perl);
perl_parse(my_perl, NULL, 3, embedding, NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
text = newSV(0);
sv_setpv(text, "When he is at a convenience store and the "
"bill comes to some amount like 76 cents, Maynard is "
"aware that there is something he *should* do, something "
"that will enable him to get back a quarter, but he has "
"no idea *what*. He fumbles through his red squeezey "
"changepurse and gives the boy three extra pennies with "
"his dollar, hoping that he might luck into the correct "
"amount. The boy gives him back two of his own pennies "
"and then the big shiny quarter that is his prize. "
"-RICHH");
if (match(text, "m/quarter/")) /** Does text contain 'quarter'? **/
printf("match: Text contains the word 'quarter'.\n\n");
else
printf("match: Text doesn't contain the word 'quarter'.\n\n");
if (match(text, "m/eighth/")) /** Does text contain 'eighth'? **/
printf("match: Text contains the word 'eighth'.\n\n");
else
printf("match: Text doesn't contain the word 'eighth'.\n\n");
/** Match all occurrences of /wi../ **/
num_matches = matches(text, "m/(wi..)/g", &match_list);
printf("matches: m/(wi..)/g found %d matches...\n", num_matches);
for (i = 0; i < num_matches; i++)
printf("match: %s\n",
SvPV_nolen(*av_fetch(match_list, i, FALSE)));
printf("\n");
/** Remove all vowels from text **/
num_matches = substitute(&text, "s/[aeiou]//gi");
if (num_matches) {
printf("substitute: s/[aeiou]//gi...%lu substitutions made.\n",
(unsigned long)num_matches);
printf("Now text is: %s\n\n", SvPV_nolen(text));
}
/** Attempt a substitution **/
if (!substitute(&text, "s/Perl/C/")) {
printf("substitute: s/Perl/C...No substitution made.\n\n");
}
SvREFCNT_dec(text);
PL_perl_destruct_level = 1;
perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
} что выводит результат (опять же, длинные строки были здесь разбиты на несколько строк):
match: Text contains the word 'quarter'.
match: Text doesn't contain the word 'eighth'.
matches: m/(wi..)/g found 2 matches...
match: will
match: with
substitute: s/[aeiou]//gi...139 substitutions made.
Now text is: Whn h s t cnvnnc str nd th bll cms t sm mnt lk 76 cnts,
Mynrd s wr tht thr s smthng h *shld* d, smthng tht wll nbl hm t gt
bck qrtr, bt h hs n d *wht*. H fmbls thrgh hs rd sqzy chngprs nd
gvs th by thr xtr pnns wth hs dllr, hpng tht h mght lck nt th crrct
mnt. Th by gvs hm bck tw f hs wn pnns nd thn th bg shny qrtr tht s
hs prz. -RCHH
substitute: s/Perl/C...No substitution made. Работа с Perl-стеком из вашей C-программы
При попытке объяснить стеки, большинство учебников по информатике бормочут что-то о пружинных столбцах тарелок в кафетерии: последняя положенная на стек тарелка — первая, которую вы снимаете. Это подойдёт для наших целей: ваша C-программа поместит некоторые аргументы в «Perl-стек», закроет глаза, пока произойдёт немного магии, и затем извлечёт результаты — возвращаемое значение вашей Perl-подпрограммы — со стека.
Сначала вам нужно узнать, как преобразовывать между типами C и типами Perl, используя newSViv(), sv_setnv(), newAV() и все их аналоги. Они описаны в perlguts и perlapi.
Затем вам нужно узнать, как управлять Perl-стеком. Это описано в perlcall.
После того, как вы это поймёте, встраивание Perl в C станет простым.
Поскольку в C нет встроенной функции для возведения в степень целых чисел, давайте сделаем оператор ** Perl доступным для него (это менее полезно, чем кажется, потому что Perl реализует ** с помощью функции pow() C). Сначала я создам заглушку функции возведения в степень в power.pl:
sub expo {
my ($a, $b) = @_;
return $a ** $b;
} Теперь я создам C-программу power.c с функцией PerlPower(), которая содержит все необходимые элементы из perlguts для передачи двух аргументов в expo() и извлечения возвращаемого значения. Глубокий вдох...
#include <EXTERN.h>
#include <perl.h>
static PerlInterpreter *my_perl;
static void
PerlPower(int a, int b)
{
dSP; /* initialize stack pointer */
ENTER; /* everything created after here */
SAVETMPS; /* ...is a temporary variable. */
PUSHMARK(SP); /* remember the stack pointer */
XPUSHs(sv_2mortal(newSViv(a))); /* push the base onto the stack */
XPUSHs(sv_2mortal(newSViv(b))); /* push the exponent onto stack */
PUTBACK; /* make local stack pointer global */
call_pv("expo", G_SCALAR); /* call the function */
SPAGAIN; /* refresh stack pointer */
/* pop the return value from stack */
printf ("%d to the %dth power is %d.\n", a, b, POPi);
PUTBACK;
FREETMPS; /* free that return value */
LEAVE; /* ...and the XPUSHed "mortal" args.*/
}
int main (int argc, char **argv, char **env)
{
char *my_argv[] = { "", "power.pl", NULL };
PERL_SYS_INIT3(&argc,&argv,&env);
my_perl = perl_alloc();
perl_construct( my_perl );
perl_parse(my_perl, NULL, 2, my_argv, (char **)NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
perl_run(my_perl);
PerlPower(3, 4); /*** Compute 3 ** 4 ***/
perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
exit(EXIT_SUCCESS);
} Компилировать и запускать:
% cc -o power power.c `perl -MExtUtils::Embed -e ccopts -e ldopts`
% power
3 to the 4th power is 81. Поддержание постоянного интерпретатора
При разработке интерактивных и/или потенциально долго работающих приложений, полезно поддерживать постоянный интерпретатор вместо многократного выделения и создания нового интерпретатора. Основная причина — скорость: Perl будет загружен в память только один раз.
Однако при использовании постоянного интерпретатора нужно быть более осторожным с пространством имён и областью видимости переменных. В предыдущих примерах мы использовали глобальные переменные в пакете по умолчанию main. Мы точно знали, какой код будет выполнен, и предполагали, что можем избежать столкновений переменных и ненормального роста таблицы символов.
Предположим, ваше приложение — сервер, который время от времени будет запускать Perl-код из произвольного файла. Ваш сервер не может знать, какой код он будет запускать. Очень опасно.
Если файл подтягивается с помощью perl_parse(), компилируется в новый интерпретатор и затем очищается с помощью perl_destruct() впоследствии, вы защищены от большинства проблем с пространством имён.
Один из способов избежать столкновений имён в этом сценарии — преобразовать имя файла в гарантированно уникальное имя пакета, а затем скомпилировать код в этот пакет, используя "eval" в perlfunc. В примере ниже каждый файл будет скомпилирован только один раз. Или приложение может выбрать очистку таблицы символов, связанной с файлом, после того, как он больше не нужен. Используя "call_argv" в perlapi, мы вызовем подпрограмму Embed::Persistent::eval_file, которая находится в файле persistent.pl, и передадим имя файла и флаг очистки/кеша как аргументы.
Обратите внимание, что процесс будет продолжать расти для каждого используемого файла. Кроме того, могут быть AUTOLOAD подпрограммы и другие условия, которые приводят к росту таблицы символов Perl. Возможно, вы захотите добавить логику, которая отслеживает размер процесса или перезапускает его после определённого количества запросов, чтобы свести к минимуму потребление памяти. Также вы хотите ограничить область видимости ваших переменных с помощью "my" в perlfunc всякий раз, когда это возможно.
package Embed::Persistent;
#persistent.pl
use strict;
our %Cache;
use Symbol qw(delete_package);
sub valid_package_name {
my($string) = @_;
$string =~ s/([^A-Za-z0-9\/])/sprintf("_%2x",unpack("C",$1))/eg;
# second pass only for words starting with a digit
$string =~ s|/(\d)|sprintf("/_%2x",unpack("C",$1))|eg;
# Dress it up as a real package name
$string =~ s|/|::|g;
return "Embed" . $string;
}
sub eval_file {
my($filename, $delete) = @_;
my $package = valid_package_name($filename);
my $mtime = -M $filename;
if(defined $Cache{$package}{mtime}
&&
$Cache{$package}{mtime} <= $mtime)
{
# we have compiled this subroutine already,
# it has not been updated on disk, nothing left to do
print STDERR "already compiled $package->handler\n";
}
else {
local *FH;
open FH, $filename or die "open '$filename' $!";
local($/) = undef;
my $sub = <FH>;
close FH;
#wrap the code into a subroutine inside our unique package
my $eval = qq{package $package; sub handler { $sub; }};
{
# hide our variables within this block
my($filename,$mtime,$package,$sub);
eval $eval;
}
die $@ if $@;
#cache it unless we're cleaning out each time
$Cache{$package}{mtime} = $mtime unless $delete;
}
eval {$package->handler;};
die $@ if $@;
delete_package($package) if $delete;
#take a look if you want
#print Devel::Symdump->rnew($package)->as_string, $/;
}
1;
__END__
/* persistent.c */
#include <EXTERN.h>
#include <perl.h>
/* 1 = clean out filename's symbol table after each request,
0 = don't
*/
#ifndef DO_CLEAN
#define DO_CLEAN 0
#endif
#define BUFFER_SIZE 1024
static PerlInterpreter *my_perl = NULL;
int
main(int argc, char **argv, char **env)
{
char *embedding[] = { "", "persistent.pl", NULL };
char *args[] = { "", DO_CLEAN, NULL };
char filename[BUFFER_SIZE];
int failing, exitstatus;
PERL_SYS_INIT3(&argc,&argv,&env);
if((my_perl = perl_alloc()) == NULL) {
fprintf(stderr, "no memory!");
exit(EXIT_FAILURE);
}
perl_construct(my_perl);
PL_origalen = 1; /* don't let $0 assignment update the
proctitle or embedding[0] */
failing = perl_parse(my_perl, NULL, 2, embedding, NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
if(!failing)
failing = perl_run(my_perl);
if(!failing) {
while(printf("Enter file name: ") &&
fgets(filename, BUFFER_SIZE, stdin)) {
filename[strlen(filename)-1] = '\0'; /* strip \n */
/* call the subroutine,
passing it the filename as an argument */
args[0] = filename;
call_argv("Embed::Persistent::eval_file",
G_DISCARD | G_EVAL, args);
/* check $@ */
if(SvTRUE(ERRSV))
fprintf(stderr, "eval error: %s\n", SvPV_nolen(ERRSV));
}
}
PL_perl_destruct_level = 0;
exitstatus = perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
exit(exitstatus);
} Теперь компилировать:
% cc -o persistent persistent.c \
`perl -MExtUtils::Embed -e ccopts -e ldopts` Вот пример файла скрипта:
#test.pl
my $string = "hello";
foo($string);
sub foo {
print "foo says: @_\n";
} Теперь запустить:
% persistent
Enter file name: test.pl
foo says: hello
Enter file name: test.pl
already compiled Embed::test_2epl->handler
foo says: hello
Enter file name: ^C Выполнение блоков END
Традиционно блоки END выполняются в конце perl_run. Это создаёт проблемы для приложений, которые никогда не вызывают perl_run. Начиная с perl 5.7.2, вы можете указать PL_exit_flags |= PERL_EXIT_DESTRUCT_END, чтобы получить новое поведение. Это также позволяет запускать блоки END, если perl_parse завершается с ошибкой, и perl_destruct вернёт код выхода.
Присвоения $0
Когда Perl-скрипт присваивает значение $0, среда выполнения Perl попытается отобразить это значение как имя программы, отслеживаемое командой «ps», обновив память, на которую указывает argv, переданный в perl_parse(), и также вызвав API-функции, такие как setproctitle(), где это доступно. Это поведение может быть неподходящим при встраивании Perl и может быть отключено путём присвоения значения 1 переменной PL_origalen до вызова perl_parse().
Например, пример persistent.c, скорее всего, завершится с ошибкой segmentation fault, если присвоение $0 выполняется, если присвоение PL_origalen = 1; удалено. Это происходит потому, что Perl попытается записать в постоянную память строк embedding[].
Поддержание нескольких экземпляров интерпретатора
Некоторые редкие приложения потребуют создания более одного интерпретатора во время сессии. Такое приложение может периодически решить высвободить все ресурсы, связанные с интерпретатором.
Программа должна позаботиться о том, чтобы это произошло *до* создания следующего интерпретатора. По умолчанию, если Perl не скомпилирован со специальными опциями, глобальная переменная PL_perl_destruct_level установлена в 0, так как дополнительная очистка обычно не нужна, когда программа создаёт только один интерпретатор за всё время своего существования.
Установка PL_perl_destruct_level в 1 обеспечивает полную чистоту:
while(1) {
...
/* reset global variables here with PL_perl_destruct_level = 1 */
PL_perl_destruct_level = 1;
perl_construct(my_perl);
...
/* clean and reset _everything_ during perl_destruct */
PL_perl_destruct_level = 1;
perl_destruct(my_perl);
perl_free(my_perl);
...
/* let's go do it again! */
} Когда вызывается perl_destruct(), синтаксическое дерево разбора и таблицы символов интерпретатора очищаются, и глобальные переменные сбрасываются. Второе присвоение PL_perl_destruct_level необходимо, потому что perl_construct сбрасывает его в значение 0.
Теперь предположим, что у нас запущено более одного экземпляра интерпретатора одновременно. Это возможно, но только если вы использовали опцию конфигурации -Dusemultiplicity или опции -Dusethreads -Duseithreads при компиляции Perl. По умолчанию включение одной из этих опций конфигурации устанавливает глобальную переменную каждого интерпретатора PL_perl_destruct_level в значение 1, чтобы обеспечить автоматическую тщательную очистку и корректную инициализацию переменных интерпретатора. Даже если вы не планируете запускать два или более интерпретаторов одновременно, но запускать их последовательно, как в приведённом выше примере, рекомендуется скомпилировать Perl с опцией -Dusemultiplicity, в противном случае некоторые переменные интерпретатора могут не быть правильно инициализированы между последовательными запусками, и ваше приложение может аварийно завершиться.
См. также "Thread-aware system interfaces" в perlxs.
Использование -Dusethreads -Duseithreads вместо -Dusemultiplicity более уместно, если вы планируете запускать несколько интерпретаторов параллельно в разных потоках, так как это позволяет поддерживать поддержку подключения к библиотекам потоков вашей системы с интерпретатором.
Давайте попробуем:
#include <EXTERN.h>
#include <perl.h>
/* we're going to embed two interpreters */
#define SAY_HELLO "-e", "print qq(Hi, I'm $^X\n)"
int main(int argc, char **argv, char **env)
{
PerlInterpreter *one_perl, *two_perl;
char *one_args[] = { "one_perl", SAY_HELLO, NULL };
char *two_args[] = { "two_perl", SAY_HELLO, NULL };
PERL_SYS_INIT3(&argc,&argv,&env);
one_perl = perl_alloc();
two_perl = perl_alloc();
PERL_SET_CONTEXT(one_perl);
perl_construct(one_perl);
PERL_SET_CONTEXT(two_perl);
perl_construct(two_perl);
PERL_SET_CONTEXT(one_perl);
perl_parse(one_perl, NULL, 3, one_args, (char **)NULL);
PERL_SET_CONTEXT(two_perl);
perl_parse(two_perl, NULL, 3, two_args, (char **)NULL);
PERL_SET_CONTEXT(one_perl);
perl_run(one_perl);
PERL_SET_CONTEXT(two_perl);
perl_run(two_perl);
PERL_SET_CONTEXT(one_perl);
perl_destruct(one_perl);
PERL_SET_CONTEXT(two_perl);
perl_destruct(two_perl);
PERL_SET_CONTEXT(one_perl);
perl_free(one_perl);
PERL_SET_CONTEXT(two_perl);
perl_free(two_perl);
PERL_SYS_TERM();
exit(EXIT_SUCCESS);
} Обратите внимание на вызовы PERL_SET_CONTEXT(). Они необходимы для инициализации глобального состояния, которое отслеживает, какой интерпретатор является «текущим» в конкретном процессе или потоке, в котором он может выполняться. Он всегда должен использоваться, если у вас более одного интерпретатора и вы делаете вызовы Perl API для обоих интерпретаторов вперемешку.
PERL_SET_CONTEXT(interp) также должен вызываться всякий раз, когда interp используется потоком, который его не создавал (используя perl_alloc() или более экзотический perl_clone()).
Компилировать как обычно:
% cc -o multiplicity multiplicity.c \
`perl -MExtUtils::Embed -e ccopts -e ldopts` Запустить, запустить:
% multiplicity
Hi, I'm one_perl
Hi, I'm two_perl Использование Perl-модулей, которые сами используют C-библиотеки, из вашей C-программы
Если вы играли с примерами выше и пытались встроить скрипт, который использует use() Perl-модуль (например, Socket), который сам использует C или C++ библиотеку, то, вероятно, произошло следующее:
Can't load module Socket, dynamic loading not available in this perl.
(You may need to build a new perl executable which either supports
dynamic loading or has the Socket module statically linked into it.) В чём проблема?
Ваш интерпретатор не знает, как общаться с этими расширениями самостоятельно. Немного «клея» поможет. До сих пор вы вызывали perl_parse(), передавая NULL в качестве второго аргумента:
perl_parse(my_perl, NULL, argc, my_argv, NULL); Вот где код «клея» может быть вставлен, чтобы установить начальный контакт между Perl и связанными C/C++ процедурами. Давайте посмотрим на некоторые фрагменты perlmain.c, чтобы увидеть, как это делает Perl:
static void xs_init (pTHX);
EXTERN_C void boot_DynaLoader (pTHX_ CV* cv);
EXTERN_C void boot_Socket (pTHX_ CV* cv);
EXTERN_C void
xs_init(pTHX)
{
char *file = __FILE__;
/* DynaLoader is a special case */
newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
newXS("Socket::bootstrap", boot_Socket, file);
} Проще говоря: для каждого расширения, связанного с вашим исполняемым файлом Perl (определяется во время его первоначальной настройки на вашем компьютере или при добавлении нового расширения), создаётся подпрограмма Perl для включения процедур расширения. Обычно эта подпрограмма называется Module::bootstrap() и вызывается при использовании use Module. В свою очередь, это подключается к XSUB, boot_Module, который создаёт Perl-аналог для каждого XSUB расширения. Не беспокойтесь об этой части; оставьте это xsubpp и авторам расширения. Если ваше расширение загружается динамически, DynaLoader создаёт Module::bootstrap() для вас на лету. Фактически, если у вас есть работающий DynaLoader, то вряд ли есть необходимость в статической линковке других расширений.
После того, как вы получили этот код, поместите его во второй аргумент perl_parse():
perl_parse(my_perl, xs_init, argc, my_argv, NULL); Затем выполните компиляцию:
% cc -o interp interp.c `perl -MExtUtils::Embed -e ccopts -e ldopts`
% interp
use Socket;
use SomeDynamicallyLoadedModule;
print "Now I can use extensions!\n"' ExtUtils::Embed также может автоматизировать написание кода склейки xs_init.
% perl -MExtUtils::Embed -e xsinit -- -o perlxsi.c
% cc -c perlxsi.c `perl -MExtUtils::Embed -e ccopts`
% cc -c interp.c `perl -MExtUtils::Embed -e ccopts`
% cc -o interp perlxsi.o interp.o `perl -MExtUtils::Embed -e ldopts` См. perlxs, perlguts и perlapi для получения дополнительной информации.
Использование встроенного Perl с POSIX-локали
(См. perllocale для получения информации об этих локалиях.) Когда интерпретатор Perl обычно запускается, он сообщает системе, что хочет использовать системную локаль по умолчанию. Это часто, но не обязательно, локаль "C" или "POSIX". Если "use locale" внутри кода Perl отсутствует, это в основном не оказывает влияния (но см. "Не в рамках действия "use locale"" в perllocale). Также нет проблем, если локаль, которую вы хотите использовать во встроенном perl, совпадает с системной локалью по умолчанию. Однако это не сработает, если вы настроили и хотите использовать локаль, которая не является системной локалью по умолчанию. Начиная с Perl v5.20, вы можете указать встроенному интерпретатору Perl, что локаль уже должным образом настроена и пропустить свою обычную инициализацию. Он пропускает инициализацию, если переменная среды PERL_SKIP_LOCALE_INIT установлена (даже если она установлена в 0 или ""). Perl, обладающий этой возможностью, определит символ препроцессора C HAS_SKIP_LOCALE_INIT. Это позволяет коду, который должен работать с несколькими версиями Perl, выполнить какой-либо обходной путь при столкновении со старой версией Perl.
Если ваша программа использует многопоточную функциональность локали POSIX 2008, вы должны переключиться на глобальную локаль и должным образом настроить её перед запуском интерпретатора Perl. Затем он должным образом переключится на использование потокобезопасных функций.
Скрытие Perl_
Если вы полностью скроете короткие формы публичного API Perl, добавьте -DPERL_NO_SHORT_NAMES в флаги компиляции. Это означает, что, например, вместо записи
warn("%d bottles of beer on the wall", bottlecount); вам придётся написать явную полную форму
Perl_warn(aTHX_ "%d bottles of beer on the wall", bottlecount); (См. "Обзор и PERL_IMPLICIT_CONTEXT" в perlguts для объяснения aTHX_. ) Скрытие коротких форм очень полезно для избегания всех видов неприятных (препроцессора C или иного) конфликтов с другими программными пакетами (Perl определяет около 2400 API с этими короткими именами, плюс-минус несколько сотен, поэтому определённо есть место для конфликта.)
MORAL
Иногда можно писать более быстрый код на C, но всегда можно писать код быстрее на Perl. Поскольку вы можете использовать каждый из них с другого, комбинируйте их по своему желанию.
АВТОР
Jon Orwant <orwant@media.mit.edu> и Doug MacEachern <dougm@covalent.net>, с небольшими contributions от Tim Bunce, Tom Christiansen, Guy Decoux, Hallvard Furuseth, Dov Grobgeld и Ilya Zakharevich.
У Doug MacEachern есть статья о встраивании в 1-ом выпуске 4-й части The Perl Journal ( http://www.tpj.com/ ). Doug также является разработчиком наиболее широко используемого встраивания Perl: системы mod_perl (perl.apache.org), которая встраивает Perl в веб-сервер Apache. Oracle, Binary Evolution, ActiveState и nsapi_perl Бена Сагарса использовали эту модель для плагинов Perl в Oracle, Netscape и Internet Information Server.
АВТОРСКИЕ ПРАВА
Авторские права (C) 1995, 1996, 1997, 1998 Doug MacEachern и Jon Orwant. Все права защищены.
Этот документ можно распространять на тех же условиях, что и сам Perl.
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlembed