Spec-Zone.ru › Perl 5.36

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-завершённый список args , переданный call_argv. Для других типов данных или для проверки возвращаемых значений вам нужно будет манипулировать Perl-стеком. Это показано в "Работа с Perl-стеком из вашей C-программы".

Вычисление оператора Perl из вашей C-программы

Perl предоставляет две функции API для вычисления фрагментов кода Perl. Это "eval_sv" в perlapi и "eval_pv" в perlapi.

Можно утверждать, что это единственные необходимые процедуры для выполнения фрагментов Perl-кода из вашей C-программы. Ваш код может быть произвольной длины; он может содержать несколько операторов; он может использовать "use" в perlfunc, "require" в perlfunc и "do" в perlfunc для включения внешних Perl-файлов.

eval_pv позволяет нам вычислить отдельные Perl-строки, а затем извлечь переменные для преобразования в C-типы. Следующая программа, string.c, выполняет три Perl-строки, извлекая int из первой, float из второй и char * из третьей.

#include <EXTERN.h>
#include <perl.h>

static PerlInterpreter *my_perl;

main (int argc, char **argv, char **env)
{
    char *embedding[] = { "", "-e", "0", NULL };

    PERL_SYS_INIT3(&argc,&argv,&env);
    my_perl = perl_alloc();
    perl_construct( my_perl );

    perl_parse(my_perl, NULL, 3, embedding, NULL);
    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
    perl_run(my_perl);

    /** Treat $a as an integer **/
    eval_pv("$a = 3; $a **= 2", TRUE);
    printf("a = %d\n", SvIV(get_sv("a", 0)));

    /** Treat $a as a float **/
    eval_pv("$a = 3.14; $a **= 2", TRUE);
    printf("a = %f\n", SvNV(get_sv("a", 0)));

    /** Treat $a as a string **/
    eval_pv(
      "$a = 'rekcaH lreP rehtonA tsuJ'; $a = reverse($a);", TRUE);
    printf("a = %s\n", SvPV_nolen(get_sv("a", 0)));

    perl_destruct(my_perl);
    perl_free(my_perl);
    PERL_SYS_TERM();
}

Все эти странные функции с sv в своих именах помогают преобразовать Perl-скаляры в C-типы. Они описаны в perlguts и perlapi.

Если вы скомпилируете и запустите string.c, вы увидите результаты использования SvIV() для создания int, SvNV() для создания float, и SvPV() для создания строки:

a = 9
a = 9.859600
a = Just Another Perl Hacker

В приведенном выше примере мы создали глобальную переменную для временного хранения вычисленного значения нашего выражения, которое было обработано с помощью eval. Также возможно, и в большинстве случаев это лучшая стратегия, извлечь возвращаемое значение из eval_pv(). Пример:

...
SV *val = eval_pv("reverse 'rekcaH lreP rehtonA tsuJ'", TRUE);
printf("%s\n", SvPV_nolen(val));
...

Таким образом, мы избегаем загрязнения пространства имен, не создавая глобальных переменных, и упростили наш код.

Выполнение соответствий и подстановок шаблонов Perl из вашей C-программы

Функция eval_sv() позволяет нам оценивать строки кода Perl, поэтому мы можем определить некоторые функции, которые используют её для «специализации» на соответствиях и подстановках: match(), substitute() и matches().

I32 match(SV *string, char *pattern);

Для заданной строки и шаблона (например, m/clasp/ или /\b\w*\b/, которые в вашей C-программе могут быть представлены как "/\\b\\w*\\b/") функция match() возвращает 1, если строка соответствует шаблону, и 0 в противном случае.

int substitute(SV **string, char *pattern);

Для указателя на SV и операции =~ (например, s/bob/robert/g или tr[A-Z][a-z]) функция substitute() изменяет строку в SV в соответствии с операцией, возвращая количество произведённых подстановок.

SSize_t matches(SV *string, char *pattern, AV **matches);

Для заданного SV, шаблона и указателя на пустой AV, функция matches() оценивает $string =~ $pattern в контексте списка, заполняет matches элементами массива и возвращает количество найденных соответствий.

Вот пример программы match.c, которая использует все три функции (длинные строки здесь были разбиты):

#include <EXTERN.h>
#include <perl.h>

static PerlInterpreter *my_perl;

/** my_eval_sv(code, error_check)
** kinda like eval_sv(),
** but we pop the return value off the stack
**/
SV* my_eval_sv(SV *sv, I32 croak_on_error)
{
    dSP;
    SV* retval;


    PUSHMARK(SP);
    eval_sv(sv, G_SCALAR);

    SPAGAIN;
    retval = POPs;
    PUTBACK;

    if (croak_on_error && SvTRUE(ERRSV))
       croak_sv(ERRSV);

    return retval;
}

/** match(string, pattern)
**
** Used for matches in a scalar context.
**
** Returns 1 if the match was successful; 0 otherwise.
**/

I32 match(SV *string, char *pattern)
{
    SV *command = newSV(0), *retval;

    sv_setpvf(command, "my $string = '%s'; $string =~ %s",
             SvPV_nolen(string), pattern);

    retval = my_eval_sv(command, TRUE);
    SvREFCNT_dec(command);

    return SvIV(retval);
}

/** substitute(string, pattern)
**
** Used for =~ operations that
** modify their left-hand side (s/// and tr///)
**
** Returns the number of successful matches, and
** modifies the input string if there were any.
**/

I32 substitute(SV **string, char *pattern)
{
    SV *command = newSV(0), *retval;

    sv_setpvf(command, "$string = '%s'; ($string =~ %s)",
             SvPV_nolen(*string), pattern);

    retval = my_eval_sv(command, TRUE);
    SvREFCNT_dec(command);

    *string = get_sv("string", 0);
    return SvIV(retval);
}

/** matches(string, pattern, matches)
**
** Used for matches in a list context.
**
** Returns the number of matches,
** and fills in **matches with the matching substrings
**/

SSize_t matches(SV *string, char *pattern, AV **match_list)
{
    SV *command = newSV(0);
    SSize_t num_matches;

    sv_setpvf(command, "my $string = '%s'; @array = ($string =~ %s)",
             SvPV_nolen(string), pattern);

    my_eval_sv(command, TRUE);
    SvREFCNT_dec(command);

    *match_list = get_av("array", 0);
    num_matches = av_top_index(*match_list) + 1;

    return num_matches;
}

main (int argc, char **argv, char **env)
{
    char *embedding[] = { "", "-e", "0", NULL };
    AV *match_list;
    I32 num_matches, i;
    SV *text;

    PERL_SYS_INIT3(&argc,&argv,&env);
    my_perl = perl_alloc();
    perl_construct(my_perl);
    perl_parse(my_perl, NULL, 3, embedding, NULL);
    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;

    text = newSV(0);
    sv_setpv(text, "When he is at a convenience store and the "
       "bill comes to some amount like 76 cents, Maynard is "
       "aware that there is something he *should* do, something "
       "that will enable him to get back a quarter, but he has "
       "no idea *what*.  He fumbles through his red squeezey "
       "changepurse and gives the boy three extra pennies with "
       "his dollar, hoping that he might luck into the correct "
       "amount.  The boy gives him back two of his own pennies "
       "and then the big shiny quarter that is his prize. "
       "-RICHH");

    if (match(text, "m/quarter/")) /** Does text contain 'quarter'? **/
       printf("match: Text contains the word 'quarter'.\n\n");
    else
       printf("match: Text doesn't contain the word 'quarter'.\n\n");

    if (match(text, "m/eighth/")) /** Does text contain 'eighth'? **/
       printf("match: Text contains the word 'eighth'.\n\n");
    else
       printf("match: Text doesn't contain the word 'eighth'.\n\n");

    /** Match all occurrences of /wi../ **/
    num_matches = matches(text, "m/(wi..)/g", &match_list);
    printf("matches: m/(wi..)/g found %d matches...\n", num_matches);

    for (i = 0; i < num_matches; i++)
        printf("match: %s\n",
                 SvPV_nolen(*av_fetch(match_list, i, FALSE)));
    printf("\n");

    /** Remove all vowels from text **/
    num_matches = substitute(&text, "s/[aeiou]//gi");
    if (num_matches) {
       printf("substitute: s/[aeiou]//gi...%lu substitutions made.\n",
              (unsigned long)num_matches);
       printf("Now text is: %s\n\n", SvPV_nolen(text));
    }

    /** Attempt a substitution **/
    if (!substitute(&text, "s/Perl/C/")) {
       printf("substitute: s/Perl/C...No substitution made.\n\n");
    }

    SvREFCNT_dec(text);
    PL_perl_destruct_level = 1;
    perl_destruct(my_perl);
    perl_free(my_perl);
    PERL_SYS_TERM();
}

что приводит к выводу (снова, длинные строки здесь были разбиты):

match: Text contains the word 'quarter'.

match: Text doesn't contain the word 'eighth'.

matches: m/(wi..)/g found 2 matches...
match: will
match: with

substitute: s/[aeiou]//gi...139 substitutions made.
Now text is: Whn h s t  cnvnnc str nd th bll cms t sm mnt lk 76 cnts,
Mynrd s wr tht thr s smthng h *shld* d, smthng tht wll nbl hm t gt
bck qrtr, bt h hs n d *wht*.  H fmbls thrgh hs rd sqzy chngprs nd
gvs th by thr xtr pnns wth hs dllr, hpng tht h mght lck nt th crrct
mnt.  Th by gvs hm bck tw f hs wn pnns nd thn th bg shny qrtr tht s
hs prz. -RCHH

substitute: s/Perl/C...No substitution made.

Работа с Perl-стеком из вашей C-программы

При попытке объяснить стеки, большинство учебников по информатике бормочут что-то о пружинных колоннах кафетерийских тарелок: последний элемент, который вы поместили в стек, — первый, который вы извлекаете. Этого достаточно для наших целей: ваша C-программа поместит некоторые аргументы на «Perl-стек», закроет глаза, пока произойдёт какое-то волшебство, а затем извлечёт результаты — возвращаемое значение вашей Perl-подпрограммы — со стека.

Сначала вам нужно будет узнать, как преобразовать типы C в типы Perl, используя newSViv(), sv_setnv(), newAV() и все их аналоги. Они описаны в perlguts и perlapi.

Затем вам нужно будет узнать, как управлять Perl-стеком. Это описано в perlcall.

После того, как вы это поймёте, встраивание Perl в C станет лёгким.

Поскольку в C нет встроенной функции для возведения в степень целых чисел, давайте сделаем оператор ** Perl доступным для него (это менее полезно, чем кажется, потому что Perl реализует ** с помощью функции pow() C). Сначала я создам функцию возведения в степень в виде заглушки в power.pl:

sub expo {
    my ($a, $b) = @_;
    return $a ** $b;
}

Теперь я создам C-программу power.c с функцией PerlPower(), которая содержит все необходимые perlguts для помещения двух аргументов в expo() и извлечения возвращаемого значения. Глубокий вдох…

#include <EXTERN.h>
#include <perl.h>

static PerlInterpreter *my_perl;

static void
PerlPower(int a, int b)
{
  dSP;                            /* initialize stack pointer      */
  ENTER;                          /* everything created after here */
  SAVETMPS;                       /* ...is a temporary variable.   */
  PUSHMARK(SP);                   /* remember the stack pointer    */
  XPUSHs(sv_2mortal(newSViv(a))); /* push the base onto the stack  */
  XPUSHs(sv_2mortal(newSViv(b))); /* push the exponent onto stack  */
  PUTBACK;                      /* make local stack pointer global */
  call_pv("expo", G_SCALAR);      /* call the function             */
  SPAGAIN;                        /* refresh stack pointer         */
                                /* pop the return value from stack */
  printf ("%d to the %dth power is %d.\n", a, b, POPi);
  PUTBACK;
  FREETMPS;                       /* free that return value        */
  LEAVE;                       /* ...and the XPUSHed "mortal" args.*/
}

int main (int argc, char **argv, char **env)
{
  char *my_argv[] = { "", "power.pl", NULL };

  PERL_SYS_INIT3(&argc,&argv,&env);
  my_perl = perl_alloc();
  perl_construct( my_perl );

  perl_parse(my_perl, NULL, 2, my_argv, (char **)NULL);
  PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
  perl_run(my_perl);

  PerlPower(3, 4);                      /*** Compute 3 ** 4 ***/

  perl_destruct(my_perl);
  perl_free(my_perl);
  PERL_SYS_TERM();
  exit(EXIT_SUCCESS);
}

Компилировать и запускать:

% cc -o power power.c `perl -MExtUtils::Embed -e ccopts -e ldopts`

% power
3 to the 4th power is 81.

Поддержание постоянного интерпретатора

При разработке интерактивных и/или потенциально долгосрочных приложений, имеет смысл поддерживать постоянный интерпретатор вместо многократного выделения и создания нового интерпретатора. Основная причина — скорость: Perl будет загружен в память только один раз.

Однако нужно проявлять больше осторожности с пространством имен и областью видимости переменных при использовании постоянного интерпретатора. В предыдущих примерах мы использовали глобальные переменные в стандартном пакете main. Мы точно знали, какой код будет выполняться, и предположили, что мы можем избежать коллизий переменных и чрезмерного роста таблицы символов.

Предположим, ваше приложение — это сервер, который время от времени будет запускать Perl-код из некоторого произвольного файла. Ваш сервер не может знать, какой код будет запущен. Очень опасно.

Если файл подключается с помощью perl_parse(), компилируется в только что созданный интерпретатор и впоследствии очищается с помощью perl_destruct() , вы защищены от большинства проблем с пространством имен.

Один из способов избежать коллизий пространства имен в этом сценарии — перевести имя файла в гарантированно уникальное имя пакета, а затем скомпилировать код в этот пакет, используя "eval" в perlfunc. В приведённом ниже примере каждый файл будет скомпилирован только один раз. Либо приложение может выбрать очистку таблицы символов, связанной с файлом, после того, как она больше не понадобится. Используя "call_argv" в perlapi, мы вызовем подпрограмму Embed::Persistent::eval_file, которая находится в файле persistent.pl, и передадим имя файла и флаг очистки/кэширования в качестве аргументов.

Обратите внимание, что процесс будет продолжать расти для каждого используемого файла. Кроме того, могут быть AUTOLOAD-подпрограммы и другие условия, которые приводят к росту таблицы символов Perl. Возможно, вам захочется добавить логику, которая отслеживает размер процесса или перезапускает его после определённого количества запросов, чтобы свести к минимуму потребление памяти. Также вам следует использовать "my" в perlfunc для ограничения области видимости переменных, где это возможно.

package Embed::Persistent;
#persistent.pl

use strict;
our %Cache;
use Symbol qw(delete_package);

sub valid_package_name {
    my($string) = @_;
    $string =~ s/([^A-Za-z0-9\/])/sprintf("_%2x",unpack("C",$1))/eg;
    # second pass only for words starting with a digit
    $string =~ s|/(\d)|sprintf("/_%2x",unpack("C",$1))|eg;

    # Dress it up as a real package name
    $string =~ s|/|::|g;
    return "Embed" . $string;
}

sub eval_file {
    my($filename, $delete) = @_;
    my $package = valid_package_name($filename);
    my $mtime = -M $filename;
    if(defined $Cache{$package}{mtime}
       &&
       $Cache{$package}{mtime} <= $mtime)
    {
       # we have compiled this subroutine already,
       # it has not been updated on disk, nothing left to do
       print STDERR "already compiled $package->handler\n";
    }
    else {
       local *FH;
       open FH, $filename or die "open '$filename' $!";
       local($/) = undef;
       my $sub = <FH>;
       close FH;

       #wrap the code into a subroutine inside our unique package
       my $eval = qq{package $package; sub handler { $sub; }};
       {
           # hide our variables within this block
           my($filename,$mtime,$package,$sub);
           eval $eval;
       }
       die $@ if $@;

       #cache it unless we're cleaning out each time
       $Cache{$package}{mtime} = $mtime unless $delete;
    }

    eval {$package->handler;};
    die $@ if $@;

    delete_package($package) if $delete;

    #take a look if you want
    #print Devel::Symdump->rnew($package)->as_string, $/;
}

1;

__END__

/* persistent.c */
#include <EXTERN.h>
#include <perl.h>

/* 1 = clean out filename's symbol table after each request,
   0 = don't
*/
#ifndef DO_CLEAN
#define DO_CLEAN 0
#endif

#define BUFFER_SIZE 1024

static PerlInterpreter *my_perl = NULL;

int
main(int argc, char **argv, char **env)
{
    char *embedding[] = { "", "persistent.pl", NULL };
    char *args[] = { "", DO_CLEAN, NULL };
    char filename[BUFFER_SIZE];
    int failing, exitstatus;

    PERL_SYS_INIT3(&argc,&argv,&env);
    if((my_perl = perl_alloc()) == NULL) {
       fprintf(stderr, "no memory!");
       exit(EXIT_FAILURE);
    }
    perl_construct(my_perl);

    PL_origalen = 1; /* don't let $0 assignment update the
                        proctitle or embedding[0] */
    failing = perl_parse(my_perl, NULL, 2, embedding, NULL);
    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
    if(!failing)
       failing = perl_run(my_perl);
    if(!failing) {
       while(printf("Enter file name: ") &&
             fgets(filename, BUFFER_SIZE, stdin)) {

           filename[strlen(filename)-1] = '\0'; /* strip \n */
           /* call the subroutine,
                    passing it the filename as an argument */
           args[0] = filename;
           call_argv("Embed::Persistent::eval_file",
                          G_DISCARD | G_EVAL, args);

           /* check $@ */
           if(SvTRUE(ERRSV))
               fprintf(stderr, "eval error: %s\n", SvPV_nolen(ERRSV));
       }
    }

    PL_perl_destruct_level = 0;
    exitstatus = perl_destruct(my_perl);
    perl_free(my_perl);
    PERL_SYS_TERM();
    exit(exitstatus);
}

Теперь компилируйте:

% cc -o persistent persistent.c \
       `perl -MExtUtils::Embed -e ccopts -e ldopts`

Вот пример файла скрипта:

#test.pl
my $string = "hello";
foo($string);

sub foo {
    print "foo says: @_\n";
}

Теперь запустите:

% persistent
Enter file name: test.pl
foo says: hello
Enter file name: test.pl
already compiled Embed::test_2epl->handler
foo says: hello
Enter file name: ^C

Выполнение блоков END

Традиционно блоки END выполняются в конце perl_run. Это вызывает проблемы для приложений, которые никогда не вызывают perl_run. Начиная с perl 5.7.2 вы можете указать PL_exit_flags |= PERL_EXIT_DESTRUCT_END, чтобы получить новое поведение. Это также позволяет запускать блоки END, если perl_parse терпит неудачу, и perl_destruct вернёт значение завершения.

Присваивание $0

Когда Perl-скрипт присваивает значение $0, система попытается отобразить это значение в качестве имени программы, отображаемой в «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.

Теперь предположим, что у нас работает более одного экземпляра интерпретатора одновременно. Это возможно, но только если вы использовали опцию Configure -Dusemultiplicity или опции -Dusethreads -Duseithreads при компиляции Perl. По умолчанию включение одной из этих опций Configure устанавливает глобальную переменную каждого интерпретатора PL_perl_destruct_level в 1, чтобы обеспечить тщательную очистку и правильную инициализацию переменных интерпретатора. Даже если вы не планируете запускать два или более интерпретаторов одновременно, а только последовательно, как в приведенном выше примере, рекомендуется скомпилировать Perl с опцией -Dusemultiplicity, иначе некоторые переменные интерпретатора могут не инициализироваться должным образом между последовательными запусками, и ваше приложение может выйти из строя.

См. также "Thread-aware system interfaces" в perlxs.

Использование -Dusethreads -Duseithreads вместо -Dusemultiplicity более уместно, если вы планируете запустить несколько интерпретаторов одновременно в разных потоках, так как это позволяет использовать библиотеки потоков вашей системы в рамках интерпретатора.

Давайте попробуем:

#include <EXTERN.h>
#include <perl.h>

/* we're going to embed two interpreters */

#define SAY_HELLO "-e", "print qq(Hi, I'm $^X\n)"

int main(int argc, char **argv, char **env)
{
    PerlInterpreter *one_perl, *two_perl;
    char *one_args[] = { "one_perl", SAY_HELLO, NULL };
    char *two_args[] = { "two_perl", SAY_HELLO, NULL };

    PERL_SYS_INIT3(&argc,&argv,&env);
    one_perl = perl_alloc();
    two_perl = perl_alloc();

    PERL_SET_CONTEXT(one_perl);
    perl_construct(one_perl);
    PERL_SET_CONTEXT(two_perl);
    perl_construct(two_perl);

    PERL_SET_CONTEXT(one_perl);
    perl_parse(one_perl, NULL, 3, one_args, (char **)NULL);
    PERL_SET_CONTEXT(two_perl);
    perl_parse(two_perl, NULL, 3, two_args, (char **)NULL);

    PERL_SET_CONTEXT(one_perl);
    perl_run(one_perl);
    PERL_SET_CONTEXT(two_perl);
    perl_run(two_perl);

    PERL_SET_CONTEXT(one_perl);
    perl_destruct(one_perl);
    PERL_SET_CONTEXT(two_perl);
    perl_destruct(two_perl);

    PERL_SET_CONTEXT(one_perl);
    perl_free(one_perl);
    PERL_SET_CONTEXT(two_perl);
    perl_free(two_perl);
    PERL_SYS_TERM();
    exit(EXIT_SUCCESS);
}

Обратите внимание на вызовы PERL_SET_CONTEXT(). Они необходимы для инициализации глобального состояния, которое отслеживает, какой интерпретатор является «текущим» в конкретном процессе или потоке, который его выполняет. Это всегда должно использоваться, если у вас более одного интерпретатора и вы вызываете 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-программы

Если вы играли с примерами выше и пытались встроить скрипт, который использует 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–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.36.0/perlembed

Spec-Zone.ru

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