Spec-Zone.ru › Perl 5.38

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?

Прочитайте perlxstut, perlxs, h2xs, perlguts и perlapi.

Использовать программу 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, вы можете получить его с https://metacpan.org/pod/ExtUtils::Embed (Если эта документация пришла из вашей дистрибуции 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-terminated 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_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", обновляя память, указанную аргументом, переданным в 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.

Теперь предположим, что у нас запущено более одного экземпляра интерпретатора одновременно. Это возможно, но только если вы использовали опцию Configure -Dusemultiplicity или параметры -Dusethreads -Duseithreads при компиляции Perl. По умолчанию включение одной из этих опций Configure устанавливает глобальную переменную каждого интерпретатора 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(). Они необходимы для инициализации глобального состояния, которое отслеживает, какой интерпретатор является «текущим» в данном процессе или потоке, который его может запускать. Его всегда следует использовать, если у вас более одного интерпретатора и вы делаете вызовы API Perl для обоих интерпретаторов чередуясь.

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);

(См. "Обзор и MULTIPLICITY" в perlguts для объяснения aTHX_. ) Скрытие коротких форм очень полезно для предотвращения всех видов неприятных (препроцессора C или иного) конфликтов с другими программными пакетами (Perl определяет около 2400 API с этими короткими именами, плюс-минус несколько сотен, так что определенно есть место для конфликта.)

MORAL

Иногда вы можете писать более быстрый код на C, но вы всегда можете писать код быстрее на Perl. Поскольку вы можете использовать каждый из них из другого, комбинируйте их по своему желанию.

АВТОР

Jon Orwant <orwant@media.mit.edu> и Doug MacEachern <dougm@covalent.net>, с небольшими вкладами от 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–2023 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.38.0/perlembed

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API