Spec-Zone.ru › Perl 5.34

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?

Прочитайте о 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-дистрибуцию, вы можете получить его с 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. Также возможно, и в большинстве случаев это более эффективная стратегия, извлечь возвращаемое значение из 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–2021 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.34.0/perlembed

Spec-Zone.ru

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