perlcall
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- ФУНКЦИИ CALL_
- ЗНАЧЕНИЯ ФЛАГОВ
- ПРИМЕРЫ
- Без параметров, ничего не возвращается
- Передача параметров
- Возвращение скаляра
- Возвращение списка значений
- Возвращение списка в скалярном контексте
- Возвращение данных из Perl через список параметров
- Использование G_EVAL
- Использование G_KEEPERR
- Использование call_sv
- Использование call_argv
- Использование call_method
- Использование GIMME_V
- Использование Perl для освобождения временных объектов
- Стратегии хранения информации о контексте обратного вызова
- Альтернативное управление стеком
- Создание и вызов анонимной подпрограммы в C
- ЛЕГКОВЕСНЫЕ ОБРАТНЫЕ ВЫЗОВЫ
- СМОТРИТЕ ТАКЖЕ
- АВТОР
- ДАТА
ИМЯ
perlcall - Соглашения Perl для вызовов из C
ОПИСАНИЕ
Цель данного документа — показать, как вызывать подпрограммы Perl непосредственно из C, то есть как писать обратные вызовы.
Помимо обсуждения интерфейса C, предоставляемого Perl для написания обратных вызовов, в документе представлены примеры работы с интерфейсом на практике. Кроме того, рассматриваются некоторые методы программирования обратных вызовов.
Примеры, где необходимы обратные вызовы:
-
Обработчик ошибок
Вы создали интерфейс XSUB к API приложения на C.
Довольно распространенной функцией в приложениях является возможность определения функции C, которая будет вызываться всякий раз, когда происходит что-то плохое. Мы хотели бы иметь возможность указать подпрограмму Perl, которая будет вызываться вместо неё.
-
Программа с обработкой событий
Классический пример использования обратных вызовов — написание программы с обработкой событий, например, для приложения X11. В этом случае вы регистрируете функции, которые будут вызываться при возникновении определённых событий, например, при нажатии кнопки мыши, перемещении курсора в окно или выборе пункта меню.
Хотя описанные здесь методы применимы и при встраивании Perl в программу C, это не основная цель данного документа. Существуют и другие моменты, которые необходимо учитывать, специфичные для встраивания Perl. Подробности по встраиванию Perl в C см. в документе perlembed.
Прежде чем начать изучать этот документ, рекомендуется ознакомиться с документами perlxs и perlguts.
ФУНКЦИИ CALL_
Хотя всё это проще объяснить на примерах, сначала нужно знать несколько важных определений.
Perl предоставляет ряд функций C, позволяющих вызывать подпрограммы Perl. Это
I32 call_sv(SV* sv, I32 flags);
I32 call_pv(char *subname, I32 flags);
I32 call_method(char *methname, I32 flags);
I32 call_argv(char *subname, I32 flags, char **argv); Ключевой функцией является call_sv. Все остальные функции — это простые обёртки, которые упрощают вызов подпрограмм Perl в особых случаях. В конечном итоге все они вызовут call_sv для вызова подпрограммы Perl.
Все функции call_* имеют параметр flags , который используется для передачи битовой маски опций в Perl. Эта битовая маска работает одинаково для каждой из функций. Доступные настройки в битовой маске обсуждаются в разделе "ЗНАЧЕНИЯ ФЛАГОВ".
Теперь каждая из функций будет обсуждаться по отдельности.
- call_sv
-
call_sv принимает два параметра. Первый,
sv, является SV*. Это позволяет указать подпрограмму Perl для вызова, либо как строку C (которая предварительно преобразована в SV), либо ссылку на подпрограмму. В разделе «Использование call_sv» показано, как можно использовать call_sv. - call_pv
-
Функция call_pv похожа на call_sv, за исключением того, что её первый параметр должен быть C char*, который идентифицирует подпрограмму Perl, которую нужно вызвать, например,
call_pv("fred", 0). Если подпрограмма, которую нужно вызвать, находится в другом пакете, просто добавьте имя пакета в строку, например,"pkg::fred". - call_method
-
Функция call_method используется для вызова метода из класса Perl. Параметр
methnameсоответствует имени вызываемого метода. Обратите внимание, что класс, к которому принадлежит метод, передаётся в стек Perl, а не в список параметров. Этот класс может быть либо именем класса (для статического метода), либо ссылкой на объект (для виртуального метода). Подробнее о статических и виртуальных методах см. в perlobj, а пример использования call_method см. в «Использование call_method». - call_argv
-
call_argv вызывает подпрограмму Perl, указанную строкой C в параметре
subname. Она также принимает обычный параметрflags. Последний параметр,argv, представляет собой список строк C с нулевым завершением, которые будут переданы в качестве параметров подпрограмме Perl. См. «Использование call_argv».
Все функции возвращают целое число. Это количество элементов, возвращенных подпрограммой Perl. Фактические элементы, возвращенные подпрограммой, хранятся в стеке Perl.
Как общее правило, вы всегда должны проверять возвращаемое значение этих функций. Даже если вы ожидаете, что подпрограмма Perl вернёт определённое количество значений, ничего не мешает кому-то сделать что-то неожиданное — будьте готовы.
ЗНАЧЕНИЯ ФЛАГОВ
Параметр flags во всех функциях call_* представляет собой одно из значений G_VOID, G_SCALAR или G_ARRAY, которые указывают контекст вызова, объединяемый поразрядно с любой комбинацией других символов G_*, определённых ниже.
G_VOID
Вызывает подпрограмму Perl в контексте void.
Этот флаг имеет два эффекта:
-
Он указывает подпрограмме, что она выполняется в контексте void (если она выполняет wantarray, результат будет неопределённым).
-
Он гарантирует, что из подпрограммы ничего не возвращается.
Возвращаемое значение функции call_* указывает количество элементов, возвращенных подпрограммой Perl — в этом случае оно будет равно 0.
G_SCALAR
Вызывает подпрограмму Perl в скалярном контексте. Это значение по умолчанию для всех функций call_*.
Этот флаг имеет два эффекта:
-
Он указывает подпрограмме, что она выполняется в скалярном контексте (если она выполняет wantarray, результат будет false).
-
Он гарантирует, что из подпрограммы возвращается только скаляр. Конечно, подпрограмма может игнорировать wantarray и вернуть список. В этом случае будет возвращён только последний элемент списка.
Возвращаемое значение функции call_* указывает количество элементов, возвращенных подпрограммой Perl — в этом случае это 0 или 1.
Если 0, то вы задали флаг G_DISCARD.
Если 1, то возвращаемый подпрограммой элемент будет помещён в стек Perl — раздел «Возвращение скаляра» демонстрирует, как получить это значение из стека. Независимо от того, сколько элементов возвращает Perl-подпрограмма, доступен только последний — представьте случай, когда возвращается только одно значение, как список с одним элементом. Другие возвращаемые элементы будут недоступны к тому моменту, как управление вернётся из функции call_*. Раздел «Возвращение списка в скалярном контексте» показывает пример этого поведения.
G_ARRAY
Вызывает подпрограмму Perl в списковом контексте.
Как и G_SCALAR, этот флаг имеет два эффекта:
-
Он указывает подпрограмме, что она выполняется в списковом контексте (если она выполняет wantarray, результат будет true).
-
Он гарантирует, что все элементы, возвращённые из подпрограммы, будут доступны, когда управление вернётся из функции call_*.
Возвращаемое значение функции call_* указывает количество элементов, возвращённых подпрограммой Perl.
Если 0, то вы задали флаг G_DISCARD.
Если не 0, то это количество элементов, возвращенных подпрограммой. Эти элементы будут размещены в стеке Perl. Раздел «Возвращение списка значений» содержит пример использования флага G_ARRAY и механизма доступа к возвращаемым элементам из стека Perl.
G_DISCARD
По умолчанию функции call_* помещают элементы, возвращённые подпрограммой Perl, в стек. Если вам не нужны эти элементы, то установка этого флага заставит Perl автоматически их удалить. Обратите внимание, что вы всё ещё можете указать контекст для подпрограммы Perl, используя G_SCALAR или G_ARRAY.
Если вы не устанавливаете этот флаг, то крайне важно убедиться, что все временные переменные (т.е., параметры, передаваемые в подпрограмму Perl, и значения, возвращаемые из подпрограммы) удалены. Раздел "Возврат скалярного значения" содержит подробности о том, как явно удалить эти временные переменные, а раздел "Использование Perl для удаления временных переменных" обсуждает конкретные случаи, когда вы можете проигнорировать проблему и позволить Perl справиться с ней.
G_NOARGS
Всякий раз, когда подпрограмма Perl вызывается с помощью одной из функций call_*, по умолчанию предполагается, что параметры должны быть переданы в подпрограмму. Если вы не передаете никаких параметров в подпрограмму Perl, вы можете немного сэкономить время, установив этот флаг. Он имеет эффект, что не создается массив @_ для подпрограммы Perl.
Хотя функциональность, предоставляемая этим флагом, может показаться простой, ее следует использовать только в случае веской причины. Причина осторожности заключается в том, что даже если вы указали флаг G_NOARGS, подпрограмма Perl, которая была вызвана, все равно может считать, что вы передали ей параметры.
На самом деле, может произойти так, что подпрограмма Perl, которую вы вызвали, может получить доступ к массиву @_ из предыдущей подпрограммы Perl. Это произойдет, когда код, выполняющий функцию call_*, сам был вызван из другой подпрограммы Perl. Приведенный ниже код иллюстрирует это
sub fred
{ print "@_\n" }
sub joe
{ &fred }
&joe(1,2,3); Это будет выводить
1 2 3 Что произошло, так это то, что fred получает доступ к массиву @_, который принадлежит joe.
G_EVAL
Подпрограмма Perl, которую вы вызываете, может завершиться аномально, например, явно вызвав die или не существуя на самом деле. По умолчанию, при возникновении любого из этих событий процесс завершается немедленно. Если вы хотите перехватить этот тип события, укажите флаг G_EVAL. Он поместит eval { } вокруг вызова подпрограммы.
Всякий раз, когда управление возвращается из функции call_*, необходимо проверить переменную $@, как и в обычном скрипте Perl.
Значение, возвращаемое функцией call_*, зависит от того, какие другие флаги были указаны и произошла ли ошибка. Вот все возможные случаи:
-
Если функция call_* возвращает значение нормально, то возвращаемое значение указано в предыдущих разделах.
-
Если указан G_DISCARD, возвращаемое значение всегда будет 0.
-
Если указан G_ARRAY и произошла ошибка, возвращаемое значение всегда будет 0.
-
Если указан G_SCALAR и произошла ошибка, возвращаемое значение будет 1, а значение в вершине стека — undef. Это означает, что если вы уже обнаружили ошибку, проверив
$@, и хотите продолжить выполнение программы, вы должны помнить о необходимости извлечь undef из стека.
См. "Использование G_EVAL" для получения подробностей об использовании G_EVAL.
G_KEEPERR
Использование флага G_EVAL, описанного выше, всегда установит $@: очищая его, если ошибки не было, и устанавливая его, чтобы описать ошибку, если в вызываемом коде была ошибка. Это то, что вам нужно, если вы намерены обрабатывать возможные ошибки, но иногда вы просто хотите перехватывать ошибки и не давать им мешать остальной части программы.
Этот сценарий, в основном, будет применим к коду, который должен вызываться из деструкторов, асинхронных обратных вызовов и обработчиков сигналов. В таких ситуациях, когда вызываемый код слабо связан с окружающей динамической контекстом, основной программе необходимо изолироваться от ошибок в вызываемом коде, даже если они не могут быть обработаны разумно. Это также может быть полезно для кода для __DIE__ или __WARN__ хуков и tie функций.
Флаг G_KEEPERR предназначен для использования совместно с G_EVAL в функциях call_*, используемых для реализации такого кода, или с eval_sv. Этот флаг не влияет на функции call_* при отсутствии G_EVAL.
При использовании G_KEEPERR любая ошибка в вызываемом коде завершает вызов, как обычно, и ошибка не распространяется за пределы вызова (как обычно для G_EVAL), но она не попадает в $@. Вместо этого ошибка преобразуется в предупреждение, снабженное префиксом "\t(в очистке)". Это можно отключить с помощью no warnings 'misc'. Если ошибок нет, $@ не будет очищен.
Обратите внимание, что флаг G_KEEPERR не распространяется на внутренние eval; они по-прежнему могут устанавливать $@.
Флаг G_KEEPERR был введен в версии Perl 5.002.
См. "Использование G_KEEPERR" для примера ситуации, оправдывающей использование этого флага.
Определение контекста
Как упоминалось выше, вы можете определить контекст текущей подпрограммы в Perl с помощью wantarray. Эквивалентный тест можно выполнить в C, используя макрос GIMME_V, который возвращает G_ARRAY, если вы были вызваны в списочном контексте, G_SCALAR, если в скалярном контексте, или G_VOID, если в контексте без значения возврата (т.е., возвращаемое значение не будет использоваться). Более старая версия этого макроса называется GIMME; в контексте без значения возврата он возвращает G_SCALAR вместо G_VOID. Пример использования макроса GIMME_V показан в разделе "Использование GIMME_V".
ПРИМЕРЫ
Достаточно определений! Давайте рассмотрим несколько примеров.
Perl предоставляет множество макросов для помощи в доступе к стеку Perl. Всюду, где это возможно, эти макросы следует использовать при взаимодействии с внутренними компонентами Perl. Мы надеемся, что это сделает код менее уязвимым к изменениям, которые могут быть внесены в Perl в будущем.
Еще один момент, который стоит отметить, заключается в том, что в первом ряду примеров я использовал только функцию call_pv. Это было сделано для того, чтобы код был проще и легче понять. Всюду, где это возможно, если выбор стоит между использованием call_pv и call_sv, вы всегда должны пытаться использовать call_sv. См. "Использование call_sv" для получения подробностей.
Без параметров, ничего не возвращается
Этот первый тривиальный пример вызовет подпрограмму Perl, PrintUID, для вывода UID процесса.
sub PrintUID
{
print "UID is $<\n";
} и вот функция C для её вызова
static void
call_PrintUID()
{
dSP;
PUSHMARK(SP);
call_pv("PrintUID", G_DISCARD|G_NOARGS);
} Просто, да?
Несколько моментов, которые следует отметить в этом примере:
-
Проигнорируйте
dSPиPUSHMARK(SP)пока. Они будут обсуждаться в следующем примере. -
Мы не передаем никаких параметров в PrintUID, поэтому можно указать G_NOARGS.
-
Мы не заинтересованы в возвращаемых значениях PrintUID, поэтому указывается G_DISCARD. Даже если PrintUID была изменена для возврата каких-либо значений, использование G_DISCARD означает, что они будут удалены к моменту возврата управления из call_pv.
-
Поскольку используется call_pv, подпрограмма Perl указывается как строка C. В данном случае имя подпрограммы жёстко запрограммировано в коде.
-
Поскольку мы указали G_DISCARD, нет необходимости проверять возвращаемое значение из call_pv. Оно всегда будет 0.
Передача параметров
Теперь давайте рассмотрим немного более сложный пример. На этот раз мы хотим вызвать подпрограмму Perl, LeftString, которая будет принимать 2 параметра — строку ($s) и целое число ($n). Подпрограмма просто выведет первые $n символов строки.
Итак, подпрограмма Perl будет выглядеть так:
sub LeftString
{
my($s, $n) = @_;
print substr($s, 0, $n), "\n";
} Функция C, необходимая для вызова LeftString, будет выглядеть так:
static void
call_LeftString(a, b)
char * a;
int b;
{
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sv_2mortal(newSVpv(a, 0)));
PUSHs(sv_2mortal(newSViv(b)));
PUTBACK;
call_pv("LeftString", G_DISCARD);
FREETMPS;
LEAVE;
} Вот несколько заметок о функции C call_LeftString.
-
Параметры передаются в подпрограмму Perl с помощью стека Perl. Это цель кода, начинающегося с строки
dSPи заканчивающегося строкойPUTBACK.dSPобъявляет локальную копию указателя стека. К этой локальной копии необходимо всегда обращаться какSP. -
Если вы собираетесь поместить что-то в стек Perl, вам нужно знать, куда это поместить. Для этого предназначен макрос
dSP— он объявляет и инициализирует локальную копию указателя стека Perl.Все остальные макросы, которые будут использоваться в этом примере, требуют использования этого макроса.
Исключение из этого правила — если вы вызываете подпрограмму Perl непосредственно из функции XSUB. В этом случае не нужно явно использовать макрос
dSP— он будет объявлен автоматически. -
Любые параметры, которые нужно поместить в стек, должны быть заключены в макросы
PUSHMARKиPUTBACK. Цель этих двух макросов в этом контексте — автоматически подсчитывать количество параметров, которые вы помещаете. Тогда, когда Perl создаёт массив@_для подпрограммы, он знает, каким сделать его размером.Макрос
PUSHMARKсообщает Perl, чтобы он запомнил текущий указатель стека. Даже если вы не передаёте никаких параметров (как в примере в разделе "Без параметров, ничего не возвращается"), вы всё равно должны вызвать макросPUSHMARKперед вызовом любых функций call_* — Perl всё равно должен знать, что параметров нет.Макрос
PUTBACKустанавливает глобальную копию указателя стека такой же, как наша локальная копия. Если мы этого не сделаем, call_pv не узнает, где находятся два параметра, которые мы поместили в стек — помните, что до сих пор все манипуляции с указателем стека выполнялись с нашей локальной копией, а не с глобальной. -
Далее мы переходим к EXTEND и PUSH. Именно здесь параметры фактически помещаются в стек. В этом случае мы помещаем строку и целое число.
Альтернативно, вы можете использовать макрос XPUSHs(), который объединяет
EXTEND(SP, 1)иPUSHs(). Это менее эффективно, если вы помещаете несколько значений.Подробную информацию о работе макросов PUSH см. в разделе "XSUBs и стек аргументов" в perlguts.
-
Поскольку мы создали временные значения (с помощью вызовов sv_2mortal()), нам нужно будет привести в порядок стек Perl и утилизировать смертные SVs.
Это цель
ENTER; SAVETMPS;в начале функции и
FREETMPS; LEAVE;в конце. Пара
ENTER/SAVETMPSсоздаёт границу для любых временных значений, которые мы создаём. Это означает, что временные значения, которые мы удаляем, будут ограничены теми, которые были созданы после этих вызовов.Пара
FREETMPS/LEAVEудалит любые значения, возвращаемые подпрограммой Perl (см. следующий пример), а также удалит смертные SVs, которые мы создали. НаличиеENTER/SAVETMPSв начале кода гарантирует, что другие смертные значения не будут уничтожены.Подумайте о работе этих макросов как о работе
{и}в Perl для ограничения области видимости локальных переменных.Подробную информацию об альтернативе использования этих макросов см. в разделе "Использование Perl для удаления временных значений".
-
Наконец, LeftString теперь можно вызвать через функцию call_pv. Единственный указанный флаг — G_DISCARD. Поскольку мы передаём 2 параметра подпрограмме Perl в этот раз, мы не указали G_NOARGS.
Возвращение скалярного значения
Теперь пример обработки элементов, возвращаемых подпрограммой Perl.
Вот подпрограмма Perl, Adder, которая принимает 2 целых параметра и просто возвращает их сумму.
sub Adder
{
my($a, $b) = @_;
$a + $b;
} Поскольку теперь нас интересует возвращаемое значение от Adder, функция C, необходимая для её вызова, теперь немного сложнее.
static void
call_Adder(a, b)
int a;
int b;
{
dSP;
int count;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sv_2mortal(newSViv(a)));
PUSHs(sv_2mortal(newSViv(b)));
PUTBACK;
count = call_pv("Adder", G_SCALAR);
SPAGAIN;
if (count != 1)
croak("Big trouble\n");
printf ("The sum of %d and %d is %d\n", a, b, POPi);
PUTBACK;
FREETMPS;
LEAVE;
} Следует обратить внимание на следующие моменты:
-
Единственный указанный флаг в этот раз — G_SCALAR. Это означает, что массив
@_будет создан, и значение, возвращённое Adder, будет существовать после вызова call_pv. -
Цель макроса
SPAGAIN— обновить локальную копию указателя стека. Это необходимо, потому что возможно, что память, выделенная для стека Perl, была перераспределена во время вызова call_pv.Если вы используете указатель стека Perl в своём коде, вы всегда должны обновлять локальную копию с помощью SPAGAIN всякий раз, когда используете функции call_* или любые другие внутренние функции Perl.
-
Хотя ожидалось, что Adder вернёт только одно значение, всё же рекомендуется проверять код возврата от call_pv.
Ожидание одного значения — это не то же самое, что знание, что оно будет. Если кто-то изменит Adder, чтобы она возвращала список, и мы не проверим эту возможность и не примем соответствующие меры, стек Perl окажется в несогласованном состоянии. Этого действительно следует избегать.
-
Здесь макрос
POPiиспользуется для извлечения возвращаемого значения из стека. В этом случае нам нужно было целое число, поэтому использовалсяPOPi.Вот полный список доступных макросов POP и типы возвращаемых ими значений.
POPs SV POPp pointer (PV) POPpbytex pointer to bytes (PV) POPn double (NV) POPi integer (IV) POPu unsigned integer (UV) POPl long POPul unsigned longПоскольку эти макросы имеют побочные эффекты, не используйте их в качестве аргументов макросам, которые могут оценивать свой аргумент несколько раз, например:
/* Bad idea, don't do this */ STRLEN len; const char *s = SvPV(POPs, len);Вместо этого используйте временную переменную:
STRLEN len; SV *sv = POPs; const char *s = SvPV(sv, len);или макрос, гарантирующий, что он будет оценивать свои аргументы только один раз:
STRLEN len; const char *s = SvPVx(POPs, len); -
Окончательный
PUTBACKиспользуется для приведения стека Perl в согласованное состояние перед выходом из функции. Это необходимо, потому что при извлечении возвращаемого значения из стека с помощьюPOPiобновлялась только наша локальная копия указателя стека. Помните, чтоPUTBACKустанавливает глобальный указатель стека таким же, как наша локальная копия.
Возвращение списка значений
Теперь расширим предыдущий пример, чтобы вернуть как сумму параметров, так и разность.
Вот подпрограмма Perl
sub AddSubtract
{
my($a, $b) = @_;
($a+$b, $a-$b);
} и соответствующая функция C
static void
call_AddSubtract(a, b)
int a;
int b;
{
dSP;
int count;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sv_2mortal(newSViv(a)));
PUSHs(sv_2mortal(newSViv(b)));
PUTBACK;
count = call_pv("AddSubtract", G_ARRAY);
SPAGAIN;
if (count != 2)
croak("Big trouble\n");
printf ("%d - %d = %d\n", a, b, POPi);
printf ("%d + %d = %d\n", a, b, POPi);
PUTBACK;
FREETMPS;
LEAVE;
} Если call_AddSubtract вызывается так
call_AddSubtract(7, 4); то вот вывод
7 - 4 = 3
7 + 4 = 11 Примечания
-
Нам нужен контекст списка, поэтому использовался G_ARRAY.
-
Неудивительно, что
POPiиспользуется дважды в этот раз, потому что мы извлекали 2 значения из стека. Важно отметить, что при использовании макросовPOP*они извлекаются из стека в обратном порядке.
Возвращение списка в скалярном контексте
Предположим, что подпрограмма Perl в предыдущем разделе вызывалась в скалярном контексте, как это:
static void
call_AddSubScalar(a, b)
int a;
int b;
{
dSP;
int count;
int i;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sv_2mortal(newSViv(a)));
PUSHs(sv_2mortal(newSViv(b)));
PUTBACK;
count = call_pv("AddSubtract", G_SCALAR);
SPAGAIN;
printf ("Items Returned = %d\n", count);
for (i = 1; i <= count; ++i)
printf ("Value %d = %d\n", i, POPi);
PUTBACK;
FREETMPS;
LEAVE;
} Другое изменение заключается в том, что call_AddSubScalar будет выводить количество возвращённых элементов из подпрограммы Perl и их значения (для простоты предполагается, что это целые числа). Итак, если call_AddSubScalar вызывается так:
call_AddSubScalar(7, 4); то вывод будет:
Items Returned = 1
Value 1 = 3 В этом случае главное — то, что из подпрограммы возвращается только последний элемент списка. AddSubtract фактически вернулась в call_AddSubScalar.
Возвращение данных из Perl через список параметров
Также возможно возвращать значения непосредственно через список параметров — вопрос о целесообразности этого — отдельный.
Подпрограмма Perl, Inc, ниже принимает 2 параметра и увеличивает каждый напрямую.
sub Inc
{
++ $_[0];
++ $_[1];
} и вот функция C для её вызова.
static void
call_Inc(a, b)
int a;
int b;
{
dSP;
int count;
SV * sva;
SV * svb;
ENTER;
SAVETMPS;
sva = sv_2mortal(newSViv(a));
svb = sv_2mortal(newSViv(b));
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sva);
PUSHs(svb);
PUTBACK;
count = call_pv("Inc", G_DISCARD);
if (count != 0)
croak ("call_Inc: expected 0 values from 'Inc', got %d\n",
count);
printf ("%d + 1 = %d\n", a, SvIV(sva));
printf ("%d + 1 = %d\n", b, SvIV(svb));
FREETMPS;
LEAVE;
} Для доступа к двум параметрам, которые были помещены в стек после возвращения из call_pv, необходимо записать их адреса — таким образом, переменные sva и svb.
Причина, по которой это необходимо, заключается в том, что область стека Perl, где они находились, очень вероятно, будет перезаписана чем-то другим к тому моменту, когда управление вернётся из call_pv.
Использование G_EVAL
Теперь пример с использованием G_EVAL. Ниже приведена подпрограмма Perl, которая вычисляет разность её 2 параметров. Если это приведёт к отрицательному результату, подпрограмма вызывает die.
sub Subtract
{
my ($a, $b) = @_;
die "death can be fatal\n" if $a < $b;
$a - $b;
} и код C для её вызова
static void
call_Subtract(a, b)
int a;
int b;
{
dSP;
int count;
SV *err_tmp;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sv_2mortal(newSViv(a)));
PUSHs(sv_2mortal(newSViv(b)));
PUTBACK;
count = call_pv("Subtract", G_EVAL|G_SCALAR);
SPAGAIN;
/* Check the eval first */
err_tmp = ERRSV;
if (SvTRUE(err_tmp))
{
printf ("Uh oh - %s\n", SvPV_nolen(err_tmp));
POPs;
}
else
{
if (count != 1)
croak("call_Subtract: wanted 1 value from 'Subtract', got %d\n",
count);
printf ("%d - %d = %d\n", a, b, POPi);
}
PUTBACK;
FREETMPS;
LEAVE;
} Если call_Subtract вызывается так:
call_Subtract(4, 5) будет напечатано следующее:
Uh oh - death can be fatal Примечания
-
Мы хотим иметь возможность перехватить die, поэтому мы использовали флаг G_EVAL. Если бы мы этого не сделали, программа сразу завершилась бы в инструкции die в подпрограмме Subtract.
-
Код
err_tmp = ERRSV; if (SvTRUE(err_tmp)) { printf ("Uh oh - %s\n", SvPV_nolen(err_tmp)); POPs; }является прямым эквивалентом этой части Perl
print "Uh oh - $@\n" if $@;PL_errgv— это глобальная переменная Perl типаGV *, которая указывает на запись в таблице символов, содержащую ошибку.ERRSVпоэтому относится к C-эквиваленту$@. Мы используем локальную временную переменнуюerr_tmp, посколькуERRSV— это макрос, вызывающий функцию, иSvTRUE(ERRSV)в конечном итоге вызывал бы эту функцию несколько раз. -
Обратите внимание, что стек извлекается с помощью
POPsв блоке, гдеSvTRUE(err_tmp)имеет значение true. Это необходимо, потому что всякий раз, когда функция call_*, вызываемая с флагами G_EVAL|G_SCALAR, возвращает ошибку, в верхней части стека находится значение undef. Поскольку мы хотим, чтобы программа продолжала выполнение после обнаружения этой ошибки, необходимо, чтобы стек был приведён в порядок путём удаления undef.
Использование G_KEEPERR
Рассмотрим этот довольно шуточный пример, где мы использовали XS-версию примера call_Subtract выше внутри деструктора:
package Foo;
sub new { bless {}, $_[0] }
sub Subtract {
my($a,$b) = @_;
die "death can be fatal" if $a < $b;
$a - $b;
}
sub DESTROY { call_Subtract(5, 4); }
sub foo { die "foo dies"; }
package main;
{
my $foo = Foo->new;
eval { $foo->foo };
}
print "Saw: $@" if $@; # should be, but isn't В этом примере не будет распознано возникновение ошибки внутри eval {}. Вот почему: код call_Subtract выполнялся, когда Perl очищал временные объекты при выходе из внешнего блока фигурных скобок, и поскольку call_Subtract реализован с помощью call_pv с флагом G_EVAL, он быстро сбросил $@. В результате не сработала внешняя проверка на $@, и, следовательно, не сработала обработка ошибки.
Добавление флага G_KEEPERR, так что вызов call_pv в call_Subtract будет выглядеть так:
count = call_pv("Subtract", G_EVAL|G_SCALAR|G_KEEPERR); сохранит ошибку и восстановит надёжную обработку ошибок.
Использование call_sv
Во всех предыдущих примерах я «жестко» задавал имя подпрограммы Perl, вызываемой из C. Однако в большинстве случаев удобнее указывать имя подпрограммы Perl изнутри скрипта Perl, и в этом случае вам нужно использовать call_sv.
Рассмотрим код Perl ниже
sub fred
{
print "Hello there\n";
}
CallSubPV("fred"); Вот фрагмент XSUB, который определяет CallSubPV.
void
CallSubPV(name)
char * name
CODE:
PUSHMARK(SP);
call_pv(name, G_DISCARD|G_NOARGS); Это нормально, насколько это возможно. Дело в том, что подпрограмма Perl может быть указана только как строка, однако Perl допускает ссылки на подпрограммы и анонимные подпрограммы. Именно здесь call_sv полезен.
Код ниже для CallSubSV идентичен CallSubPV, за исключением того, что параметр name теперь определен как SV* и мы используем call_sv вместо call_pv.
void
CallSubSV(name)
SV * name
CODE:
PUSHMARK(SP);
call_sv(name, G_DISCARD|G_NOARGS); Поскольку мы используем SV для вызова fred, все перечисленное ниже может быть использовано:
CallSubSV("fred");
CallSubSV(\&fred);
$ref = \&fred;
CallSubSV($ref);
CallSubSV( sub { print "Hello there\n" } ); Как видите, call_sv предоставляет гораздо большую гибкость в том, как вы можете указать подпрограмму Perl.
Следует отметить, что если необходимо сохранить SV (name в примере выше), который соответствует подпрограмме Perl, чтобы она могла использоваться позднее в программе, недостаточно просто сохранить копию указателя на SV. Предположим, что код выше выглядел так:
static SV * rememberSub;
void
SaveSub1(name)
SV * name
CODE:
rememberSub = name;
void
CallSavedSub1()
CODE:
PUSHMARK(SP);
call_sv(rememberSub, G_DISCARD|G_NOARGS); Причина, по которой это неправильно, заключается в том, что к моменту использования указателя rememberSub в CallSavedSub1, он может или не может по-прежнему ссылаться на подпрограмму Perl, которая была записана в SaveSub1. Это особенно верно для следующих случаев:
SaveSub1(\&fred);
CallSavedSub1();
SaveSub1( sub { print "Hello there\n" } );
CallSavedSub1(); К моменту выполнения каждой из SaveSub1 строк выше, SV*ы, которые соответствовали параметрам, больше не будут существовать. Ожидайте сообщение об ошибке от Perl в формате
Can't use an undefined value as a subroutine reference at ... для каждой из CallSavedSub1 строк.
Аналогично, с этим кодом
$ref = \&fred;
SaveSub1($ref);
$ref = 47;
CallSavedSub1(); можно ожидать одно из этих сообщений (которое вы фактически получите, зависит от версии Perl, которую вы используете)
Not a CODE reference at ...
Undefined subroutine &main::47 called ... Переменная $ref могла ссылаться на подпрограмму fred всякий раз, когда вызывался SaveSub1, но к моменту вызова CallSavedSub1 она теперь содержит число 47. Поскольку мы сохранили только указатель на исходный SV в SaveSub1, любые изменения в $ref будут отслеживаться указателем rememberSub. Это означает, что всякий раз, когда вызывается CallSavedSub1, она будет пытаться выполнить код, на который ссылается SV* rememberSub. В этом случае, однако, он теперь ссылается на целое число 47, поэтому ожидайте, что Perl громко пожалуется.
Аналогичная, но более тонкая проблема проиллюстрирована этим кодом:
$ref = \&fred;
SaveSub1($ref);
$ref = \&joe;
CallSavedSub1(); На этот раз всякий раз, когда CallSavedSub1 вызывается, она будет выполнять подпрограмму Perl joe (если она существует), а не fred, как изначально запрашивалось при вызове SaveSub1.
Чтобы обойти эти проблемы, необходимо сделать полную копию SV. Код ниже показывает SaveSub2, измененный для этого.
/* this isn't thread-safe */
static SV * keepSub = (SV*)NULL;
void
SaveSub2(name)
SV * name
CODE:
/* Take a copy of the callback */
if (keepSub == (SV*)NULL)
/* First time, so create a new SV */
keepSub = newSVsv(name);
else
/* Been here before, so overwrite */
SvSetSV(keepSub, name);
void
CallSavedSub2()
CODE:
PUSHMARK(SP);
call_sv(keepSub, G_DISCARD|G_NOARGS); Чтобы избежать создания нового SV каждый раз, когда SaveSub2 вызывается, функция сначала проверяет, вызывалась ли она ранее. Если нет, то выделяется место для нового SV, и ссылка на подпрограмму Perl name копируется в переменную keepSub в одной операции с помощью newSVsv. После этого всякий раз, когда SaveSub2 вызывается, существующий SV, keepSub, перезаписывается новым значением с помощью SvSetSV.
Примечание: использование статической или глобальной переменной для хранения SV небезопасно в многопоточных приложениях. Вы можете использовать механизм MY_CXT, описанный в "Безопасное хранение статических данных в XS" в perlxs, который быстрый, или хранить значения в глобальных переменных Perl, используя get_sv(), что намного медленнее.
Использование call_argv
Вот подпрограмма Perl, которая печатает все параметры, передаваемые ей.
sub PrintList
{
my(@list) = @_;
foreach (@list) { print "$_\n" }
} И вот пример call_argv, который вызовет PrintList.
static char * words[] = {"alpha", "beta", "gamma", "delta", NULL};
static void
call_PrintList()
{
call_argv("PrintList", G_DISCARD, words);
} Обратите внимание, что в этом случае вызывать PUSHMARK необязательно. Это потому, что call_argv сделает это за вас.
Использование call_method
Рассмотрим следующий код Perl:
{
package Mine;
sub new
{
my($type) = shift;
bless [@_]
}
sub Display
{
my ($self, $index) = @_;
print "$index: $$self[$index]\n";
}
sub PrintID
{
my($class) = @_;
print "This is Class $class version 1.0\n";
}
} Он реализует очень простой класс для управления массивом. Помимо конструктора, new, он объявляет методы, один статический и один виртуальный. Статический метод, PrintID, выводит просто имя класса и номер версии. Виртуальный метод, Display, выводит один элемент массива. Вот пример использования его на чистом Perl.
$a = Mine->new('red', 'green', 'blue');
$a->Display(1);
Mine->PrintID; будет печатать
1: green
This is Class Mine version 1.0 Вызов метода Perl из C довольно прост. Требуются следующие вещи:
-
Ссылка на объект для виртуального метода или имя класса для статического метода
-
Имя метода
-
Любые другие параметры, специфичные для метода
Вот простой XSUB, который иллюстрирует механику вызова методов PrintID и Display из C.
void
call_Method(ref, method, index)
SV * ref
char * method
int index
CODE:
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(ref);
PUSHs(sv_2mortal(newSViv(index)));
PUTBACK;
call_method(method, G_DISCARD);
void
call_PrintID(class, method)
char * class
char * method
CODE:
PUSHMARK(SP);
XPUSHs(sv_2mortal(newSVpv(class, 0)));
PUTBACK;
call_method(method, G_DISCARD); Таким образом, методы PrintID и Display можно вызвать следующим образом:
$a = Mine->new('red', 'green', 'blue');
call_Method($a, 'Display', 1);
call_PrintID('Mine', 'PrintID'); Единственное, что нужно отметить, состоит в том, что как в статических, так и в виртуальных методах имя метода не передается через стек — оно используется в качестве первого параметра для call_method.
Использование GIMME_V
Вот тривиальный XSUB, который печатает контекст, в котором он выполняется.
void
PrintContext()
CODE:
U8 gimme = GIMME_V;
if (gimme == G_VOID)
printf ("Context is Void\n");
else if (gimme == G_SCALAR)
printf ("Context is Scalar\n");
else
printf ("Context is Array\n"); И вот немного Perl для его проверки.
PrintContext;
$a = PrintContext;
@a = PrintContext; Вывод от этого будет
Context is Void
Context is Scalar
Context is Array Использование Perl для удаления временных объектов
В примерах, приведенных до настоящего времени, все временные объекты, созданные в обратном вызове (т. е. параметры, передаваемые по стеку в функцию call_* или значения, возвращаемые через стек), освобождались одним из этих методов:
-
Указание флага G_DISCARD при вызове call_*
-
Явное использование пары
ENTER/SAVETMPS--FREETMPS/LEAVE
Существует другой метод, который можно использовать, а именно, позволить Perl сделать это автоматически всякий раз, когда он получает управление после завершения обратного вызова. Это делается просто путем неиспользования
ENTER;
SAVETMPS;
...
FREETMPS;
LEAVE; последовательности в обратном вызове (и, конечно же, не указания флага G_DISCARD).
Если вы собираетесь использовать этот метод, вы должны быть осведомлены о возможной утечке памяти, которая может возникнуть в очень специфических обстоятельствах. Чтобы объяснить эти обстоятельства, вам нужно немного узнать о потоке управления между Perl и процедурой обратного вызова.
Приведенные в начале документа примеры (обработчик ошибок и программа с событийно-ориентированным программированием) являются типичными для двух основных типов потока управления, с которыми вы, вероятно, столкнетесь с обратными вызовами. Существует очень важное различие между ними, поэтому обратите внимание.
В первом примере, обработчике ошибок, поток управления может быть следующим. Вы создали интерфейс к внешней библиотеке. Управление может достичь внешней библиотеки так:
perl --> XSUB --> external library Пока управление находится в библиотеке, возникает состояние ошибки. Вы предварительно настроили обратный вызов Perl для обработки этой ситуации, поэтому он будет выполнен. После завершения обратного вызова управление вернется обратно в Perl. Вот как будет выглядеть поток управления в этой ситуации:
perl --> XSUB --> external library
...
error occurs
...
external library --> call_* --> perl
|
perl <-- XSUB <-- external library <-- call_* <----+ После завершения обработки ошибки с помощью call_* управление почти мгновенно возвращается в Perl.
На диаграмме чем дальше вы идете вправо, тем глубже вложен область. Только когда управление возвращается в perl в крайнем левом положении диаграммы, вы вернетесь к окружающей области, и все временные объекты, которые вы оставили, будут освобождены.
Во втором примере, программе с событийно-ориентированным программированием, поток управления будет выглядеть так
perl --> XSUB --> event handler
...
event handler --> call_* --> perl
|
event handler <-- call_* <----+
...
event handler --> call_* --> perl
|
event handler <-- call_* <----+
...
event handler --> call_* --> perl
|
event handler <-- call_* <----+ В этом случае поток управления может состоять только из повторяющейся последовательности
event handler --> call_* --> perl практически на протяжении всего времени работы программы. Это означает, что управление может никогда не вернуться в окружающую область в Perl в крайнем левом положении.
Так в чем же проблема? Ну, если вы ожидаете, что Perl приведет в порядок эти временные объекты, вы можете долго ждать. Чтобы Perl удалил ваши временные объекты, управление должно вернуться в окружающую область Perl на каком-то этапе. В сценарии событийно-ориентированного программирования это может никогда не произойти. Это означает, что с течением времени ваша программа будет создавать все больше и больше временных объектов, ни один из которых никогда не будет освобожден. Поскольку каждый из этих временных объектов потребляет некоторую память, ваша программа, в конечном итоге, потребляет всю доступную память в вашей системе — бах!
И так, вывод — если вы уверены, что управление вернется в окружающую область Perl довольно быстро после окончания вашего обратного вызова, то совершенно необязательно явно удалять любые временные объекты, которые вы можете создать. Однако, если вы сомневаетесь в том, что делать, не повредит привести в порядок все.
Стратегии хранения информации о контексте обратного вызова
Возможно, одна из самых сложных проблем при проектировании интерфейса обратного вызова — это определение того, как хранить отображение между функцией обратного вызова C и ее эквивалентом в Perl.
Чтобы понять, почему это может быть реальной проблемой, сначала рассмотрите, как обратный вызов настраивается в полностью C-среде. Как правило, API C предоставляет функцию для регистрации обратного вызова. Эта функция ожидает в качестве одного из своих параметров указатель на функцию. Ниже приведен вызов гипотетической функции register_fatal, которая регистрирует вызов C-функции при возникновении фатальной ошибки.
register_fatal(cb1); Единственный параметр cb1 — это указатель на функцию, поэтому вы должны определить cb1 в своем коде, например, так
static void
cb1()
{
printf ("Fatal Error\n");
exit(1);
} Теперь измените это, чтобы вызвать подпрограмму Perl вместо этого
static SV * callback = (SV*)NULL;
static void
cb1()
{
dSP;
PUSHMARK(SP);
/* Call the Perl sub to process the callback */
call_sv(callback, G_DISCARD);
}
void
register_fatal(fn)
SV * fn
CODE:
/* Remember the Perl sub */
if (callback == (SV*)NULL)
callback = newSVsv(fn);
else
SvSetSV(callback, fn);
/* register the callback with the external library */
register_fatal(cb1); где Perl-эквивалент register_fatal и обратный вызов, который он регистрирует, pcb1, могут выглядеть так
# Register the sub pcb1
register_fatal(\&pcb1);
sub pcb1
{
die "I'm dying...\n";
} Сопоставление между C-обратным вызовом и его Perl-эквивалентом хранится в глобальной переменной callback.
Это будет достаточно, если вы когда-либо захотите зарегистрировать только один обратный вызов в любой момент времени. Примером может служить обработчик ошибок, как в коде, набросанном выше. Однако помните, что повторные вызовы register_fatal будут заменять ранее зарегистрированную функцию обратного вызова новой.
Предположим, вы хотите взаимодействовать с библиотекой, которая позволяет выполнять асинхронное ввод-вывод файлов. В этом случае вы можете зарегистрировать обратный вызов всякий раз, когда операция чтения завершена. Для практического применения мы хотим вызывать отдельные подпрограммы Perl для каждого открытого файла. В текущем виде пример обработчика ошибок выше не подходит, так как он позволяет определить только один обратный вызов за один раз. Нам нужен способ хранения сопоставления между открытым файлом и подпрограммой Perl, которую мы хотим вызвать для этого файла.
Предположим, библиотека ввода-вывода имеет функцию asynch_read, которая связывает функцию C ProcessRead с дескриптором файла fh - это предполагает, что она также предоставляет некоторую процедуру для открытия файла и получения дескриптора файла.
asynch_read(fh, ProcessRead) Она может ожидать функцию C ProcessRead следующего формата
void
ProcessRead(fh, buffer)
int fh;
char * buffer;
{
...
} Для обеспечения интерфейса Perl для этой библиотеки нам необходимо иметь возможность сопоставить параметр fh с подпрограммой Perl, которую мы хотим вызвать. Хэш-таблица - удобный механизм для хранения этого сопоставления. Приведенный ниже код демонстрирует возможное реализацию.
static HV * Mapping = (HV*)NULL;
void
asynch_read(fh, callback)
int fh
SV * callback
CODE:
/* If the hash doesn't already exist, create it */
if (Mapping == (HV*)NULL)
Mapping = newHV();
/* Save the fh -> callback mapping */
hv_store(Mapping, (char*)&fh, sizeof(fh), newSVsv(callback), 0);
/* Register with the C Library */
asynch_read(fh, asynch_read_if); и asynch_read_if может выглядеть так
static void
asynch_read_if(fh, buffer)
int fh;
char * buffer;
{
dSP;
SV ** sv;
/* Get the callback associated with fh */
sv = hv_fetch(Mapping, (char*)&fh , sizeof(fh), FALSE);
if (sv == (SV**)NULL)
croak("Internal error...\n");
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sv_2mortal(newSViv(fh)));
PUSHs(sv_2mortal(newSVpv(buffer, 0)));
PUTBACK;
/* Call the Perl sub */
call_sv(*sv, G_DISCARD);
} Для полноты, вот asynch_close. Это демонстрирует, как удалить запись из хеша Mapping.
void
asynch_close(fh)
int fh
CODE:
/* Remove the entry from the hash */
(void) hv_delete(Mapping, (char*)&fh, sizeof(fh), G_DISCARD);
/* Now call the real asynch_close */
asynch_close(fh); Таким образом, интерфейс Perl будет выглядеть так
sub callback1
{
my($handle, $buffer) = @_;
}
# Register the Perl callback
asynch_read($fh, \&callback1);
asynch_close($fh); Сопоставление между обратным вызовом C и Perl хранится во глобальном хеше Mapping в этот раз. Использование хеша имеет существенное преимущество, так как позволяет регистрировать неограниченное количество обратных вызовов.
Что делать, если интерфейс, предоставляемый обратным вызовом C, не содержит параметра, который позволяет сопоставить дескриптор файла с подпрограммой Perl? Предположим, в пакете асинхронного ввода-вывода функция обратного вызова получает только параметр buffer следующим образом:
void
ProcessRead(buffer)
char * buffer;
{
...
} Без дескриптора файла нет простого способа сопоставить обратный вызов C с подпрограммой Perl.
В этом случае возможным способом решения этой проблемы является предварительное определение ряда функций C для взаимодействия с Perl, таким образом:
#define MAX_CB 3
#define NULL_HANDLE -1
typedef void (*FnMap)();
struct MapStruct {
FnMap Function;
SV * PerlSub;
int Handle;
};
static void fn1();
static void fn2();
static void fn3();
static struct MapStruct Map [MAX_CB] =
{
{ fn1, NULL, NULL_HANDLE },
{ fn2, NULL, NULL_HANDLE },
{ fn3, NULL, NULL_HANDLE }
};
static void
Pcb(index, buffer)
int index;
char * buffer;
{
dSP;
PUSHMARK(SP);
XPUSHs(sv_2mortal(newSVpv(buffer, 0)));
PUTBACK;
/* Call the Perl sub */
call_sv(Map[index].PerlSub, G_DISCARD);
}
static void
fn1(buffer)
char * buffer;
{
Pcb(0, buffer);
}
static void
fn2(buffer)
char * buffer;
{
Pcb(1, buffer);
}
static void
fn3(buffer)
char * buffer;
{
Pcb(2, buffer);
}
void
array_asynch_read(fh, callback)
int fh
SV * callback
CODE:
int index;
int null_index = MAX_CB;
/* Find the same handle or an empty entry */
for (index = 0; index < MAX_CB; ++index)
{
if (Map[index].Handle == fh)
break;
if (Map[index].Handle == NULL_HANDLE)
null_index = index;
}
if (index == MAX_CB && null_index == MAX_CB)
croak ("Too many callback functions registered\n");
if (index == MAX_CB)
index = null_index;
/* Save the file handle */
Map[index].Handle = fh;
/* Remember the Perl sub */
if (Map[index].PerlSub == (SV*)NULL)
Map[index].PerlSub = newSVsv(callback);
else
SvSetSV(Map[index].PerlSub, callback);
asynch_read(fh, Map[index].Function);
void
array_asynch_close(fh)
int fh
CODE:
int index;
/* Find the file handle */
for (index = 0; index < MAX_CB; ++ index)
if (Map[index].Handle == fh)
break;
if (index == MAX_CB)
croak ("could not close fh %d\n", fh);
Map[index].Handle = NULL_HANDLE;
SvREFCNT_dec(Map[index].PerlSub);
Map[index].PerlSub = (SV*)NULL;
asynch_close(fh); В этом случае функции fn1, fn2, и fn3 используются для запоминания подпрограммы Perl, которую нужно вызвать. Каждая из функций содержит отдельный жёстко заданный индекс, который используется в функции Pcb для доступа к массиву Map и фактического вызова подпрограммы Perl.
У этого метода есть некоторые очевидные недостатки.
Во-первых, код значительно сложнее, чем в предыдущем примере.
Во-вторых, существует жёстко заданный лимит (в данном случае 3) на количество обратных вызовов, которые могут существовать одновременно. Единственный способ увеличить этот лимит - изменить код, добавив больше функций, и затем перекомпилировать. Тем не менее, при внимательном выборе числа функций это по-прежнему работоспособное решение, а в некоторых случаях является единственно возможным.
Подводя итог, вот несколько возможных методов, которые вы можете рассмотреть для хранения сопоставления между C и обратным вызовом Perl
- 1. Игнорирование проблемы - Разрешение только 1 обратного вызова
-
Для многих ситуаций, таких как взаимодействие с обработчиком ошибок, это может быть вполне подходящее решение.
- 2. Создание последовательности обратных вызовов - жёстко заданный лимит
-
Если невозможно определить контекст из параметров, возвращаемых обратным вызовом C, то вам может потребоваться создать последовательность функций интерфейса обратного вызова C и сохранить указатели на каждую из них в массиве.
- 3. Использование параметра для сопоставления с обратным вызовом Perl
-
Хеш-таблица - идеальный механизм для хранения сопоставления между C и Perl.
Альтернативное управление стеком
Хотя я использовал только макросы POP* для доступа к значениям, возвращаемым подпрограммами Perl, также возможно обойти эти макросы и прочитать стек с помощью макроса ST (см. perlxs для полного описания макроса ST).
В большинстве случаев макросы POP* должны быть достаточными; основная проблема с ними заключается в том, что они заставляют вас обрабатывать возвращаемые значения последовательно. В некоторых случаях это может быть не самый подходящий способ обработки значений. Нам нужно иметь возможность доступа к стеку в произвольном порядке. Макрос ST , как используется при написании XSUB, идеально подходит для этой цели.
Приведенный ниже код является примером, приведенным в разделе "Возвращение списка значений", переписанный с использованием ST вместо POP*.
static void
call_AddSubtract2(a, b)
int a;
int b;
{
dSP;
I32 ax;
int count;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 2);
PUSHs(sv_2mortal(newSViv(a)));
PUSHs(sv_2mortal(newSViv(b)));
PUTBACK;
count = call_pv("AddSubtract", G_ARRAY);
SPAGAIN;
SP -= count;
ax = (SP - PL_stack_base) + 1;
if (count != 2)
croak("Big trouble\n");
printf ("%d + %d = %d\n", a, b, SvIV(ST(0)));
printf ("%d - %d = %d\n", a, b, SvIV(ST(1)));
PUTBACK;
FREETMPS;
LEAVE;
} Примечания
-
Обратите внимание, что необходимо определить переменную
ax. Это связано с тем, что макросSTожидает ее существования. Если бы мы находились в XSUB, не было бы необходимости определятьax, так как она уже определена для нас. -
Код
SPAGAIN; SP -= count; ax = (SP - PL_stack_base) + 1;подготавливает стек таким образом, что мы можем использовать макрос
ST. -
В отличие от исходного кодирования этого примера, возвращаемые значения не считываются в обратном порядке. Таким образом,
ST(0)относится к первому значению, возвращенному подпрограммой Perl, аST(count-1)относится к последнему.
Создание и вызов анонимной подпрограммы в C
Как мы уже показали, call_sv может быть использован для вызова анонимной подпрограммы. Однако наш пример показал, как скрипт Perl вызывает XSUB для выполнения этой операции. Давайте посмотрим, как это можно сделать внутри нашего кода C:
...
SV *cvrv
= eval_pv("sub {
print 'You will not find me cluttering any namespace!'
}", TRUE);
...
call_sv(cvrv, G_VOID|G_NOARGS); eval_pv используется для компиляции анонимной подпрограммы, которая также будет являться результатом (подробнее о eval_pv в "eval_pv" в perlapi). После получения этой ссылки на код её можно использовать во всех предыдущих примерах.
ЛЕГКИЕ ОБРАТНЫЕ ВЫЗОВЫ
Иногда вам нужно многократно вызывать одну и ту же подпрограмму. Это обычно происходит с функцией, которая действует на списке значений, такой как встроенная функция Perl sort(). Вы можете передать функцию сравнения в sort(), которая затем будет вызываться для каждой пары значений, которые необходимо сравнить. Функции first() и reduce() из List::Util следуют аналогичной схеме.
В этом случае можно ускорить процедуру (часто довольно существенно) с помощью API легких обратных вызовов. Идея состоит в том, что контекст вызова необходимо создать и уничтожить только один раз, а подпрограмму можно вызывать произвольное количество раз между ними.
Обычно параметры передаются с помощью глобальных переменных (обычно $_ для одного параметра или $a и $b для двух параметров), а не через @_. (Возможно использование механизма @_ если вы знаете, что делаете, хотя для него пока нет поддерживаемого API. Также это в целом медленнее.)
Шаблон вызова макросов выглядит следующим образом:
dMULTICALL; /* Declare local variables */
U8 gimme = G_SCALAR; /* context of the call: G_SCALAR,
* G_ARRAY, or G_VOID */
PUSH_MULTICALL(cv); /* Set up the context for calling cv,
and set local vars appropriately */
/* loop */ {
/* set the value(s) af your parameter variables */
MULTICALL; /* Make the actual call */
} /* end of loop */
POP_MULTICALL; /* Tear down the calling context */ Для некоторых конкретных примеров см. реализацию функций first() и reduce() из List::Util 1.18. Там вы также найдете заголовочный файл, который эмулирует API multicall на более старых версиях perl.
См. также
АВТОР
Пол Маркесс
Особая благодарность следующим людям, которые помогли в создании документации.
Джефф Окамото, Тим Бэнс, Ник Джианниотис, Стив Келем, Гурусами Сарати и Ларри Уолл.
ДАТА
Последнее обновление для perl 5.23.1.
© 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/perlcall