Spec-Zone.ru › Perl 5.32

perlxs

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ОПИСАНИЕ
    • Введение
    • В дороге
    • Структура XSUB
    • Стек аргументов
    • Переменная RETVAL
    • Возвращение SVs, AVs и HVs через RETVAL
    • Ключевое слово MODULE
    • Ключевое слово PACKAGE
    • Ключевое слово PREFIX
    • Ключевое слово OUTPUT:
    • Ключевое слово NO_OUTPUT
    • Ключевое слово CODE:
    • Ключевое слово INIT:
    • Ключевое слово NO_INIT
    • Ключевое слово TYPEMAP:
    • Инициализация параметров функции
    • Значения параметров по умолчанию
    • Ключевое слово PREINIT:
    • Ключевое слово SCOPE:
    • Ключевое слово INPUT:
    • Ключевые слова IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT
    • Ключевое слово length(NAME)
    • Списки параметров переменной длины
    • Ключевое слово C_ARGS:
    • Ключевое слово PPCODE:
    • Возвращение undef и пустых списков
    • Ключевое слово REQUIRE:
    • Ключевое слово CLEANUP:
    • Ключевое слово POSTCALL:
    • Ключевое слово BOOT:
    • Ключевое слово VERSIONCHECK:
    • Ключевое слово PROTOTYPES:
    • Ключевое слово PROTOTYPE:
    • Ключевое слово ALIAS:
    • Ключевое слово OVERLOAD:
    • Ключевое слово FALLBACK:
    • Ключевое слово INTERFACE:
    • Ключевое слово INTERFACE_MACRO:
    • Ключевое слово INCLUDE:
    • Ключевое слово INCLUDE_COMMAND:
    • Ключевое слово CASE:
    • Ключевое слово EXPORT_XSUB_SYMBOLS:
    • Унарный оператор &
    • Вставка POD, комментариев и директив препроцессора C
    • Использование XS с C++
    • Стратегия интерфейса
    • Объекты Perl и структуры C
    • Безопасное хранение статических данных в XS
      • Ссылка MY_CXT
    • Интерфейсы систем, учитывающие потоки
  • ПРИМЕРЫ
  • ЗАМЕЧАНИЯ
  • ВЕРСИЯ XS
  • АВТОР

НАЗВАНИЕ

perlxs - Справочник по языку XS

ОПИСАНИЕ

Введение

Формат описания интерфейса XS используется для создания интерфейса расширения между Perl и кодом C (или библиотекой C), который вы хотите использовать с Perl. Интерфейс XS объединяется с библиотекой, чтобы создать новую библиотеку, которую затем можно загрузить динамически или статически связать с perl. Описание интерфейса XS написано на языке XS и является основной частью интерфейса расширения Perl.

Перед написанием XS прочитайте раздел "ЗАМЕЧАНИЯ" ниже.

XSUB является основной единицей интерфейса XS. После компиляции с помощью компилятора xsubpp каждый XSUB представляет собой определение функции C, которая обеспечит связующее звено между соглашениями о вызовах Perl и C.

Код связующего звена извлекает аргументы из стека Perl, преобразует эти значения Perl в форматы, ожидаемые функцией C, вызывает эту функцию C, передает возвращаемые значения функции C обратно в Perl. Возвращаемые значения здесь могут быть обычным возвращаемым значением C или любыми аргументами функции C, которые могут служить параметрами вывода. Эти возвращаемые значения могут передаваться обратно в Perl, помещая их в стек Perl или изменяя аргументы, предоставленные со стороны Perl.

Вышеприведенное представление несколько упрощено. Поскольку Perl допускает более гибкие соглашения о вызовах, чем C, XSUB могут делать гораздо больше на практике, например, проверять входные параметры на корректность, выбрасывать исключения (или возвращать undef/пустой список), если возвращаемое значение из функции C указывает на ошибку, вызывать разные функции C в зависимости от количества и типов аргументов, предоставлять объектно-ориентированный интерфейс и т. д.

Конечно, можно написать такой код связующего звена напрямую на C. Однако это была бы трудоемкая задача, особенно если необходимо написать связующее звено для нескольких функций C и/или если вы недостаточно знакомы с дисциплиной стека Perl и другими подобными аспектами. XS приходит на помощь: вместо написания этого кода связующего звена вручную, можно написать более краткое краткое описание того, что должно сделать связующее звено, и позволить компилятору XS xsubpp выполнить остальную работу.

Язык XS позволяет описать соответствие между тем, как используется функция C, и тем, как используется соответствующая функция Perl. Он также позволяет создавать функции Perl, которые напрямую преобразуются в код C и не связаны с предварительно существующей функцией C. В случаях, когда C-интерфейс совпадает с Perl-интерфейсом, объявление XSUB почти идентично объявлению функции C (в стиле K&R). В таких обстоятельствах существует другой инструмент под названием h2xs, который способен преобразовать весь заголовочный файл C в соответствующий файл XS, который обеспечит связующее звено для функций/макросов, описанных в заголовочном файле.

Компилятор XS называется xsubpp. Этот компилятор создает конструкции, необходимые для того, чтобы XSUB манипулировал значениями Perl, и создает связующее звено, необходимое для того, чтобы Perl вызывал XSUB. Компилятор использует typemaps для определения того, как сопоставлять параметры функции C и возвращаемые значения с значениями Perl и обратно. Типовое сопоставление (которое поставляется с Perl) обрабатывает многие распространенные типы C. Также может потребоваться дополнительное сопоставление типов для обработки каких-либо специальных структур и типов для связываемой библиотеки. Дополнительную информацию о сопоставлении типов см. в perlxstypemap.

Файл в формате XS начинается с раздела кода C, который продолжается до первой директивы MODULE =. После этой строки могут следовать другие директивы XS и определения XSUB. "Язык", используемый в этой части файла, обычно называется языком XS. xsubpp распознает и пропускает POD (см. perlpod) в разделах C и XS языка, что позволяет файлу XS содержать встроенную документацию.

См. perlxstut для получения руководства по всему процессу создания расширения.

Примечание: для некоторых расширений система SWIG Дэва Бисли может предоставить значительно более удобный механизм для создания кода связующего звена расширения. См. http://www.swig.org/ для получения дополнительной информации.

Для простых связей с библиотеками C, а также другими библиотеками машинного кода, рассмотрите использование более простого интерфейса libffi через модули CPAN, такие как FFI::Platypus или FFI::Raw.

В дороге

Многие последующие примеры будут сосредоточены на создании интерфейса между Perl и библиотекой функций привязки ONC+ RPC. Функция rpcb_gettime() используется для демонстрации многих функций языка XS. Эта функция имеет два параметра: первый — входной параметр, а второй — выходной параметр. Функция также возвращает значение состояния.

bool_t rpcb_gettime(const char *host, time_t *timep);

В C эта функция будет вызываться с помощью следующих операторов.

#include <rpc/rpc.h>
bool_t status;
time_t timep;
status = rpcb_gettime( "localhost", &timep );

Если XSUB создается для обеспечения прямого преобразования между этой функцией и Perl, то этот XSUB будет использоваться из Perl с помощью следующего кода. Переменные $status и $timep будут содержать результат работы функции.

use RPC;
$status = rpcb_gettime( "localhost", $timep );

Следующий файл XS демонстрирует подпрограмму XS или XSUB, которая демонстрирует один из возможных интерфейсов к функции rpcb_gettime(). Этот XSUB представляет собой прямое преобразование между C и Perl, поэтому сохраняет интерфейс даже из Perl. Этот XSUB будет вызываться из Perl с использованием показанного выше использования. Обратите внимание, что первые три оператора #include, для EXTERN.h, perl.h и XSUB.h, всегда будут присутствовать в начале файла XS. Этот подход и другие будут расширены позднее в этом документе. #define для PERL_NO_GET_CONTEXT должен присутствовать для более эффективного извлечения контекста интерпретатора, см. perlguts для получения подробностей.

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <rpc/rpc.h>

MODULE = RPC  PACKAGE = RPC

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep
   OUTPUT:
     timep

Любое расширение Perl, включая те, которые содержат XSUB, должно иметь модуль Perl, который служит загрузчиком, подключающим расширение к Perl. Этот модуль будет экспортировать функции и переменные расширения в программу Perl и вызовет связывание XSUB расширения в Perl. Следующий модуль будет использоваться в большинстве примеров в этом документе и должен использоваться из Perl с командой use, как показано ранее. Модули Perl более подробно описаны далее в этом документе.

package RPC;

require Exporter;
require DynaLoader;
@ISA = qw(Exporter DynaLoader);
@EXPORT = qw( rpcb_gettime );

bootstrap RPC;
1;

В этом документе будет изучено множество интерфейсов к XSUB rpcb_gettime(). XSUB будут принимать параметры в разном порядке или с разным количеством параметров. В каждом случае XSUB является абстракцией между Perl и реальной функцией C rpcb_gettime(), и XSUB всегда должен гарантировать, что реальная функция rpcb_gettime() вызывается с правильными параметрами. Эта абстракция позволит программисту создать более Perl-подобный интерфейс к функции C.

Структура XSUB

Самые простые XSUB состоят из 3 частей: описание возвращаемого значения, имя функции XSUB и имена её аргументов, и описание типов или форматов аргументов.

Следующий XSUB позволяет программе Perl получить доступ к функции библиотеки C под названием sin(). XSUB будет имитировать функцию C, которая принимает один аргумент и возвращает одно значение.

double
sin(x)
  double x

По желанию, можно объединить описание типов и список имён аргументов, переписав это как

double
sin(double x)

Это делает этот XSUB похожим на объявление ANSI C. После списка аргументов разрешается необязательная точка с запятой, как в

double
sin(double x);

Параметры с типами указателей C могут иметь разную семантику: функции C с похожими объявлениями

bool string_looks_as_a_number(char *s);
bool make_char_uppercase(char *c);

используются абсолютно несовместимым образом. Параметры этих функций могут быть описаны xsubpp так:

char *  s
char    &c

Оба этих объявления XS соответствуют типу C char*, но они имеют разную семантику, см. "Оператор &".

Удобно считать, что оператор косвенного обращения * следует рассматривать как часть типа, а оператор получения адреса & следует рассматривать как часть переменной. Для получения дополнительной информации о обработке квалификаторов и унарных операторов в типах C см. perlxstypemap.

Имя функции и тип возвращаемого значения должны располагаться на отдельных строках и выравниваться по левому краю.

INCORRECT                        CORRECT

double sin(x)                    double
  double x                       sin(x)
                                   double x

Остальная часть описания функции может быть отступающей или выровненной по левому краю. Следующий пример показывает функцию с её телом, выровненным по левому краю. В большинстве примеров в этом документе тело будет отступать для лучшей читабельности.

CORRECT

double
sin(x)
double x

Более сложные XSUB могут содержать много других разделов. Каждый раздел XSUB начинается с соответствующего ключевого слова, такого как INIT: или CLEANUP:. Однако первые две строки XSUB всегда содержат одинаковые данные: описания типа возвращаемого значения и имён функции и её параметров. Всё, что следует непосредственно за этим, рассматривается как раздел INPUT:, если явно не помечено другим ключевым словом. (См. "Ключевое слово INPUT:".)

Раздел XSUB продолжается до тех пор, пока не будет найдено другое ключевое слово начала раздела.

Стек аргументов

Стек аргументов Perl используется для хранения значений, которые передаются в качестве параметров XSUB, и для хранения возвращаемого значения(ей) XSUB. На самом деле все функции Perl (включая не-XSUB) хранят свои значения в этом стеке всё время, каждая ограничена своим диапазоном позиций в стеке. В этом документе первая позиция в этом стеке, которая принадлежит активной функции, будет обозначаться как позиция 0 для этой функции.

XSUB ссылаются на свои аргументы стека с макросом ST(x), где x относится к позиции в части стека этого XSUB. Позиция 0 для этой функции будет известна XSUB как ST(0). Входящие параметры XSUB и возвращаемые значения всегда начинаются с ST(0). Для многих простых случаев компилятор xsubpp сгенерирует код, необходимый для обработки стека аргументов, встраивая фрагменты кода, найденные в typemaps. В более сложных случаях программист должен предоставить код.

Переменная RETVAL

Переменная RETVAL — это специальная переменная C, которая объявляется автоматически для вас. Тип C переменной RETVAL соответствует типу возвращаемого значения функции C. Компилятор xsubpp объявит эту переменную в каждом XSUB с не-void типом возврата. По умолчанию сгенерированная функция C будет использовать RETVAL для хранения возвращаемого значения вызываемой функции C. В простых случаях значение RETVAL будет помещено в ST(0) стека аргументов, где оно может быть получено Perl в качестве возвращаемого значения XSUB.

Если у XSUB тип возврата void, то компилятор не будет объявлять переменную RETVAL для этой функции. При использовании раздела PPCODE: манипуляции с переменной RETVAL не требуются, раздел может использовать прямую манипуляцию стеком для размещения выходных значений в стеке.

Если директива PPCODE: не используется, void возвращаемое значение должно использоваться только для подпрограмм, которые не возвращают значение, даже если используется директива CODE:, которая явно устанавливает ST(0).

Старые версии этого документа рекомендовали использовать void возвращаемое значение в таких случаях. Было обнаружено, что это может привести к сегментации в случаях, когда XSUB на самом деле void. Эта практика теперь устарела и может не поддерживаться в будущих версиях. Используйте возвращаемое значение SV * в таких случаях. (В настоящее время xsubpp содержит некоторый эвристический код, который пытается различать "настоящие пустые" и "объявленные как пустые по старой практике" функции. Таким образом, ваш код зависит от этой эвристики, если вы не используете SV * как возвращаемое значение.)

Возвращение SVs, AV и HV через RETVAL

Когда вы используете RETVAL для возвращения SV *, происходит некоторая магия за кулисами, о которой стоит упомянуть. Когда вы работаете со стеком аргументов с помощью макроса ST(x), например, вам обычно нужно уделить особое внимание счёту ссылок. (Подробнее о счёте ссылок см. perlguts.) Чтобы облегчить вам задачу, файл typemap автоматически делает RETVAL смертельным, когда вы возвращаете SV *. Таким образом, следующие два XSUB в большей или меньшей степени эквивалентны:

void
alpha()
    PPCODE:
        ST(0) = newSVpv("Hello World",0);
        sv_2mortal(ST(0));
        XSRETURN(1);

SV *
beta()
    CODE:
        RETVAL = newSVpv("Hello World",0);
    OUTPUT:
        RETVAL

Это довольно полезно, так как обычно улучшает читабельность. Хотя это работает хорошо для SV *, к сожалению, не так просто иметь AV * или HV * как возвращаемое значение. Вы должны иметь возможность написать:

AV *
array()
    CODE:
        RETVAL = newAV();
        /* do something with RETVAL */
    OUTPUT:
        RETVAL

Но из-за неисправимой ошибки (исправление её сломало бы много существующих модулей CPAN) в файле typemap счётчик ссылок AV * не корректно уменьшается. Таким образом, вышеприведенный XSUB будет утечка памяти всякий раз, когда он вызывается. Та же проблема существует для HV *, CV * и SVREF (что указывает на скалярную ссылку, а не на общую SV *). В коде XS на perl, начиная с perl 5.16, вы можете переопределить typemaps для любого из этих типов с версией, которая имеет правильную обработку счётчиков ссылок. В вашем разделе TYPEMAP сделайте

AV*   T_AVREF_REFCOUNT_FIXED

чтобы получить исправленный вариант. Для обратной совместимости со старыми версиями Perl, вы можете вместо этого вручную уменьшить счётчик ссылок, когда вы возвращаете один из перечисленных типов, используя sv_2mortal:

AV *
array()
    CODE:
        RETVAL = newAV();
        sv_2mortal((SV*)RETVAL);
        /* do something with RETVAL */
    OUTPUT:
        RETVAL

Помните, что вам не нужно делать это для SV *. Справочная документация по всем core typemaps находится в perlxstypemap.

Ключевое слово MODULE

Ключевое слово MODULE используется для запуска кода XS и для указания пакета функций, которые определяются. Весь текст перед первым ключевым словом MODULE рассматривается как код C и передаётся на вывод с удалённым POD, но в остальном без изменений. Каждый модуль XS будет иметь функцию загрузки, которая используется для подключения XSUB к Perl. Имя пакета этой функции загрузки будет соответствовать значению последнего оператора MODULE в файлах исходного кода XS. Значение MODULE должно всегда оставаться постоянным в пределах одного файла XS, хотя это не требуется.

Следующий пример запустит код XS и поместит все функции в пакет под названием RPC.

MODULE = RPC

Ключевое слово PACKAGE

Когда функции в файле исходного кода XS должны быть разделены на пакеты, следует использовать ключевое слово PACKAGE. Это ключевое слово используется вместе с ключевым словом MODULE и должно следовать сразу после него при использовании.

MODULE = RPC  PACKAGE = RPC

[ XS code in package RPC ]

MODULE = RPC  PACKAGE = RPCB

[ XS code in package RPCB ]

MODULE = RPC  PACKAGE = RPC

[ XS code in package RPC ]

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

Хотя это ключевое слово необязательно и в некоторых случаях предоставляет избыточную информацию, оно всегда должно использоваться. Это ключевое слово гарантирует, что XSUB появятся в нужном пакете.

Ключевое слово PREFIX

Ключевое слово PREFIX обозначает префиксы, которые должны быть удалены из имён функций Perl. Если функция C является rpcb_gettime(), а значение PREFIX равно rpcb_, то Perl увидит эту функцию как gettime().

Это ключевое слово должно следовать за ключевым словом PACKAGE при использовании. Если PACKAGE не используется, PREFIX должно следовать за ключевым словом MODULE.

MODULE = RPC  PREFIX = rpc_

MODULE = RPC  PACKAGE = RPCB  PREFIX = rpcb_

Ключевое слово OUTPUT:

Ключевое слово OUTPUT: указывает, что определённые параметры функций должны быть обновлены (новые значения станут видимыми для Perl), когда XSUB завершается или что определённые значения должны быть возвращены вызывающей функции Perl. Для простых функций, которые не имеют разделов CODE: или PPCODE:, таких как функция sin() выше, переменная RETVAL автоматически обозначается как выходное значение. Для более сложных функций компилятору xsubpp потребуется помощь, чтобы определить, какие переменные являются выходными переменными.

Это ключевое слово обычно используется для дополнения ключевого слова CODE:. Переменная RETVAL не распознаётся как выходная переменная, когда присутствует ключевое слово CODE:. Ключевое слово OUTPUT: используется в этой ситуации, чтобы сказать компилятору, что RETVAL действительно является выходной переменной.

Ключевое слово OUTPUT: также можно использовать для указания того, что параметры функции являются выходными переменными. Это может потребоваться, когда параметр был изменён внутри функции, и программист хочет, чтобы обновление было видно Perl.

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep
   OUTPUT:
     timep

Ключевое слово OUTPUT: также позволит сопоставить выходной параметр с соответствующим куском кода, а не с typemap.

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep
   OUTPUT:
     timep sv_setnv(ST(1), (double)timep);

xsubpp генерирует автоматическое SvSETMAGIC() для всех параметров в разделе OUTPUT XSUB, кроме RETVAL. Это обычно желаемое поведение, так как оно обеспечивает правильное вызов магии 'set' для выходных параметров (необходимой для параметров типа хэш или массив, которые должны быть созданы, если они не существовали). Если по какой-либо причине это поведение нежелательно, раздел OUTPUT может содержать строку SETMAGIC: DISABLE, чтобы отключить его для оставшихся параметров в разделе OUTPUT. Аналогично, SETMAGIC: ENABLE может быть использована для его повторного включения для оставшейся части раздела OUTPUT. Подробнее о магии 'set' см. perlguts.

Ключевое слово NO_OUTPUT

Ключевое слово NO_OUTPUT может быть помещено в качестве первого токена XSUB. Это ключевое слово указывает, что, хотя C-подпрограмма, к которой мы предоставляем интерфейс, имеет тип возвращаемого значения, отличного от void, значение возвращаемое этой C-подпрограммой не должно возвращаться из сгенерированной Perl-подпрограммы.

При наличии этого ключевого слова "Переменная RETVAL" создается, и в сгенерированном вызове подпрограммы эта переменная присваивается, но значение этой переменной не будет использоваться в автоматически сгенерированном коде.

Это ключевое слово имеет смысл только если RETVAL будет доступна коду, написанному пользователем. Это особенно полезно для создания более Perl-подобного интерфейса функции, особенно когда значение возврата C является просто индикатором состояния ошибки. Например,

NO_OUTPUT int
delete_file(char *name)
  POSTCALL:
    if (RETVAL != 0)
        croak("Error %d while deleting file '%s'", RETVAL, name);

Здесь сгенерированная XS-функция ничего не возвращает при успехе и будет вызывать die() с осмысленным сообщением об ошибке при ошибке.

Ключевое слово CODE:

Это ключевое слово используется в более сложных XSUB, которые требуют специальной обработки для C-функции. Переменная RETVAL все равно объявляется, но она не будет возвращена, если это не указано в разделе OUTPUT:

Следующий XSUB предназначен для C-функции, которая требует специальной обработки ее параметров. Сначала приведен пример использования в Perl.

$status = rpcb_gettime( "localhost", $timep );

Следующий XSUB.

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t timep
   CODE:
          RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

Ключевое слово INIT:

Ключевое слово INIT: позволяет вставить инициализацию в XSUB перед тем, как компилятор сгенерирует вызов C-функции. В отличие от ключевого слова CODE: выше, это ключевое слово не влияет на то, как компилятор обрабатывает RETVAL.

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t &timep
    INIT:
      printf("# Host is %s\n", host );
    OUTPUT:
      timep

Другое использование раздела INIT: проверка предварительных условий перед вызовом C-функции:

long long
lldiv(a,b)
    long long a
    long long b
  INIT:
    if (a == 0 && b == 0)
        XSRETURN_UNDEF;
    if (b == 0)
        croak("lldiv: cannot divide by 0");

Ключевое слово NO_INIT

Ключевое слово NO_INIT используется для указания того, что параметр функции используется только как значение выхода. Компилятор xsubpp обычно генерирует код для чтения значений всех параметров функции из стека аргументов и присваивания их C-переменным при входе в функцию. NO_INIT сообщит компилятору, что некоторые параметры будут использоваться для выхода, а не для входа, и что они будут обработаны до завершения функции.

Следующий пример показывает изменение функции rpcb_gettime(). Эта функция использует переменную timep только как выходную переменную и не заботится о ее начальном содержимом.

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep = NO_INIT
   OUTPUT:
     timep

Ключевое слово TYPEMAP:

Начиная с Perl 5.16, вы можете встраивать typemaps в свой XS-код вместо или дополнительно к typemaps в отдельном файле. Несколько таких встроенных typemaps будут обрабатываться в порядке их появления в XS-коде и, как локальные typemaps файлы, имеют приоритет над стандартным typemap, встроенные typemaps могут перезаписывать предыдущие определения разделов TYPEMAP, INPUT и OUTPUT. Синтаксис встроенных typemaps

TYPEMAP: <<HERE
... your typemap code here ...
HERE

где ключевое слово TYPEMAP должно находиться в первом столбце новой строки.

Подробности о написании typemaps см. в perlxstypemap.

Инициализация параметров функции

Параметры C-функции обычно инициализируются своими значениями из стека аргументов (который, в свою очередь, содержит параметры, переданные XSUB из Perl). Typemaps содержат фрагменты кода, используемые для преобразования значений Perl в параметры C. Однако программист имеет право переопределить typemaps и предоставить альтернативный (или дополнительный) код инициализации. Код инициализации начинается с первой =, ; или + в строке в разделе INPUT:. Единственное исключение происходит, если это ; завершает строку, тогда эта ; спокойно игнорируется.

Следующий код демонстрирует, как предоставить код инициализации для параметров функции. Код инициализации выполняется в двойных кавычках компилятором перед добавлением в вывод, поэтому все, что должно быть интерпретировано буквально [в основном $, @ или \\], должно быть защищено обратными слэшами. Переменные $var, $arg и $type могут быть использованы так же, как в typemaps.

bool_t
rpcb_gettime(host,timep)
     char *host = (char *)SvPV_nolen($arg);
     time_t &timep = 0;
   OUTPUT:
     timep

Это не должно использоваться для предоставления значений по умолчанию для параметров. Обычно это используется, когда параметр функции должен быть обработан другой функцией библиотеки перед использованием. Параметры по умолчанию рассматриваются в следующем разделе.

Если инициализация начинается с =, то она выводится в объявлении входной переменной, заменяя инициализацию, предоставленную typemap. Если инициализация начинается с ; или +, то она выполняется после объявления всех входных переменных. В случае ; инициализация, обычно предоставляемая typemap, не выполняется. В случае + объявление переменной будет включать инициализацию из typemap. Глобальная переменная %v доступна для действительно редкого случая, когда информация из одной инициализации требуется в другой инициализации.

Вот действительно редкий пример:

bool_t
rpcb_gettime(host,timep)
     time_t &timep; /* \$v{timep}=@{[$v{timep}=$arg]} */
     char *host + SvOK($v{timep}) ? SvPV_nolen($arg) : NULL;
   OUTPUT:
     timep

Конструкция \$v{timep}=@{[$v{timep}=$arg]} в приведённом выше примере имеет двойное назначение: во-первых, при обработке этой строки xsubpp, выполняется Perl-фрагмент $v{timep}=$arg. Во-вторых, текст вычисленного фрагмента выводится в сгенерированный C-файл (внутри C-комментария)! Во время обработки строки char *host, $arg будет равняться ST(0), а $v{timep} будет равняться ST(1).

Значения параметров по умолчанию

Значения параметров XSUB по умолчанию можно указать, поместив оператор присваивания в список параметров. Значение по умолчанию может быть числом, строкой или специальной строкой NO_INIT. Значения по умолчанию всегда должны использоваться только для правых параметров.

Чтобы разрешить XSUB для rpcb_gettime() иметь значение host по умолчанию, параметры XSUB можно переупорядочить. Затем XSUB будет вызывать реальную функцию rpcb_gettime() с параметрами в правильном порядке. Этот XSUB можно вызывать из Perl с помощью следующих команд:

$status = rpcb_gettime( $timep, $host );

$status = rpcb_gettime( $timep );

XSUB будет выглядеть как код, который следует. Используется блок CODE: для вызова реальной функции rpcb_gettime() с параметрами в правильном порядке для этой функции.

bool_t
rpcb_gettime(timep,host="localhost")
     char *host
     time_t timep = NO_INIT
   CODE:
          RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

Ключевое слово PREINIT:

Ключевое слово PREINIT: позволяет объявлять дополнительные переменные непосредственно перед или после объявления параметров из раздела INPUT:

Если переменная объявлена внутри раздела CODE:, она будет следовать за любым кодом typemap, который выводится для входных параметров. Это может привести к тому, что объявление окажется после C-кода, что является синтаксической ошибкой C. Похожие ошибки могут возникнуть, если используется явная инициализация типа ; или + параметров (см. "Инициализация параметров функции"). Объявление этих переменных в разделе INIT: не поможет.

В таких случаях, чтобы принудительно объявить дополнительную переменную вместе с объявлениями других переменных, поместите объявление в раздел PREINIT:. Ключевое слово PREINIT: может использоваться один или несколько раз в XSUB.

Следующие примеры эквивалентны, но если код использует сложные typemaps, то первый пример безопаснее.

bool_t
rpcb_gettime(timep)
     time_t timep = NO_INIT
   PREINIT:
     char *host = "localhost";
   CODE:
     RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

Для этого конкретного случая ключевое слово INIT: сгенерирует тот же C-код, что и ключевое слово PREINIT:. Еще один правильный, но подверженный ошибкам пример:

bool_t
rpcb_gettime(timep)
     time_t timep = NO_INIT
   CODE:
     char *host = "localhost";
     RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

Другой способ объявить host - использовать C-блок в разделе CODE:

bool_t
rpcb_gettime(timep)
     time_t timep = NO_INIT
   CODE:
     {
       char *host = "localhost";
       RETVAL = rpcb_gettime( host, &timep );
     }
   OUTPUT:
     timep
     RETVAL

Возможность поместить дополнительные объявления перед обработкой записей typemap очень полезна в тех случаях, когда преобразования typemap изменяют некоторое глобальное состояние:

MyObject
mutate(o)
    PREINIT:
        MyState st = global_state;
    INPUT:
        MyObject o;
    CLEANUP:
        reset_to(global_state, st);

Здесь мы предполагаем, что преобразование в MyObject в разделе INPUT: и из MyObject при обработке RETVAL будет изменять глобальную переменную global_state. После выполнения этих преобразований мы восстанавливаем старое значение global_state (например, для предотвращения утечек памяти).

Есть еще один способ обменять ясность на компактность: разделы INPUT позволяют объявить C-переменные, которые не появляются в списке параметров подпрограммы. Таким образом, приведенный выше код для mutate() может быть переписан как

MyObject
mutate(o)
      MyState st = global_state;
      MyObject o;
    CLEANUP:
      reset_to(global_state, st);

и код для rpcb_gettime() может быть переписан как

bool_t
rpcb_gettime(timep)
     time_t timep = NO_INIT
     char *host = "localhost";
   C_ARGS:
     host, &timep
   OUTPUT:
     timep
     RETVAL

Ключевое слово SCOPE:

Ключевое слово SCOPE: позволяет включить ограничение области действия для конкретного XSUB. При включении XSUB автоматически вызовет ENTER и LEAVE.

Для поддержки потенциально сложных typemapping, если запись typemap, используемая XSUB, содержит комментарий, подобный /*scope*/, то ограничение области действия будет автоматически включено для этого XSUB.

Для включения ограничение области действия:

SCOPE: ENABLE

Для отключения ограничение области действия:

SCOPE: DISABLE

Ключевое слово INPUT:

Параметры XSUB обычно вычисляются сразу после входа в XSUB. Ключевое слово INPUT: может использоваться для принудительного вычисления этих параметров немного позже. Ключевое слово INPUT: может использоваться несколько раз внутри XSUB и может использоваться для перечисления одной или нескольких входных переменных. Это ключевое слово используется с ключевым словом PREINIT:

Следующий пример показывает, как входной параметр timep может быть вычислен позднее, после PREINIT.

bool_t
rpcb_gettime(host,timep)
      char *host
    PREINIT:
      time_t tt;
    INPUT:
      time_t timep
    CODE:
           RETVAL = rpcb_gettime( host, &tt );
           timep = tt;
    OUTPUT:
      timep
      RETVAL

Следующий пример показывает, что каждый входной параметр вычисляется позднее.

bool_t
rpcb_gettime(host,timep)
    PREINIT:
      time_t tt;
    INPUT:
      char *host
    PREINIT:
      char *h;
    INPUT:
      time_t timep
    CODE:
           h = host;
           RETVAL = rpcb_gettime( h, &tt );
           timep = tt;
    OUTPUT:
      timep
      RETVAL

Поскольку разделы INPUT позволяют объявить C-переменные, которые не появляются в списке параметров подпрограммы, это можно сократить до:

bool_t
rpcb_gettime(host,timep)
      time_t tt;
      char *host;
      char *h = host;
      time_t timep;
    CODE:
      RETVAL = rpcb_gettime( h, &tt );
      timep = tt;
    OUTPUT:
      timep
      RETVAL

(Мы использовали наши знания, что преобразование входных данных для char * является "простым", поэтому host инициализируется в строке объявления, и наше присваивание h = host не выполняется слишком рано. В противном случае нам потребовалось бы выполнить присваивание h = host в разделе CODE: или INIT:.)

Ключевые слова IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT

В списке параметров для XSUB можно поместить имена параметров перед ключевыми словами IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT. Ключевое слово IN является по умолчанию, другие ключевые слова указывают, как интерфейс Perl должен отличаться от интерфейса C.

Параметры, предваряемые ключевыми словами OUTLIST/IN_OUTLIST/OUT/IN_OUT, считаются используемыми подпрограммой C через указатели. Ключевые слова OUTLIST/OUT указывают, что подпрограмма C не проверяет память, на которую указывает этот параметр, но запишет через этот указатель дополнительные возвращаемые значения.

Параметры, предваряемые ключевым словом OUTLIST, не отображаются в сигнатуре используемой функции Perl.

Параметры, предваряемые IN_OUTLIST/IN_OUT/OUT, являются параметрами функции Perl. За исключением параметров OUT, эти параметры преобразуются в соответствующий тип C, а затем указатели на эти данные передаются в функцию C. Ожидается, что функция C запишет через эти указатели.

Список возвращаемых значений сгенерированной функции Perl состоит из значения возврата функции C (если тип возврата XSUB не void или не использовалось The NO_OUTPUT Keyword) плюс все параметры OUTLIST и IN_OUTLIST (в порядке появления). При возврате из XSUB параметр Perl IN_OUT/OUT будет изменён, чтобы содержать значения, записанные функцией C.

Например, XSUB

void
day_month(OUTLIST day, IN unix_time, OUTLIST month)
  int day
  int unix_time
  int month

должен использоваться из Perl как

my ($day, $month) = day_month(time);

Подпись функции C должна быть

void day_month(int *day, int unix_time, int *month);

Ключевые слова IN/OUTLIST/IN_OUTLIST/IN_OUT/OUT могут быть смешаны с объявлениями в стиле ANSI, как в

void
day_month(OUTLIST int day, int unix_time, OUTLIST int month)

(здесь необязательное ключевое слово IN опущено).

Параметры IN_OUT идентичны параметрам, введённым с помощью "Оператора & Unary" и помещены в раздел OUTPUT: (см. "Ключевое слово OUTPUT:"). Параметры IN_OUTLIST очень похожи, единственное различие состоит в том, что значение, которое функция C записывает через указатель, не изменит параметр Perl, а помещается в список вывода.

Параметр OUTLIST/OUT отличается от параметров IN_OUTLIST/IN_OUT только начальным значением параметра Perl, которое не считывается (и не передаётся функции C — она получает мусор вместо этого). Например, ту же функцию C, что и выше, можно связать как

void day_month(OUT int day, int unix_time, OUT int month);

или

void
day_month(day, unix_time, month)
    int &day = NO_INIT
    int  unix_time
    int &month = NO_INIT
  OUTPUT:
    day
    month

Однако, сгенерированная функция Perl вызывается в очень стиле C:

my ($day, $month);
day_month($day, time, $month);

Ключевое слово length(NAME)

Если одним из входных аргументов функции C является длина строкового аргумента NAME, то можно заменить имя аргумента длины на length(NAME) в объявлении XSUB. Этот аргумент должен быть опущен при вызове сгенерированной функции Perl. Например,

void
dump_chars(char *s, short l)
{
  short n = 0;
  while (n < l) {
      printf("s[%d] = \"\\%#03o\"\n", n, (int)s[n]);
      n++;
  }
}

MODULE = x            PACKAGE = x

void dump_chars(char *s, short length(s))

должен вызываться как dump_chars($string).

Эта директива поддерживается только с объявлениями функций в стиле ANSI.

Переменные списки параметров

XSUB может иметь переменные списки параметров, указав многоточие (...) в списке параметров. Это использование многоточия аналогично таковому в ANSI C. Программист может определить количество аргументов, переданных в XSUB, проверив переменную items, которую компилятор xsubpp предоставляет для всех XSUB. С помощью этого механизма можно создать XSUB, который принимает список параметров неопределённой длины.

Параметр host для XSUB rpcb_gettime() может быть необязательным, поэтому многоточие может быть использовано для указания того, что XSUB будет принимать переменное количество параметров. Perl должен уметь вызывать этот XSUB с любым из следующих утверждений.

$status = rpcb_gettime( $timep, $host );

$status = rpcb_gettime( $timep );

Код XS со знаками многоточия:

bool_t
rpcb_gettime(timep, ...)
     time_t timep = NO_INIT
   PREINIT:
     char *host = "localhost";
   CODE:
     if( items > 1 )
          host = (char *)SvPV_nolen(ST(1));
     RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

Ключевое слово C_ARGS:

Ключевое слово C_ARGS: позволяет создавать XSUB, имеющие различную последовательность вызова из Perl, чем из C, без необходимости писать разделы CODE: или PPCODE:. Содержимое параграфа C_ARGS: помещается в качестве аргумента вызываемой функции C без каких-либо изменений.

Например, предположим, что функция C объявлена как

symbolic nth_derivative(int n, symbolic function, int flags);

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

$second_deriv = $function->nth_derivative(2);

Для этого объявите XSUB как

symbolic
nth_derivative(function, n)
    symbolic        function
    int             n
  C_ARGS:
    n, function, default_flags

Ключевое слово PPCODE:

Ключевое слово PPCODE: является альтернативной формой ключевого слова CODE: и используется для того, чтобы сказать компилятору xsubpp, что программист предоставляет код для управления стеком аргументов для значений возврата XSUB. Иногда требуется, чтобы XSUB возвращал список значений, а не одно значение. В этих случаях необходимо использовать PPCODE:, а затем явным образом поместить список значений в стек. Ключевые слова PPCODE: и CODE: не должны использоваться вместе в одном XSUB.

Фактическое различие между разделами PPCODE: и CODE: заключается в инициализации макроса SP (который обозначает текущий указатель стека Perl), и в обработке данных в стеке при возвращении из XSUB. В разделах CODE: SP сохраняет значение, которое было при входе в XSUB: SP находится на указателе функции (который следует за последним параметром). В разделах PPCODE: SP перемещается назад к началу списка параметров, что позволяет макросам PUSH*() помещать выходные значения в место, которое Perl ожидает, когда XSUB возвращается обратно в Perl.

Сгенерированный трейлер для раздела CODE: гарантирует, что количество возвращаемых значений Perl будет либо 0, либо 1 (в зависимости от void значения возврата функции C и эвристики, упомянутой в "Переменная RETVAL"). Трейлер, сгенерированный для раздела PPCODE:, основан на количестве возвращаемых значений и на количестве обновлений SP макросами [X]PUSH*().

Обратите внимание, что макросы ST(i), XST_m*() и XSRETURN*() работают одинаково хорошо в разделах CODE: и PPCODE:.

Следующий XSUB вызовет функцию C rpcb_gettime() и вернёт Perl два выходных значения, timep и status, в виде одного списка.

void
rpcb_gettime(host)
     char *host
   PREINIT:
     time_t  timep;
     bool_t  status;
   PPCODE:
     status = rpcb_gettime( host, &timep );
     EXTEND(SP, 2);
     PUSHs(sv_2mortal(newSViv(status)));
     PUSHs(sv_2mortal(newSViv(timep)));

Обратите внимание, что программист должен предоставить код C, необходимый для вызова реальной функции rpcb_gettime() и для правильного размещения возвращаемых значений в стеке аргументов.

Тип возврата void для этой функции сообщает компилятору xsubpp, что переменная RETVAL не нужна или используется, и что она не должна создаваться. В большинстве сценариев тип возврата void следует использовать с директивой PPCODE:.

Макрос EXTEND() используется для создания места в стеке аргументов для 2 значений возврата. Директива PPCODE: заставляет компилятор xsubpp создать указатель стека, доступный как SP, и именно этот указатель используется в макросе EXTEND(). Значения затем помещаются в стек с помощью макроса PUSHs().

Теперь функцию rpcb_gettime() можно использовать из Perl с помощью следующего оператора.

($status, $timep) = rpcb_gettime("localhost");

При обработке выходных параметров с разделом PPCODE убедитесь, что вы правильно обрабатываете магию «set». Обратитесь к perlguts за подробностями о магии «set».

Возвращение undef и пустых списков

Иногда программист захочет просто вернуть undef или пустой список, если функция завершается неудачно, а не отдельное значение состояния. Функция rpcb_gettime() предлагает именно такую ситуацию. Если функция выполняется успешно, мы хотим вернуть время, а если нет — вернуть undef. В следующем коде Perl значение $timep будет либо undef, либо действительным временем.

$timep = rpcb_gettime( "localhost" );

Следующий XSUB использует тип возврата SV * только как мнемонику и использует блок CODE: для указания компилятору, что программист предоставил весь необходимый код. Вызов sv_newmortal() инициализирует значение возврата как undef, делая это значение возврата по умолчанию.

SV *
rpcb_gettime(host)
     char *  host
   PREINIT:
     time_t  timep;
     bool_t x;
   CODE:
     ST(0) = sv_newmortal();
     if( rpcb_gettime( host, &timep ) )
          sv_setnv( ST(0), (double)timep);

Следующий пример демонстрирует, как поместить явное undef в значение возврата, если это необходимо.

SV *
rpcb_gettime(host)
     char *  host
   PREINIT:
     time_t  timep;
     bool_t x;
   CODE:
     if( rpcb_gettime( host, &timep ) ){
          ST(0) = sv_newmortal();
          sv_setnv( ST(0), (double)timep);
     }
     else{
          ST(0) = &PL_sv_undef;
     }

Чтобы вернуть пустой список, необходимо использовать блок PPCODE:, а затем не помещать значения возврата в стек.

void
rpcb_gettime(host)
     char *host
   PREINIT:
     time_t  timep;
   PPCODE:
     if( rpcb_gettime( host, &timep ) )
          PUSHs(sv_2mortal(newSViv(timep)));
     else{
         /* Nothing pushed on stack, so an empty
          * list is implicitly returned. */
     }

Некоторые люди могут быть склонны включить явное return в вышеприведённый XSUB, а не позволять управлению падать до конца. В таких ситуациях нужно использовать XSRETURN_EMPTY вместо этого. Это гарантирует, что стек XSUB будет правильно откорректирован. Обратитесь к perlapi для других макросов XSRETURN.

Поскольку макросы XSRETURN_* могут использоваться с блоками CODE, можно переписать этот пример как:

int
rpcb_gettime(host)
     char *host
   PREINIT:
     time_t  timep;
   CODE:
     RETVAL = rpcb_gettime( host, &timep );
     if (RETVAL == 0)
           XSRETURN_UNDEF;
   OUTPUT:
     RETVAL

На самом деле, можно поместить эту проверку в раздел POSTCALL:. Вместе с упрощениями PREINIT: это приводит к:

int
rpcb_gettime(host)
     char *host
     time_t  timep;
   POSTCALL:
     if (RETVAL == 0)
           XSRETURN_UNDEF;

Ключевое слово REQUIRE:

Ключевое слово REQUIRE: используется для указания минимальной версии компилятора xsubpp, необходимой для компиляции модуля XS. Модуль XS, содержащий следующее утверждение, будет компилироваться только с версией xsubpp 1.922 или выше:

REQUIRE: 1.922

Ключевое слово CLEANUP:

Это ключевое слово можно использовать, когда XSUB требует специальных процедур очистки перед завершением. Когда используется ключевое слово CLEANUP:, оно должно следовать за любыми блоками CODE: или OUTPUT:, которые присутствуют в XSUB. Код, указанный для блока очистки, будет добавлен в качестве последних операторов в XSUB.

Ключевое слово POSTCALL:

Это ключевое слово можно использовать, когда XSUB требует специальных процедур, выполняемых после выполнения вызова подпрограммы C. Когда используется ключевое слово POSTCALL:, оно должно предшествовать блокам OUTPUT: и CLEANUP:, которые присутствуют в XSUB.

См. примеры в "Ключевое слово NO_OUTPUT" и "Возвращение undef и пустых списков".

Блок POSTCALL: не имеет большого смысла, когда вызов подпрограммы C предоставляется пользователем путём предоставления раздела CODE: или PPCODE:.

Ключевое слово BOOT:

Ключевое слово BOOT: используется для добавления кода в функцию загрузки расширения. Функция загрузки генерируется компилятором xsubpp и обычно содержит операторы, необходимые для регистрации всех XSUB с Perl. С помощью ключевого слова BOOT: программист может указать компилятору добавить дополнительные операторы в функцию загрузки.

Это ключевое слово может быть использовано в любое время после первого ключевого слова MODULE и должно находиться на отдельной строке. Первая пустая строка после ключевого слова завершит блок кода.

BOOT:
# The following message will be printed when the
# bootstrap function executes.
printf("Hello from the bootstrap!\n");

Ключевое слово VERSIONCHECK:

Ключевое слово VERSIONCHECK: соответствует параметрам xsubpp -versioncheck и -noversioncheck. Это ключевое слово переопределяет параметры командной строки. Проверка версий включена по умолчанию. Когда проверка версий включена, модуль XS попытается проверить, что его версия совпадает с версией модуля PM.

Для включения проверки версий:

VERSIONCHECK: ENABLE

Для отключения проверки версий:

VERSIONCHECK: DISABLE

Обратите внимание, что если версия модуля PM является значением NV (число с плавающей запятой), она будет преобразована в строку с возможной потерей точности (в настоящее время до девяти десятичных знаков), поэтому она может больше не совпадать с версией модуля XS. Рекомендуется привести объявление $VERSION к строковому виду, если используются длинные номера версий.

Ключевое слово PROTOTYPES:

Ключевое слово PROTOTYPES: соответствует параметрам xsubpp -prototypes и -noprototypes. Это ключевое слово переопределяет параметры командной строки. Прототипы отключены по умолчанию. Когда прототипы включены, XSUB будут получать Perl-прототипы. Это ключевое слово может быть использовано несколько раз в модуле XS для включения и отключения прототипов для различных частей модуля. Обратите внимание, что xsubpp будет напоминать вам, если вы не включите или не отключите прототипы явно, с:

Please specify prototyping behavior for Foo.xs (see perlxs manual)

Для включения прототипов:

PROTOTYPES: ENABLE

Для отключения прототипов:

PROTOTYPES: DISABLE

Ключевое слово PROTOTYPE:

Это ключевое слово аналогично ключевому слову PROTOTYPES: выше, но может использоваться для принудительного использования конкретного прототипа для XSUB. Это ключевое слово переопределяет все другие параметры и ключевые слова прототипов, но влияет только на текущий XSUB. Обратитесь к "Прототипы" в perlsub за информацией о Perl-прототипах.

bool_t
rpcb_gettime(timep, ...)
      time_t timep = NO_INIT
    PROTOTYPE: $;$
    PREINIT:
      char *host = "localhost";
    CODE:
              if( items > 1 )
                   host = (char *)SvPV_nolen(ST(1));
              RETVAL = rpcb_gettime( host, &timep );
    OUTPUT:
      timep
      RETVAL

Если прототипы включены, вы можете отключить их локально для данного XSUB, как показано в следующем примере:

void
rpcb_gettime_noproto()
    PROTOTYPE: DISABLE
...

Ключевое слово ALIAS:

Ключевое слово ALIAS: позволяет XSUB иметь два или более уникальных имени Perl и знать, какое из этих имен было использовано при вызове. Имена Perl могут быть полностью квалифицированы с именами пакетов. Каждому псевдониму присваивается индекс. Компилятор создаст переменную, называемую ix, которая содержит индекс псевдонима, который был использован. Когда XSUB вызывается с его объявленным именем, ix будет равно 0.

Следующий пример создаст псевдонимы FOO::gettime() и BAR::getit() для этой функции.

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t &timep
    ALIAS:
        FOO::gettime = 1
        BAR::getit = 2
    INIT:
      printf("# ix = %d\n", ix );
    OUTPUT:
      timep

Ключевое слово OVERLOAD:

Вместо написания перегруженного интерфейса с помощью чистого Perl, вы также можете использовать ключевое слово OVERLOAD для определения дополнительных имен Perl для ваших функций (как ключевое слово ALIAS: выше). Однако перегруженные функции должны быть определены таким образом, чтобы принимать количество параметров, предоставляемых системой перегрузки Perl. Для большинства методов перегрузки это будет три параметра; для функции nomethod - четыре. Однако битовые операторы &, |, ^ и ~ могут вызываться с тремя или пятью аргументами (см. overload).

Если какая-либо функция имеет ключевое слово OVERLOAD:, в генерируемом файле C, сгенерированном xsubpp, будут определены несколько дополнительных строк для регистрации с магией перегрузки.

Поскольку освященные объекты фактически хранятся в качестве RV, полезно использовать возможности typemap для предварительной обработки параметров и извлечения фактического SV, хранящегося внутри освященного RV. См. пример для T_PTROBJ_SPECIAL ниже.

Для использования ключевого слова OVERLOAD: создайте функцию XS, которая принимает три входных параметра (или используйте определение C-стиля '...') так:

SV *
cmp (lobj, robj, swap)
My_Module_obj    lobj
My_Module_obj    robj
IV               swap
OVERLOAD: cmp <=>
{ /* function defined here */}

В этом случае функция перегрузит оба оператора сравнения трех способов. Для всех операций перегрузки, использующих неалфавитные символы, вы должны набрать параметр без кавычек, разделяя несколько перегрузок пробелами. Обратите внимание, что "" (перегрузка строкового преобразования) должно вводиться как \"\" (т.е. экранируется).

Поскольку, как упоминалось выше, битовые операторы могут принимать дополнительные аргументы, вы можете использовать что-то вроде (lobj, robj, swap, ...) (с литералом ...) в качестве списка параметров.

Ключевое слово FALLBACK:

В дополнение к ключевому слову OVERLOAD, если вам нужно контролировать, как Perl автоматически генерирует отсутствующие перегруженные операторы, вы можете установить ключевое слово FALLBACK в разделе заголовка модуля, как показано ниже:

MODULE = RPC  PACKAGE = RPC

FALLBACK: TRUE
...

где FALLBACK может принимать любое из трех значений TRUE, FALSE или UNDEF. Если вы не устанавливаете значение FALLBACK при использовании OVERLOAD, оно по умолчанию является UNDEF. FALLBACK не используется, кроме как когда одна или несколько функций, использующих OVERLOAD, были определены. Более подробную информацию см. в "fallback" в overload.

Ключевое слово INTERFACE:

Это ключевое слово объявляет текущий XSUB как хранитель заданного вызова подписи. Если за этим ключевым словом следует какой-либо текст, он рассматривается как список функций, которые имеют эту подпись и должны быть прикреплены к текущему XSUB.

Например, если у вас есть 4 функции C multiply(), divide(), add(), subtract() со следующей подписью:

symbolic f(symbolic, symbolic);

Вы можете заставить их все использовать один и тот же XSUB, используя это:

symbolic
interface_s_ss(arg1, arg2)
    symbolic        arg1
    symbolic        arg2
INTERFACE:
    multiply divide
    add subtract

(Это полный код XSUB для 4 функций Perl!) Четыре сгенерированных функции Perl используют имена соответствующих функций C.

Преимущество этого подхода по сравнению с ключевым словом ALIAS: заключается в том, что нет необходимости в коде оператора switch, каждая функция Perl (которая использует один и тот же XSUB) знает, какую функцию C она должна вызвать. Кроме того, можно прикрепить дополнительную функцию remainder() во время выполнения, используя

CV *mycv = newXSproto("Symbolic::remainder",
                      XS_Symbolic_interface_s_ss, __FILE__, "$$");
XSINTERFACE_FUNC_SET(mycv, remainder);

скажем, из другого XSUB. (Этот пример предполагает, что не было секции INTERFACE_MACRO:, в противном случае вместо XSINTERFACE_FUNC_SET нужно использовать что-то другое, см. следующий раздел.)

Ключевое слово INTERFACE_MACRO:

Это ключевое слово позволяет определить INTERFACE с помощью другого способа извлечения указателя на функцию из XSUB. Текст, который следует за этим ключевым словом, должен содержать имена макросов, которые будут извлекать/устанавливать указатель на функцию. Макрос извлечения получает возвращаемый тип, CV* и XSANY.any_dptr для этого CV*. Макрос установки получает cv и указатель на функцию.

Значения по умолчанию - XSINTERFACE_FUNC и XSINTERFACE_FUNC_SET. Ключевое слово INTERFACE со списком пустых функций можно опустить, если используется ключевое слово INTERFACE_MACRO.

Предположим, что в предыдущем примере указатели на функции multiply(), divide(), add(), subtract() хранятся в глобальном массиве C fp[] с смещениями, равными multiply_off, divide_off, add_off, subtract_off. Тогда можно использовать

#define XSINTERFACE_FUNC_BYOFFSET(ret,cv,f) \
    ((XSINTERFACE_CVT_ANON(ret))fp[CvXSUBANY(cv).any_i32])
#define XSINTERFACE_FUNC_BYOFFSET_set(cv,f) \
    CvXSUBANY(cv).any_i32 = CAT2( f, _off )

в разделе C,

symbolic
interface_s_ss(arg1, arg2)
    symbolic        arg1
    symbolic        arg2
  INTERFACE_MACRO:
    XSINTERFACE_FUNC_BYOFFSET
    XSINTERFACE_FUNC_BYOFFSET_set
  INTERFACE:
    multiply divide
    add subtract

в разделе XSUB.

Ключевое слово INCLUDE:

Это ключевое слово может быть использовано для включения других файлов в модуль XS. Другие файлы могут содержать код XS. INCLUDE: также может использоваться для выполнения команды для генерации кода XS, который должен быть включен в модуль.

Файл Rpcb1.xsh содержит нашу функцию rpcb_gettime():

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t &timep
    OUTPUT:
      timep

Модуль XS может использовать INCLUDE: для включения этого файла.

INCLUDE: Rpcb1.xsh

Если параметры ключевого слова INCLUDE: следуют за символом pipe (|), компилятор интерпретирует параметры как команду. Эта функция слегка устарела в пользу директивы INCLUDE_COMMAND:, как описано ниже.

INCLUDE: cat Rpcb1.xsh |

Не используйте это для выполнения perl: INCLUDE: perl | выполнит perl, который окажется первым в вашем пути, а не обязательно тот же perl, который используется для выполнения xsubpp. См. "Ключевое слово INCLUDE_COMMAND:".

Ключевое слово INCLUDE_COMMAND:

Выполняет предоставленную команду и включает ее вывод в текущий документ XS. INCLUDE_COMMAND присваивает специальное значение токену $^X, так как он выполняет тот же интерпретатор perl, который выполняет xsubpp:

INCLUDE_COMMAND: cat Rpcb1.xsh

INCLUDE_COMMAND: $^X -e ...

Ключевое слово CASE:

Ключевое слово CASE: позволяет XSUB иметь несколько отдельных частей, при этом каждая часть действует как виртуальный XSUB. CASE: жадный, и если он используется, все остальные ключевые слова XS должны быть заключены в CASE:. Это означает, что ничего не может предшествовать первому CASE: в XSUB, а все, что следует за последним CASE:, включается в этот случай.

CASE: может переключаться с помощью параметра XSUB, с помощью переменной ix ALIAS: (см. "Ключевое слово ALIAS:") или, возможно, с помощью переменной items (см. "Списки параметров переменной длины"). Последний CASE: становится по умолчанию, если он не связан с условием. Следующий пример демонстрирует переключение CASE с помощью ix и функцией rpcb_gettime() с псевдонимом x_gettime(). Когда функция вызывается как rpcb_gettime(), ее параметры являются обычными (char *host, time_t *timep), но когда функция вызывается как x_gettime(), ее параметры меняются местами, (time_t *timep, char *host).

long
rpcb_gettime(a,b)
  CASE: ix == 1
    ALIAS:
      x_gettime = 1
    INPUT:
      # 'a' is timep, 'b' is host
      char *b
      time_t a = NO_INIT
    CODE:
           RETVAL = rpcb_gettime( b, &a );
    OUTPUT:
      a
      RETVAL
  CASE:
      # 'a' is host, 'b' is timep
      char *a
      time_t &b = NO_INIT
    OUTPUT:
      b
      RETVAL

Эту функцию можно вызвать с любой из следующих инструкций. Обратите внимание на разные списки аргументов.

$status = rpcb_gettime( $host, $timep );

$status = x_gettime( $timep, $host );

Ключевое слово EXPORT_XSUB_SYMBOLS:

Ключевое слово EXPORT_XSUB_SYMBOLS: — это то, что вам, вероятно, никогда не понадобится. В версиях perl, более ранних чем 5.16.0, это ключевое слово ничего не делает. Начиная с версии 5.16, символы XSUB больше не экспортируются по умолчанию. То есть, они являются static функциями. Если вы включаете

EXPORT_XSUB_SYMBOLS: ENABLE

в свой код XS, XSUB, следующие за этой строкой, не будут объявляться static. Вы можете позже отключить это с помощью

EXPORT_XSUB_SYMBOLS: DISABLE

что, опять же, является значением по умолчанию, которое вы, вероятно, никогда не измените. Вы не можете использовать это ключевое слово в версиях perl до 5.16, чтобы сделать XSUB static.

Унарный оператор &

Унарный оператор & в разделе INPUT: используется для указания xsubpp, что он должен преобразовать значение Perl в/из C с использованием типа C слева от &, но при вызове функции C предоставить указатель на это значение.

Это полезно, чтобы избежать блока CODE: для C-функции, которая принимает параметр по ссылке. Как правило, параметр не должен быть типом указателя (int или long, но не int* или long*).

Следующий XSUB сгенерирует некорректный C-код. Компилятор xsubpp преобразует его в код, который вызывает rpcb_gettime() с параметрами (char *host, time_t timep), но реальный rpcb_gettime() ожидает, что параметр timep будет типа time_t*, а не time_t.

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t timep
    OUTPUT:
      timep

Эта проблема исправляется с помощью оператора &. Компилятор xsubpp теперь преобразует это в код, который правильно вызывает rpcb_gettime() с параметрами (char *host, time_t *timep). Он делает это, передавая &, поэтому вызов функции выглядит так: rpcb_gettime(host, &timep).

bool_t
rpcb_gettime(host,timep)
      char *host
      time_t &timep
    OUTPUT:
      timep

Вставка POD, комментариев и директив препроцессора C

Директивы препроцессора C разрешены в блоках BOOT:, PREINIT:, INIT:, CODE:, PPCODE:, POSTCALL: и CLEANUP:, а также вне функций. Комментарии разрешены в любом месте после ключевого слова MODULE. Компилятор передаст директивы препроцессора без изменений и удалит прокомментированные строки. Документация POD разрешена в любой точке, как в C, так и в XS-разделах. POD должен завершаться командой =cut; xsubpp завершится с ошибкой, если этого не произойдет. Вероятность того, что сгенерированный человеком C-код будет ошибочно принят за POD, очень низка, так как большинство стилей форматирования кода включают пробелы перед любой строкой, начинающейся с =. Сгенерированные машиной XS-файлы могут попасть в эту ловушку, если не позаботиться о том, чтобы пробел разрывал последовательность "\n=".

Комментарии к XSUB можно добавить, поместив # в качестве первого символа, отличного от пробела, в строке. Следует быть осторожным, чтобы комментарий не выглядел как директива препроцессора C, чтобы он не был интерпретирован как таковой. Самый простой способ предотвратить это - вставить пробелы перед #.

Если вы используете директивы препроцессора для выбора одной из двух версий функции, используйте

#if ... version1
#else /* ... version2  */
#endif

а не

#if ... version1
#endif
#if ... version2
#endif

потому что в противном случае xsubpp посчитает, что вы создали дублированное определение функции. Также поместите пустую строку перед #else/#endif, чтобы она не считалась частью тела функции.

Использование XS с C++

Если имя XSUB содержит ::, оно считается C++-методом. Сгенерированная Perl-функция предположит, что ее первый аргумент — это указатель на объект. Указатель на объект будет храниться в переменной THIS. Объект должен быть создан в C++ с помощью функции new() и должен быть освящен Perl с помощью макроса sv_setref_pv(). Осенение объекта Perl может быть обработано с помощью typemap. Пример typemap показан в конце этого раздела.

Если тип возвращаемого значения XSUB включает static, метод считается статическим методом. Он будет вызывать C++-функцию с помощью синтаксиса class::method(). Если метод не статический, функция будет вызываться с помощью синтаксиса THIS->method().

В следующих примерах будет использоваться следующий C++-класс.

class color {
     public:
     color();
     ~color();
     int blue();
     void set_blue( int );

     private:
     int c_blue;
};

XSUB для методов blue() и set_blue() определены с именем класса, но параметр для объекта (THIS или «self») неявный и не указан.

int
color::blue()

void
color::set_blue( val )
     int val

Обе Perl-функции будут ожидать объект в качестве первого параметра. В сгенерированном C++-коде объект называется THIS, и вызов метода будет выполнен на этом объекте. Таким образом, в C++-коде методы blue() и set_blue() будут вызываться так:

RETVAL = THIS->blue();

THIS->set_blue( val );

Вы также можете написать один метод get/set с использованием необязательного аргумента:

int
color::blue( val = NO_INIT )
    int val
    PROTOTYPE $;$
    CODE:
        if (items > 1)
            THIS->set_blue( val );
        RETVAL = THIS->blue();
    OUTPUT:
        RETVAL

Если имя функции равно DESTROY, то будет вызвана C++-функция delete, и THIS будет передано в качестве параметра. Сгенерированный C++-код для

void
color::DESTROY()

будет выглядеть так:

color *THIS = ...;  // Initialized as in typemap

delete THIS;

Если имя функции равно new, то будет вызвана C++-функция new для создания динамического C++-объекта. XSUB будет ожидать имя класса, которое будет храниться в переменной CLASS, в качестве первого аргумента.

color *
color::new()

Сгенерированный C++-код вызовет new.

RETVAL = new color();

Ниже приведен пример typemap, который можно использовать для этого C++-примера.

TYPEMAP
color *  O_OBJECT

OUTPUT
# The Perl object is blessed into 'CLASS', which should be a
# char* having the name of the package for the blessing.
O_OBJECT
    sv_setref_pv( $arg, CLASS, (void*)$var );

INPUT
O_OBJECT
    if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) )
        $var = ($type)SvIV((SV*)SvRV( $arg ));
    else{
        warn(\"${Package}::$func_name() -- \"
            \"$var is not a blessed SV reference\");
        XSRETURN_UNDEF;
    }

Стратегия интерфейса

При проектировании интерфейса между Perl и C-библиотекой прямое преобразование из C в XS (например, созданное h2xs -x) часто бывает достаточно. Однако иногда интерфейс будет выглядеть очень похожим на C и порой неинтуитивно, особенно когда C-функция изменяет один из своих параметров или возвращает ошибку (как в случае «отрицательные значения возврата означают ошибку»). В тех случаях, когда программист хочет создать более похожий на Perl интерфейс, следующая стратегия может помочь определить наиболее важные части интерфейса.

Определите C-функции с параметрами ввода/вывода или параметрами вывода. XSUB для этих функций может быть способен возвращать списки в Perl.

Определите C-функции, которые используют какую-либо информацию об ошибке как указание на неудачу. Они могут стать кандидатами для возвращения undef или пустого списка в случае ошибки. Если ошибка может быть обнаружена без вызова C-функции, вы можете использовать раздел INIT: для сообщения об ошибке. Для ошибок, обнаруживаемых после возврата C-функции, можно использовать раздел POSTCALL: для обработки ошибки. В более сложных случаях используйте разделы CODE: или PPCODE:.

Если много функций используют одно и то же указание на ошибку, основанное на возвращаемом значении, вы можете создать специальный typedef для обработки этой ситуации. Поместите

typedef int negative_is_failure;

в начале файла XS и создайте запись typemap OUTPUT для negative_is_failure, которая преобразует отрицательные значения в undef или, возможно, вызовет croak(). После этого возвращаемое значение типа negative_is_failure создаст более похожий на Perl интерфейс.

Определите значения, которые используются только самими C- и XSUB-функциями, например, когда параметр функции должен быть содержимым глобальной переменной. Если Perl не нуждается в доступе к содержимому значения, то, возможно, не нужно предоставлять преобразование этого значения из C в Perl.

Определите указатели в списках параметров и возвращаемых значениях C-функций. Некоторые указатели могут быть использованы для реализации параметров ввода/вывода или параметров вывода, они могут обрабатываться в XS с помощью унарного оператора & и, возможно, с использованием ключевого слова NO_INIT. Некоторые другие потребуют обработки типов, таких как int *, и необходимо решить, что будет делать полезное Perl-преобразование в таком случае. Когда семантика понятна, рекомендуется поместить преобразование в файл typemap.

Определите структуры, используемые C-функциями. Во многих случаях может быть полезно использовать тип T_PTROBJ для этих структур, чтобы ими можно было управлять в Perl как освященными объектами. (Это обрабатывается автоматически h2xs -x.)

Если один и тот же C-тип используется в нескольких различных контекстах, требующих различных преобразований, typedef несколько новых типов, сопоставленных с этим C-типом, и создайте отдельные записи typemap для этих новых типов. Используйте эти типы в объявлениях типа возвращаемого значения и параметров XSUB.

Perl-объекты и C-структуры

При работе со C-структурами следует выбрать либо T_PTROBJ, либо T_PTRREF для типа XS. Оба типа предназначены для обработки указателей на сложные объекты. Тип T_PTRREF позволит объекту Perl быть не освященным, а тип T_PTROBJ требует, чтобы объект был освящен. Использование T_PTROBJ позволяет добиться некоторой проверки типа, потому что XSUB будет пытаться проверить, является ли Perl-объект нужного типа.

Следующий XS-код демонстрирует функцию getnetconfigent(), используемую с ONC+ TIRPC. Функция getnetconfigent() вернет указатель на C-структуру и имеет показанный ниже прототип C. Пример продемонстрирует, как C-указатель станет Perl-ссылкой. Perl будет рассматривать эту ссылку как указатель на освященный объект и попытается вызвать деструктор для объекта. Деструктор будет предоставлен в исходном коде XS для освобождения памяти, используемой getnetconfigent(). Деструкторы в XS могут быть созданы путем задания XSUB-функции, имя которой заканчивается словом DESTROY. XS-деструкторы могут быть использованы для освобождения памяти, которая могла быть выделена с помощью malloc() другой XSUB.

struct netconfig *getnetconfigent(const char *netid);

typedef будет создано для struct netconfig. Perl-объект будет освящен в классе, соответствующем имени C-типа, с добавленным тегом Ptr, а имя не должно содержать вложенных пробелов, если это будет имя Perl-пакета. Деструктор будет помещен в класс, соответствующий классу объекта, и ключевое слово PREFIX будет использоваться для обрезки имени до слова DESTROY, так как Perl ожидает этого.

typedef struct netconfig Netconfig;

MODULE = RPC  PACKAGE = RPC

Netconfig *
getnetconfigent(netid)
     char *netid

MODULE = RPC  PACKAGE = NetconfigPtr  PREFIX = rpcb_

void
rpcb_DESTROY(netconf)
     Netconfig *netconf
   CODE:
     printf("Now in NetconfigPtr::DESTROY\n");
     free( netconf );

Для этого примера требуется следующая запись typemap. См. perlxstypemap для получения дополнительной информации о добавлении новых typemap для расширения.

TYPEMAP
Netconfig *  T_PTROBJ

Этот пример будет использоваться со следующими Perl-выражениями.

use RPC;
$netconf = getnetconfigent("udp");

Когда Perl уничтожает объект, на который ссылается $netconf, он отправит объект в предоставленную XSUB-функцию DESTROY. Perl не может определить и не заботится о том, является ли этот объект C-структурой, а не Perl-объектом. В этом смысле нет никакой разницы между объектом, созданным XSUB getnetconfigent(), и объектом, созданным обычной Perl-подпрограммой.

Безопасное хранение статических данных в XS

Начиная с Perl 5.8, для безопасного хранения статических данных в XS-модулях, к которым будет обращаться многопотоковый Perl, определена система макросов.

Хотя в первую очередь предназначена для использования с многопоточным Perl, система макросов разработана таким образом, чтобы работать и с однопоточным Perl.

Поэтому настоятельно рекомендуется использовать эти макросы во всех XS-модулях, использующих статические данные.

Самый простой способ получить шаблон набора макросов — указать опцию -g (--global) с h2xs (см. h2xs).

Ниже приведен пример модуля, использующего эти макросы.

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

/* Global Data */

#define MY_CXT_KEY "BlindMice::_guts" XS_VERSION

typedef struct {
    int count;
    char name[3][100];
} my_cxt_t;

START_MY_CXT

MODULE = BlindMice           PACKAGE = BlindMice

BOOT:
{
    MY_CXT_INIT;
    MY_CXT.count = 0;
    strcpy(MY_CXT.name[0], "None");
    strcpy(MY_CXT.name[1], "None");
    strcpy(MY_CXT.name[2], "None");
}

int
newMouse(char * name)
    PREINIT:
      dMY_CXT;
    CODE:
      if (MY_CXT.count >= 3) {
          warn("Already have 3 blind mice");
          RETVAL = 0;
      }
      else {
          RETVAL = ++ MY_CXT.count;
          strcpy(MY_CXT.name[MY_CXT.count - 1], name);
      }
    OUTPUT:
      RETVAL

char *
get_mouse_name(index)
      int index
    PREINIT:
      dMY_CXT;
    CODE:
      if (index > MY_CXT.count)
        croak("There are only 3 blind mice.");
      else
        RETVAL = MY_CXT.name[index - 1];
    OUTPUT:
      RETVAL

void
CLONE(...)
    CODE:
      MY_CXT_CLONE;

ССЫЛКА MY_CXT

MY_CXT_KEY

Данный макрос используется для определения уникального ключа, ссылающегося на статические данные для модуля XS. Рекомендуемая схема именования, используемая h2xs, состоит из имени модуля, строки «::_guts» и номера версии модуля.

#define MY_CXT_KEY "MyModule::_guts" XS_VERSION
typedef my_cxt_t

Этот тип структуры обязательно должен называться my_cxt_t. Другие CXT* макросы предполагают существование типа my_cxt_t.

Объявите тип данных с именем my_cxt_t, представляющий структуру, содержащую все данные, которые должны быть локальными для интерпретатора.

typedef struct {
    int some_value;
} my_cxt_t;
START_MY_CXT

Всегда размещайте макрос START_MY_CXT непосредственно после объявления my_cxt_t.

MY_CXT_INIT

Макрос MY_CXT_INIT инициализирует хранилище для структуры my_cxt_t.

Его необходимо вызывать ровно один раз, обычно в разделе BOOT:. Если вы поддерживаете несколько интерпретаторов, его следует вызывать один раз в каждом экземпляре интерпретатора, за исключением интерпретаторов, клонированных из существующих. (Но см. "MY_CXT_CLONE" ниже.)

dMY_CXT

Используйте макрос dMY_CXT (объявление) во всех функциях, которые обращаются к MY_CXT.

MY_CXT

Используйте макрос MY_CXT для доступа к членам структуры my_cxt_t. Например, если my_cxt_t

typedef struct {
    int index;
} my_cxt_t;

то используйте это для доступа к члену index

dMY_CXT;
MY_CXT.index = 2;
aMY_CXT/pMY_CXT

dMY_CXT может быть довольно дорогим для вычисления, и чтобы избежать накладных расходов на его вызов в каждой функции, можно передать объявление в другие функции с помощью макросов aMY_CXT/pMY_CXT, например

void sub1() {
    dMY_CXT;
    MY_CXT.index = 1;
    sub2(aMY_CXT);
}

void sub2(pMY_CXT) {
    MY_CXT.index = 2;
}

Аналогично pTHX, существуют эквивалентные формы, когда макрос является первым или последним в нескольких аргументах, где нижнее подчеркивание представляет запятую, т.е. _aMY_CXT, aMY_CXT_, _pMY_CXT и pMY_CXT_.

MY_CXT_CLONE

По умолчанию, когда создается новый интерпретатор как копия существующего (например, через threads->create()), оба интерпретатора используют одну и ту же физическую структуру my_cxt_t. Вызов MY_CXT_CLONE (обычно через функцию пакета CLONE()) приводит к созданию байтовой копии структуры, и в дальнейшем dMY_CXT будет обращаться к этой копии.

MY_CXT_INIT_INTERP(my_perl)
dMY_CXT_INTERP(my_perl)

Это версии макросов, принимающие явный интерпретатор в качестве аргумента.

Обратите внимание, что эти макросы будут работать только вместе в одном и том же файле исходного кода; другими словами, dMY_CTX в одном файле исходного кода будет обращаться к другой структуре, чем dMY_CTX в другом файле исходного кода.

Поддерживающие потоки системные интерфейсы

Начиная с Perl 5.8, на уровне C/C++ Perl умеет оборачивать системные/библиотечные интерфейсы, имеющие многопоточные версии (например, getpwent_r()), в макросы переднего плана (например, getpwent()), которые правильно обрабатывают взаимодействие с многопоточным Perl-интерпретатором. Это произойдет прозрачно, вам нужно только создать Perl-интерпретатор.

Это обрамление происходит всегда при компиляции исходного кода Perl (определен PERL_CORE) или расширений Perl (определен PERL_EXT). При компиляции кода XS вне ядра Perl обрамление не выполняется до Perl 5.28. Начиная с этого выпуска, вы можете

#define PERL_REENTRANT

в своём коде, чтобы включить оборачивание. Это рекомендуется, если вы используете такие функции, так как смешение _r-форм (как Perl, скомпилированный для многопоточного использования, будет делать) и _r-форм не определено (получаются несогласованные результаты, повреждение данных или даже сбои становятся более вероятными), и не является очень портативным. К сожалению, не на всех системах есть все _r формы, но использование этого #define даёт вам любую защиту, о которой Perl знает на каждой системе.

ПРИМЕРЫ

Файл RPC.xs: Интерфейс к некоторым функциям привязки библиотеки ONC+ RPC.

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <rpc/rpc.h>

typedef struct netconfig Netconfig;

MODULE = RPC  PACKAGE = RPC

SV *
rpcb_gettime(host="localhost")
     char *host
   PREINIT:
     time_t  timep;
   CODE:
     ST(0) = sv_newmortal();
     if( rpcb_gettime( host, &timep ) )
          sv_setnv( ST(0), (double)timep );

Netconfig *
getnetconfigent(netid="udp")
     char *netid

MODULE = RPC  PACKAGE = NetconfigPtr  PREFIX = rpcb_

void
rpcb_DESTROY(netconf)
     Netconfig *netconf
   CODE:
     printf("NetconfigPtr::DESTROY\n");
     free( netconf );

Файл typemap: Специальная таблица соответствий типов для RPC.xs. (см. perlxstypemap)

TYPEMAP
Netconfig *  T_PTROBJ

Файл RPC.pm: Модуль Perl для расширения RPC.

package RPC;

require Exporter;
require DynaLoader;
@ISA = qw(Exporter DynaLoader);
@EXPORT = qw(rpcb_gettime getnetconfigent);

bootstrap RPC;
1;

Файл rpctest.pl: Программа тестирования Perl для расширения RPC.

use RPC;

$netconf = getnetconfigent();
$a = rpcb_gettime();
print "time = $a\n";
print "netconf = $netconf\n";

$netconf = getnetconfigent("tcp");
$a = rpcb_gettime("poplar");
print "time = $a\n";
print "netconf = $netconf\n";

ОСОБЕННОСТИ

Код XS имеет полный доступ к системным вызовам, включая функции библиотеки C. Таким образом, он имеет возможность вмешиваться в то, что Perl-ядро или другие модули настроили, такие как обработчики сигналов или файловые дескрипторы. Он может испортить память или совершить любое количество вредных действий. Не делайте этого.

Некоторые модули имеют цикл обработки событий, ожидая пользовательского ввода. Вряд ли два таких модуля будут работать должным образом вместе в одной Perl-программе.

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

Одна из областей конфликта — это C-локали. (См. perllocale.) Perl, за исключением одного случая и если ему не сказано иное, настраивает основную локаль программы, в которой она выполняется, на локаль, переданную ей из среды. Это важное отличие от обычной программы на C, где основная локаль — «C»-локаль, если программа её не изменит. Начиная с версии 5.20, эта основная локаль полностью скрыта от чистого Perl-кода вне лексического пространства use locale, за исключением нескольких вызовов функций в модуле POSIX, которые по необходимости используют её. Но основная локаль, за этим исключением, доступна коду XS, влияя на все процедуры библиотеки C, поведение которых зависит от локали. Ваш код XS лучше не предполагать, что основная локаль — «C». Исключением является LC_NUMERIC категория локали, и причина, по которой она является исключением, заключается в том, что опыт показал, что она может быть проблематичной для кода XS, в то время как мы не получали сообщений о проблемах с другими категориями локалей. Причина проблем с этой категорией заключается в том, что символ, используемый в качестве десятичной точки, может изменяться. Во многих европейских языках используется запятая, тогда как в английском и, следовательно, в Perl ожидается точка (U+002E: ТОЧКА). Многие модули могут обрабатывать только радикс-символ в виде точки, и поэтому Perl пытается сделать его таким. До Perl v5.20 попытка заключалась просто в установке LC_NUMERIC при запуске в "C" локаль. Любой другой setlocale() изменит её; это вызывало некоторые сбои. Поэтому начиная с v5.22, Perl пытается сохранить LC_NUMERIC всегда установленным на "C" для кода XS.

Подводя итог, вот что можно ожидать и как обрабатывать локали в коде XS:

Не учитывающие локаль коды XS

Помните, что даже если вы считаете, что ваш код не учитывает локаль, он может вызывать функцию библиотеки, которая это делает. Надеюсь, в мануале по такой функции будет указана эта зависимость, но документация неидеальна.

Текущая локаль доступна кодам XS, за исключением, возможно, LC_NUMERIC (объясняется в следующем абзаце). Проблемы с другими категориями не наблюдались. Perl инициализирует вещи при запуске, так что текущая локаль — та, которая указана в среде пользователя в этот момент. См. "СРЕДА" в perllocale.

Однако до версии v5.20 Perl инициализировал вещи при запуске, так что LC_NUMERIC устанавливалась в локаль "C". Но если какой-либо код где-либо изменит её, она останется изменённой. Это означает, что ваш модуль не может полагаться на LC_NUMERIC, являющуюся чем-то определённым, и вы не можете ожидать, что числа с плавающей точкой (включая версии строк) будут содержать точки. Если вы не допускаете отсутствие точки, ваш код может сломаться, если кто-то где-то изменит локаль. По этой причине в версии v5.22 поведение было изменено, так что Perl пытается сохранить LC_NUMERIC в локали "C", за исключением операций внутри, где она должна быть другой. Некорректно работающий код XS всегда сможет изменить локаль, но наиболее распространённый случай этого проверяется и обрабатывается.

Учитывающие локаль коды XS

Если нужна локаль из среды пользователя, кодам XS не нужно устанавливать локаль, за исключением LC_NUMERIC, так как perl уже настроил остальные. Коды XS должны избегать изменения локали, так как это может отрицательно сказаться на другом, несвязанном коде, и может быть небезопасно в многопоточных приложениях. Для минимизации проблем следует использовать макросы "STORE_LC_NUMERIC_SET_TO_NEEDED" в perlapi, "STORE_LC_NUMERIC_FORCE_TO_UNDERLYING" в perlapi и "RESTORE_LC_NUMERIC" в perlapi для внесения необходимых изменений.

Однако, начиная с Perl v5.28, локали безопасны в многопоточных приложениях на платформах, которые поддерживают эту функциональность. В Windows эта поддержка появилась с Visual Studio 2005. Многие другие современные платформы поддерживают многопоточные функции POSIX 2008. C #define USE_THREAD_SAFE_LOCALE будут определены, если этот сборка использует эти возможности. Из Perl-пространства, только для чтения переменная ${SAFE_LOCALES} равна 1, если сборка не многопоточная, или если USE_THREAD_SAFE_LOCALE определена; в противном случае она равна 0.

Способ работы под капотом заключается в том, что каждый поток может использовать локаль, специфичную для него (это функциональность Windows и POSIX 2008), или глобальную локаль, доступную всем потокам (это функциональность, которая всегда была). Реализации для Windows и POSIX совершенно разные. В Windows среда выполнения может быть настроена так, что стандартная функция setlocale(3) знает только о глобальной локали или локали для данного потока. В POSIX setlocale всегда работает с глобальной локалью, а другие функции были созданы для обработки локалей каждого потока. Perl делает это прозрачным для кода в perl-пространстве. Он по-прежнему использует POSIX::setlocale(), и интерпретатор переводит это в функции для каждого потока.

Все остальные функции, чувствительные к локали, автоматически используют локаль текущего потока, если она включена, а в противном случае — глобальную локаль. Таким образом, вызовы setlocale неэффективны на системах POSIX для текущего потока, если этот поток использует локаль текущего потока. Если perl скомпилирован для однопоточной работы, он не использует функции для каждого потока, поэтому setlocale работает как ожидается.

Если вы загрузили модуль POSIX, вы можете использовать методы, описанные в perlcall, для вызова POSIX::setlocale для безопасного изменения или запроса локали (на системах, где это безопасно), или вы можете использовать новую функцию 5.28 "Perl_setlocale" в perlapi вместо неё, которая является прямой заменой системной setlocale(3) и прозрачно обрабатывает однопоточные и многопоточные приложения.

Существуют некоторые вызовы библиотек, связанные с локалью, которые по-прежнему не безопасны в многопоточных приложениях, потому что они возвращают данные в буфере, общем для всех потоков. В прошлом это не имело значения, так как локали вообще не были многопоточными. Но теперь вы должны быть осведомлены об этом, если ваш модуль вызывается в многопоточном приложении. Известные:

asctime()
ctime()
gcvt() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
getdate()
wcrtomb() if its final argument is NULL
wcsrtombs() if its final argument is NULL
wcstombs()
wctomb()

Некоторые из них не должны вызываться в приложении Perl, а для других уже существуют многопоточные версии:

asctime_r()
ctime_r()
Perl_langinfo()

Формы _r автоматически используются, начиная с Perl 5.28, если вы компилируете свой код, с

#define PERL_REENTRANT

См. также "Perl_langinfo" в perlapi. Вы можете использовать методы, описанные в perlcall, для получения наилучших доступных безопасных по локали версий этих функций

POSIX::localeconv()
POSIX::wcstombs()
POSIX::wctomb()

И обратите внимание, что некоторые элементы, возвращаемые Localeconv, доступны через "Perl_langinfo" в perlapi.

Другие не следует использовать в многопоточном приложении.

Некоторые модули могут вызывать библиотеку, не являющуюся Perl, которая учитывает локаль. Это нормально, если она не пытается запросить или изменить локаль, используя системную setlocale. Но если они вызывают системную функцию setlocale, эти вызовы могут быть неэффективными. Вместо этого Perl_setlocale работает во всех ситуациях. Простая функция setlocale неэффективна в многопоточных системах POSIX 2008. Она работает только с глобальной локалью, тогда как каждый поток имеет свою собственную локаль, не обращая внимания на глобальную. Поскольку перенос этих библиотек, не являющихся Perl, в Perl_setlocale не является вариантом, в версии 5.28 появилась новая функция switch_to_global_locale, которая переключает поток, из которого она вызывается, так что любые системные вызовы setlocale будут иметь желаемый эффект. Функция sync_locale должна вызываться перед возвратом в perl.

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

В версиях perl без поддержки многопоточной локали некоторые внешние библиотеки, такие как Gtk, изменяют локали. Это может вызвать проблемы для ядра Perl и других модулей. В этих случаях, перед возвращением управления в perl, начиная с v5.20.1, вызов функции sync_locale() из XS должен быть достаточным для предотвращения большинства этих проблем. До этого вам нужна чисто перловая инструкция, которая это делает:

POSIX::setlocale(LC_ALL, POSIX::setlocale(LC_ALL));

или используйте методы, описанные в perlcall.

Версия XS

В этом документе рассматриваются возможности, поддерживаемые ExtUtils::ParseXS (также известная как xsubpp) 3.13_01.

АВТОР

Первоначально написано Дином Роэрихом <roehrich@cray.com>.

Поддерживается с 1996 года Perl Porters <perlbug@perl.org>.

© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.32.0/perlxs

Spec-Zone.ru

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