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?
-
Прочитайте о back-quotes и о
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, который передаётся в 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 реализует ** с помощью функции C pow()). Сначала я создам заглушку функции возведения в степень в 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, скорее всего, вызовет сегментную ошибку, когда $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, в противном случае некоторые переменные интерпретатора могут не инициализироваться должным образом между последовательными запусками, и ваше приложение может завершиться с ошибкой.
См. также "Интерфейсы систем с поддержкой потоков" в 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(interp). Они необходимы для инициализации глобального состояния, отслеживающего, какой интерпретатор является «текущим» в конкретном процессе или потоке, который может его запускать. Он всегда должен использоваться, если у вас есть более одного интерпретатора и вы делаете вызовы 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 с такими короткими именами, с небольшими вариациями, так что определённо есть возможность конфликта.)
МОРАЛЬ
Иногда вы можете писать более быстрый код на 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 Бена Сугара использовали эту модель для Oracle, Netscape и Perl-плагинов 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.30.3/perlembed