Spec-Zone.ru › Perl 5.36

perlxstut

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ОПИСАНИЕ
  • СПЕЦИАЛЬНЫЕ ПРИМЕЧАНИЯ
    • make
    • Ограничение по версиям
    • Динамическая загрузка против статической
    • Потоки и PERL_NO_GET_CONTEXT
  • УЧЕБНИК
    • ПРИМЕР 1
    • ПРИМЕР 2
    • Что произошло?
    • Создание хороших тестовых сценариев
    • ПРИМЕР 3
    • Что нового здесь?
    • Параметры ввода и вывода
    • Программа XSUBPP
    • Файл TYPEMAP
    • Предупреждение об аргументах вывода
    • ПРИМЕР 4
    • Что произошло здесь?
    • Структура файла .xs
    • Удаление лишнего из XSUB
    • Подробнее об аргументах XSUB
    • Стек аргументов
    • Расширение вашего расширения
    • Документирование вашего расширения
    • Установка вашего расширения
    • ПРИМЕР 5
    • Новые моменты в данном примере
    • ПРИМЕР 6
    • Новые моменты в данном примере
    • ПРИМЕР 7 (Скоро)
    • ПРИМЕР 8 (Скоро)
    • ПРИМЕР 9: Передача открытых файлов в XSes
    • Устранение неполадок в этих примерах
  • См. также
  • Автор
    • Последнее изменение

НАЗВАНИЕ

perlxstut - Учебник по написанию XSUB

ОПИСАНИЕ

Этот учебник научит читателя шагам, необходимым для создания расширения Perl. Предполагается, что у читателя есть доступ к perlguts, perlapi и perlxs.

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

Этот учебник написан с точки зрения Unix. Там, где я знаю, что они отличаются на других платформах (например, Win32), я буду их перечислять. Если вы найдёте что-то, что было упущено, пожалуйста, сообщите мне.

СПЕЦИАЛЬНЫЕ ПРИМЕЧАНИЯ

make

Этот учебник предполагает, что программа make, которую настроен использовать Perl, называется make. Вместо выполнения «make» в следующих примерах вам, возможно, придётся заменить её на то, что настроен использовать Perl. Выполнение perl -V:make должно показать вам, что это.

Ограничение по версиям

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

Чтобы понять, какие виды несовместимости могут ожидаться, и в редких случаях, когда версия Perl на вашей машине старше, чем этот документ, см. раздел «Устранение неполадок в этих примерах» для получения дополнительной информации.

Если ваше расширение использует какие-либо функции Perl, которые недоступны в более старых версиях Perl, ваши пользователи оценят раннее и осмысленное предупреждение. Вы, вероятно, поместите эту информацию в файл README, но сегодня установка расширений может выполняться автоматически, руководствуясь модулем CPAN.pm или другими инструментами.

В установках, основанных на MakeMaker, Makefile.PL предоставляет самую раннюю возможность выполнения проверок версий. Для этой цели можно поместить что-то вроде этого в Makefile.PL:

eval { require 5.007 }
    or die <<EOD;
############
### This module uses frobnication framework which is not available
### before version 5.007 of Perl.  Upgrade your Perl before
### installing Kara::Mba.
############
EOD

Динамическая загрузка против статической

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

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

Если вы хотите создать статически связанный исполняемый файл на системе, которая может динамически загружать библиотеки, то во всех следующих примерах, где выполняется команда «make» без аргументов, вы можете выполнить команду «make perl» вместо этого.

Если вы создали такой статически связанный исполняемый файл по своему выбору, то вместо того, чтобы говорить «make test», вы должны сказать «make test_static». На системах, на которых вообще невозможно создать библиотеки с динамической загрузкой, просто выполнение команды «make test» будет достаточным.

Потоки и PERL_NO_GET_CONTEXT

Для многопоточных сборок Perl требует указателя контекста для текущего потока, без PERL_NO_GET_CONTEXT, Perl будет вызывать функцию для получения контекста.

Для повышения производительности включите:

#define PERL_NO_GET_CONTEXT

как показано ниже.

Для получения более подробной информации см. perlguts.

УЧЕБНИК

Теперь перейдём к делу!

ПРИМЕР 1

Наше первое расширение будет очень простым. Когда мы вызываем процедуру в расширении, она выведет известное сообщение и вернётся.

Выполните «h2xs -A -n Mytest». Это создаст каталог Mytest, возможно, в ext/, если этот каталог существует в текущем рабочем каталоге. В каталоге Mytest будут созданы несколько файлов, включая MANIFEST, Makefile.PL, lib/Mytest.pm, Mytest.xs, t/Mytest.t и Changes.

Файл MANIFEST содержит имена всех созданных файлов в каталоге Mytest.

Файл Makefile.PL должен выглядеть примерно так:

use ExtUtils::MakeMaker;

# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
    NAME         => 'Mytest',
    VERSION_FROM => 'Mytest.pm', # finds $VERSION
    LIBS         => [''],        # e.g., '-lm'
    DEFINE       => '',          # e.g., '-DHAVE_SOMETHING'
    INC          => '-I',        # e.g., '-I. -I/usr/include/other'
);

Файл Mytest.pm должен начинаться примерно так:

package Mytest;

use 5.008008;
use strict;
use warnings;

require Exporter;

our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw(

) ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

our @EXPORT = qw(

);

our $VERSION = '0.01';

require XSLoader;
XSLoader::load('Mytest', $VERSION);

# Preloaded methods go here.

1;
__END__
# Below is the stub of documentation for your module. You better
# edit it!

Остальная часть файла .pm содержит примерный код для предоставления документации для расширения.

Наконец, файл Mytest.xs должен выглядеть примерно так:

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

#include "ppport.h"

MODULE = Mytest             PACKAGE = Mytest

Давайте отредактируем файл .xs, добавив в конец файла это:

void
hello()
    CODE:
        printf("Hello, world!\n");

Строки, начинающиеся со строки «CODE:», могут быть без отступа. Однако для повышения читабельности рекомендуется отступать строки CODE: на один уровень, а последующие строки — ещё на один уровень.

Теперь выполним «perl Makefile.PL». Это создаст реальный Makefile, который нужен make. Его вывод выглядит примерно так:

% perl Makefile.PL
Checking if your kit is complete...
Looks good
Writing Makefile for Mytest
%

Теперь выполнение make произведёт вывод, похожий на этот (некоторые длинные строки были укорочены для ясности, а некоторые лишние строки были удалены):

% make
cp lib/Mytest.pm blib/lib/Mytest.pm
perl xsubpp  -typemap typemap  Mytest.xs > Mytest.xsc && \
mv Mytest.xsc Mytest.c
Please specify prototyping behavior for Mytest.xs (see perlxs manual)
cc -c     Mytest.c
Running Mkbootstrap for Mytest ()
chmod 644 Mytest.bs
rm -f blib/arch/auto/Mytest/Mytest.so
cc -shared -L/usr/local/lib Mytest.o -o blib/arch/auto/Mytest/Mytest.so

chmod 755 blib/arch/auto/Mytest/Mytest.so
cp Mytest.bs blib/arch/auto/Mytest/Mytest.bs
chmod 644 blib/arch/auto/Mytest/Mytest.bs
Manifying blib/man3/Mytest.3pm
%

Вы можете спокойно пропустить строку о «поведении прототипирования» — она поясняется в "The PROTOTYPES: Keyword" in perlxs.

Perl имеет свой собственный специальный способ лёгкого написания тестовых сценариев, но для этого примера мы создадим свой собственный тестовый сценарий. Создайте файл с именем hello, который выглядит так:

#! /opt/perl5/bin/perl

use ExtUtils::testlib;

use Mytest;

Mytest::hello();

Теперь сделайте сценарий исполняемым (chmod +x hello), запустите сценарий, и мы должны увидеть следующий вывод:

% ./hello
Hello, world!
%

ПРИМЕР 2

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

Добавьте следующее в конец Mytest.xs:

int
is_even(input)
        int input
    CODE:
        RETVAL = (input % 2 == 0);
    OUTPUT:
        RETVAL

В начале строки «int input» не обязательно должен быть пробел, но это полезно для улучшения читабельности. Помещение точки с запятой в конце этой строки также является необязательным. Любое количество и тип пробелов может быть размещено между «int» и «input».

Теперь перезапустите make, чтобы перестроить нашу новую общую библиотеку.

Теперь выполните те же шаги, что и раньше, сгенерировав Makefile из файла Makefile.PL и выполнив make.

Для проверки работы нашего расширения нам теперь необходимо взглянуть на файл Mytest.t. Этот файл настроен для имитации того же типа структуры тестирования, что и у самого Perl. Внутри тестового сценария вы выполняете ряд тестов для подтверждения поведения расширения, печатая «ok», когда тест проходит, и «not ok», когда он не проходит.

use Test::More tests => 4;
BEGIN { use_ok('Mytest') };

#########################

# Insert your test code below, the Test::More module is use()ed here
# so read its man page ( perldoc Test::More ) for help writing this
# test script.

is( Mytest::is_even(0), 1 );
is( Mytest::is_even(1), 0 );
is( Mytest::is_even(2), 1 );

Мы будем запускать тестовый сценарий через команду «make test». Вы должны увидеть вывод, похожий на этот:

%make test
PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e"
"test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/Mytest....ok
All tests successful.
Files=1, Tests=4, 0 wallclock secs ( 0.03 cusr + 0.00 csys = 0.03 CPU)
%

Что произошло?

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

h2xs создаёт ряд файлов в каталоге расширения. Файл Makefile.PL — это скрипт Perl, который сгенерирует настоящий Makefile для сборки расширения. Мы рассмотрим его подробнее позже.

Файлы .pm и .xs содержат основную часть расширения. Файл .xs содержит C-функции, которые составляют расширение. Файл .pm содержит функции, которые сообщают Perl, как загрузить ваше расширение.

Генерация Makefile и выполнение make создали директорию с именем blib (что означает "библиотека построения") в текущей рабочей директории. Эта директория будет содержать общую библиотеку, которую мы будем строить. После тестирования мы можем установить её в конечное местоположение.

Вызов скрипта теста с помощью "make test" выполнил очень важную операцию. Он вызвал perl со всеми этими -I аргументами, чтобы он мог найти различные файлы, которые являются частью расширения. Крайне важно, что при тестировании расширений вы используете "make test". Если вы попытаетесь запустить скрипт теста сам по себе, вы получите ошибку fatal. Ещё одна причина, почему важно использовать "make test" для запуска скрипта теста, заключается в том, что если вы тестируете обновление уже существующей версии, использование "make test" гарантирует, что вы будете тестировать ваше новое расширение, а не уже существующую версию.

Когда Perl видит use extension;, он ищет файл с тем же именем, что и use расширение, с суффиксом .pm. Если этот файл не найден, Perl завершается с ошибкой fatal. Путь поиска по умолчанию содержится в массиве @INC.

В нашем случае Mytest.pm сообщает perl, что ему понадобятся расширения Exporter и Dynamic Loader. Затем он устанавливает массивы @ISA и @EXPORT и скаляр $VERSION; и, наконец, он сообщает perl о загрузке модуля. Perl вызовет свою функцию динамической загрузки (если она есть) и загрузит общую библиотеку.

Два массива @ISA и @EXPORT очень важны. Массив @ISA содержит список других пакетов, в которых следует искать методы (или подпрограммы), отсутствующие в текущем пакете. Обычно это важно только для объектно-ориентированных расширений (о которых мы поговорим позже), и поэтому обычно не требует модификации.

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

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

См. perlmod для получения дополнительной информации.

Переменная $VERSION используется для обеспечения синхронизации файла .pm и общей библиотеки. Всякий раз, когда вы вносите изменения в файлы .pm или .xs, вы должны увеличивать значение этой переменной.

Написание хороших скриптов тестов

Важность написания хороших скриптов тестов нельзя переоценить. Следует строго следовать стилю "ok/not ok", используемому самим Perl, чтобы очень легко и однозначно определить результат каждого тестового случая. Когда вы находите и исправляет ошибку, обязательно добавьте тестовый случай для неё.

Выполняя "make test", вы гарантируете, что ваш скрипт Mytest.t будет выполняться и использовать правильную версию вашего расширения. Если у вас много тестовых случаев, сохраняйте ваши тестовые файлы в директории "t" с суффиксом ".t". При выполнении "make test", все эти тестовые файлы будут выполнены.

ПРИМЕР 3

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

Добавьте следующее в конец Mytest.xs:

void
round(arg)
        double  arg
    CODE:
        if (arg > 0.0) {
                arg = floor(arg + 0.5);
        } else if (arg < 0.0) {
                arg = ceil(arg - 0.5);
        } else {
                arg = 0.0;
        }
    OUTPUT:
        arg

Измените файл Makefile.PL так, чтобы соответствующая строка выглядела так:

LIBS      => ['-lm'],   # e.g., '-lm'

Сгенерируйте Makefile и выполните make. Измените номер теста в Mytest.t на "9" и добавьте следующие тесты:

my $i;

$i = -1.5;
Mytest::round($i);
is( $i, -2.0, 'Rounding -1.5 to -2.0' );

$i = -1.1;
Mytest::round($i);
is( $i, -1.0, 'Rounding -1.1 to -1.0' );

$i = 0.0;
Mytest::round($i);
is( $i, 0.0, 'Rounding 0.0 to 0.0' );

$i = 0.5;
Mytest::round($i);
is( $i, 1.0, 'Rounding 0.5 to 1.0' );

$i = 1.2;
Mytest::round($i);
is( $i, 1.0, 'Rounding 1.2 to 1.0' );

Выполнение "make test" теперь должно вывести, что все девять тестов прошли успешно.

Обратите внимание, что в этих новых тестовых случаях аргумент, переданный round, был скалярной переменной. Вы можете задаться вопросом, можно ли округлить константу или литерал. Чтобы увидеть, что происходит, временно добавьте следующую строку в Mytest.t:

Mytest::round(3);

Выполните "make test", и вы увидите, что Perl завершится с ошибкой fatal. Perl не позволит вам изменять значение констант!

Что нового здесь?

  • Мы внесли некоторые изменения в Makefile.PL. В этом случае мы указали дополнительную библиотеку, которая должна быть подключена к общей библиотеке расширения, в данном случае математическую библиотеку libm. Позже мы поговорим о том, как писать XSUB, которые могут вызывать каждую функцию в библиотеке.

  • Значение функции не возвращается в качестве значения возврата функции, а изменяется значение переменной, которая была передана в функцию. Вы могли догадаться об этом, увидев, что значение возврата round имеет тип "void".

Входные и выходные параметры

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

Список выходных параметров находится в самом конце функции, сразу после директивы OUTPUT:. Использование RETVAL сообщает Perl, что вы хотите отправить это значение обратно в качестве значения возврата функции XSUB. В Примере 3 мы хотели, чтобы "значение возврата" было помещено в исходную переменную, которую мы передали, поэтому мы включили её (а не RETVAL) в раздел OUTPUT:.

Программа XSUBPP

Программа xsubpp берёт код XS в файле .xs и переводит его в код C, помещая его в файл с суффиксом .c. Созданный C-код интенсивно использует C-функции в Perl.

Файл TYPEMAP

Программа xsubpp использует правила для преобразования типов данных Perl (скаляр, массив и т. д.) в типы данных C (int, char и т. д.). Эти правила хранятся в файле typemap ($PERLLIB/ExtUtils/typemap). Ниже приведено краткое обсуждение, но все подробности можно найти в perlxstypemap. Если у вас достаточно новая версия perl (5.16 и выше) или обновленный компилятор XS (ExtUtils::ParseXS 3.13_01 или лучше), то вы можете встроить typemap в свой XS вместо написания отдельных файлов. В любом случае, этот typemap разделён на три части:

Первый раздел отображает различные типы данных C в имя, которое несколько соответствует различным типам Perl. Второй раздел содержит C-код, который xsubpp использует для обработки входных параметров. Третий раздел содержит C-код, который xsubpp использует для обработки выходных параметров.

Давайте посмотрим на часть файла .c, созданного для нашего расширения. Имя файла Mytest.c:

XS(XS_Mytest_round)
{
    dXSARGS;
    if (items != 1)
        Perl_croak(aTHX_ "Usage: Mytest::round(arg)");
    PERL_UNUSED_VAR(cv); /* -W */
    {
        double  arg = (double)SvNV(ST(0));      /* XXXXX */
        if (arg > 0.0) {
                arg = floor(arg + 0.5);
        } else if (arg < 0.0) {
                arg = ceil(arg - 0.5);
        } else {
                arg = 0.0;
        }
        sv_setnv(ST(0), (double)arg);   /* XXXXX */
        SvSETMAGIC(ST(0));
    }
    XSRETURN_EMPTY;
}

Обратите внимание на две строки, помеченные как "XXXXX". Если вы посмотрите на первую часть файла typemap (или раздела), вы увидите, что числа с плавающей точкой имеют тип T_DOUBLE. В части INPUT typemap аргумент, который является T_DOUBLE, присваивается переменной arg путём вызова функции SvNV над чем-то, затем приведения его к double и присваивания переменной arg. Аналогично, в разделе OUTPUT, когда arg имеет своё конечное значение, оно передаётся в функцию sv_setnv, чтобы быть передано обратно вызывающей подпрограмме. Эти две функции описаны в perlguts; позже мы поговорим о том, что означает "ST(0)" в разделе стека аргументов.

Предупреждение об выходных аргументах

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

ПРИМЕР 4

В этом примере мы начнём писать XSUB, которые будут взаимодействовать с предварительно определёнными C-библиотеками. Для начала мы создадим небольшую библиотеку, а затем позволим h2xs написать наши файлы .pm и .xs за нас.

Создайте новую директорию Mytest2 на том же уровне, что и Mytest. В директории Mytest2 создайте другую директорию mylib и перейдите в неё.

Здесь мы создадим файлы, которые сгенерируют тестовую библиотеку. К ним будут относиться файл исходного кода C и заголовочный файл. Мы также создадим Makefile.PL в этой директории. Затем мы убедимся, что выполнение make на уровне Mytest2 автоматически выполнит этот файл Makefile.PL и полученный Makefile.

В директории mylib создайте файл mylib.h, который выглядит так:

#define TESTVAL 4

extern double   foo(int, long, const char*);

Также создайте файл mylib.c, который выглядит так:

#include <stdlib.h>
#include "mylib.h"

double
foo(int a, long b, const char *c)
{
        return (a + b + atof(c) + TESTVAL);
}

И, наконец, создайте файл Makefile.PL, который выглядит так:

use ExtUtils::MakeMaker;
$Verbose = 1;
WriteMakefile(
    NAME  => 'Mytest2::mylib',
    SKIP  => [qw(all static static_lib dynamic dynamic_lib)],
    clean => {'FILES' => 'libmylib$(LIB_EXT)'},
);


sub MY::top_targets {
        '
all :: static

pure_all :: static

static ::       libmylib$(LIB_EXT)

libmylib$(LIB_EXT): $(O_FILES)
        $(AR) cr libmylib$(LIB_EXT) $(O_FILES)
        $(RANLIB) libmylib$(LIB_EXT)

';
}

Убедитесь, что вы используете табуляцию, а не пробелы, в строках, начинающихся с "$(AR)" и "$(RANLIB)". Make не будет работать должным образом, если вы используете пробелы. Также сообщалось, что аргумент "cr" для $(AR) не нужен в системах Win32.

Теперь мы создадим основные файлы Mytest2 верхнего уровня. Перейдите в директорию выше Mytest2 и выполните следующую команду:

% h2xs -O -n Mytest2 Mytest2/mylib/mylib.h

Это выведет предупреждение о перезаписи Mytest2, но это нормально. Наши файлы хранятся в Mytest2/mylib и останутся нетронутыми.

Обычный Makefile.PL, который генерирует h2xs, не знает о директории mylib. Нам нужно сказать ему, что существует поддиректория и что мы будем генерировать библиотеку в ней. Добавьте аргумент MYEXTLIB к вызову WriteMakefile, чтобы он выглядел так:

WriteMakefile(
    NAME         => 'Mytest2',
    VERSION_FROM => 'Mytest2.pm', # finds $VERSION
    LIBS         => [''],   # e.g., '-lm'
    DEFINE       => '',     # e.g., '-DHAVE_SOMETHING'
    INC          => '',     # e.g., '-I/usr/include/other'
    MYEXTLIB     => 'mylib/libmylib$(LIB_EXT)',
);

а затем в конце добавьте подпрограмму (которая переопределит существующую подпрограмму). Не забудьте использовать символ табуляции для отступа строки, начинающейся с "cd"!

sub MY::postamble {
'
$(MYEXTLIB): mylib/Makefile
        cd mylib && $(MAKE) $(PASSTHRU)
';
}

Давайте также исправим файл MANIFEST, добавив следующие три строки:

mylib/Makefile.PL
mylib/mylib.c
mylib/mylib.h

Чтобы сохранить наше пространство имён чистым, измените файл .pm и замените переменную @EXPORT на @EXPORT_OK. Наконец, в файле .xs измените строку #include на:

#include "mylib/mylib.h"

А также добавьте следующее определение функции в конец файла .xs:

double
foo(a,b,c)
        int             a
        long            b
        const char *    c
    OUTPUT:
        RETVAL

Теперь нам также нужно создать typemap, потому что стандартный Perl в настоящее время не поддерживает тип const char *. Включите новый раздел TYPEMAP в ваш XS-код перед указанной функцией:

TYPEMAP: <<END
const char *    T_PV
END

Теперь выполните perl в верхнем уровне Makefile.PL. Обратите внимание, что он также создал Makefile в директории mylib. Выполните make и наблюдайте, как он входит в директорию mylib и выполняет make там тоже.

Теперь отредактируйте скрипт Mytest2.t и измените количество тестов на "5", а также добавьте следующие строки в конец скрипта:

is( Mytest2::foo( 1, 2, "Hello, world!" ), 7 );
is( Mytest2::foo( 1, 2, "0.0" ),           7 );
ok( abs( Mytest2::foo( 0, 0, "-3.4" ) - 0.6 ) <= 0.01 );

(При работе с сравнениями чисел с плавающей точкой лучше не проверять равенство, а проверять, что различие между ожидаемым и фактическим результатом меньше определённого значения (называемого epsilon), которое в данном случае равно 0,01)

Запустите "make test", и всё должно быть хорошо. Есть некоторые предупреждения о пропущенных тестах для расширения Mytest2::mylib, но ими можно пренебречь.

Что произошло здесь?

В отличие от предыдущих примеров, мы теперь запустили h2xs на реальном заголовочном файле. Это привело к появлению дополнительных элементов как в файлах .pm, так и в файлах .xs.

  • В файле .xs теперь есть директива #include с абсолютным путем к файлу заголовков mylib.h. Мы изменили ее на относительный путь, чтобы иметь возможность перемещать каталог расширения по желанию.

  • Теперь в файл .xs добавлен новый код на C. Целью подпрограммы constant является предоставление значений, определенных с помощью #define в файле заголовков, для доступа из скрипта Perl (вызовом либо TESTVAL, либо &Mytest2::TESTVAL). Также добавлен код XS, позволяющий вызывать подпрограмму constant.

  • В файле .pm изначально экспортировалось имя TESTVAL в массиве @EXPORT. Это может привести к конфликтам имен. Хорошим правилом является то, что если #define используется только самими функциями C, а не пользователем, их следует удалить из массива @EXPORT. В качестве альтернативы, если вы не возражаете против использования «полного имени» переменной, вы можете перенести большинство или все элементы из массива @EXPORT в массив @EXPORT_OK.

  • Если наш файл заголовков содержал директивы #include, они не обрабатывались h2xs. В настоящее время нет хорошего решения.

  • Мы также сообщили Perl о библиотеке, которую мы построили в подкаталоге mylib. Для этого потребовалось только добавить переменную MYEXTLIB к вызову WriteMakefile и заменить подпрограмму postamble на cd в подкаталог и запуск make. Файл Makefile.PL для библиотеки немного сложнее, но не чрезмерно. Опять же, мы заменили подпрограмму postamble, чтобы вставить свой собственный код. Этот код просто указал, что создаваемая здесь библиотека является статической архивной библиотекой (в отличие от динамически загружаемой библиотеки), и предоставил команды для ее построения.

Архитектура файла .xs

В файле .xs из "ПРИМЕР 4" содержались новые элементы. Чтобы понять значение этих элементов, обратите внимание на строку:

MODULE = Mytest2                PACKAGE = Mytest2

Всё до этой строки — обычный код C, описывающий, какие заголовки включать и определяющий некоторые вспомогательные функции. На эту часть не выполняется перевод, за исключением пропущенной встроенной документации POD (см. perlpod), она переходит в сгенерированный выходной файл C, как есть.

Все после этой строки — описание функций XSUB. Эти описания переводятся xsubpp в код C, который реализует эти функции с использованием соглашений вызова Perl и делает эти функции видимыми из интерпретатора Perl.

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

Это довольно типично для файлов .xs: обычно файл .xs предоставляет интерфейс к существующей функции C. Затем эта функция C определяется где-то (либо во внешней библиотеке, либо в первой части файла .xs), и интерфейс Perl для этой функции (т.е. «Perl-клей») описан во второй части файла .xs. Ситуация в "ПРИМЕР 1", "ПРИМЕР 2" и "ПРИМЕР 3", когда вся работа выполняется внутри «Perl-клея», является скорее исключением, чем правилом.

Избавление от избыточного кода в XSUB

В "ПРИМЕРЕ 4" вторая часть файла .xs содержала следующее описание XSUB:

double
foo(a,b,c)
        int             a
        long            b
        const char *    c
    OUTPUT:
        RETVAL

Обратите внимание, что в отличие от "ПРИМЕРА 1", "ПРИМЕРА 2" и "ПРИМЕРА 3", это описание не содержит фактического кода того, что происходит во время вызова Perl-функции foo(). Чтобы понять, что происходит, можно добавить раздел CODE к этому XSUB:

double
foo(a,b,c)
        int             a
        long            b
        const char *    c
    CODE:
        RETVAL = foo(a,b,c);
    OUTPUT:
        RETVAL

Однако, эти два XSUB генерируют почти идентичный код C: компилятор xsubpp достаточно умён, чтобы вычислить раздел CODE: из первых двух строк описания XSUB. Что насчёт раздела OUTPUT:? Фактически, это абсолютно то же самое! Раздел OUTPUT: можно также удалить, если не указан раздел CODE: или PPCODE:: xsubpp может понять, что ему нужно сгенерировать раздел вызова функции, и автоматически сгенерирует также раздел OUTPUT. Таким образом, можно сократить XSUB до:

double
foo(a,b,c)
        int             a
        long            b
        const char *    c

Можно ли сделать то же самое с XSUB

int
is_even(input)
        int     input
    CODE:
        RETVAL = (input % 2 == 0);
    OUTPUT:
        RETVAL

из "ПРИМЕРА 2"? Для этого необходимо определить функцию C int is_even(int input). Как мы видели в "Архитектуре файла .xs", подходящим местом для этого определения является первая часть файла .xs. Фактически, функция C

int
is_even(int arg)
{
        return (arg % 2 == 0);
}

вероятно, избыточна для этого. Достаточно чего-то простого, например, #define:

#define is_even(arg)    ((arg) % 2 == 0)

После этого в первой части файла .xs часть «Perl-клей» становится такой простой, как

int
is_even(input)
        int     input

Этот приём разделения части «клей» от рабочей части имеет очевидные компромиссы: если вы хотите изменить Perl-интерфейс, вам нужно изменить два места в вашем коде. Однако это удаляет много лишнего и делает рабочую часть независимой от особенностей соглашений вызова Perl. (На самом деле, в приведенном выше описании нет ничего специфического для Perl, другая версия xsubpp могла бы перевести это в TCL-клей или Python-клей тоже.)

Подробнее о параметрах XSUB

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

Когда вы указываете аргументы функциям в файле .xs, вы фактически передаёте три части информации для каждого перечисленного аргумента. Первая часть — порядок этого аргумента относительно других (первый, второй и т. д.). Вторая — тип аргумента и состоит из объявления типа аргумента (например, int, char*, и т. д.). Третья часть — соглашение вызова для аргумента в вызове функции библиотеки.

В то время как Perl передаёт аргументы функциям по ссылке, C передаёт их по значению; для реализации функции C, которая изменяет данные одного из «аргументов», фактический аргумент этой функции C будет указателем на данные. Таким образом, две функции C с объявлениями

int string_length(char *s);
int upper_case_char(char *cp);

могут иметь совершенно разные семантики: первая может инспектировать массив символов, указанный s, а вторая может немедленно разыменовать cp и манипулировать *cp только (используя возвращаемое значение, например, как индикатор успеха). Из Perl вы будете использовать эти функции совершенно по-разному.

Вы передаёте эту информацию xsubpp, заменив * перед аргументом на &. & означает, что аргумент должен передаваться функции библиотеки по его адресу. Две вышеупомянутые функции могут быть XSUB-изированы как

int
string_length(s)
        char *  s

int
upper_case_char(cp)
        char    &cp

Например, рассмотрите:

int
foo(a,b)
        char    &a
        char *  b

Первый Perl-аргумент этой функции будет обрабатываться как символ и присваиваться переменной a, а его адрес будет передан в функцию foo. Второй Perl-аргумент будет обрабатываться как указатель на строку и присваиваться переменной b. Значение b будет передано в функцию foo. Фактический вызов функции foo, который генерирует xsubpp, будет выглядеть так:

foo(&a, b);

xsubpp будет анализировать следующие списки аргументов функций идентично:

char    &a
char&a
char    & a

Однако, для облегчения понимания рекомендуется помещать «&» рядом с именем переменной и в стороне от типа переменной, а «*» рядом с типом переменной, но в стороне от имени переменной (как в вызове foo выше). Таким образом, легко понять, что будет передано в функцию C; это будет то, что находится в «последнем столбце».

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

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

Если мы посмотрим на любой код C, сгенерированный любым из примеров, кроме примера 1, мы увидим ряд ссылок на ST(n), где n обычно равно 0. «ST» — это фактически макрос, указывающий на n-й аргумент в стеке аргументов. ST(0) — это, таким образом, первый аргумент в стеке, а, следовательно, первый аргумент, переданный в XSUB, ST(1) — второй аргумент и так далее.

Когда вы перечисляете аргументы в XSUB в файле .xs, это говорит xsubpp, какой аргумент соответствует какому из стека аргументов (т.е. первый перечисленный — первый аргумент и т. д.). Вы приглашаете беду, если не перечислите их в том же порядке, что и функция.

Фактические значения в стеке аргументов — указатели на переданные значения. Когда аргумент указан как значение OUTPUT, соответствующее значение в стеке (т. е. ST(0), если это был первый аргумент) изменяется. Вы можете проверить это, посмотрев на код C, сгенерированный для Примера 3. Код для XSUB-функции round() содержит строки, похожие на эту:

double  arg = (double)SvNV(ST(0));
/* Round the contents of the variable arg */
sv_setnv(ST(0), (double)arg);

Переменная arg изначально устанавливается путём взятия значения из ST(0), а затем сохраняется обратно в ST(0) в конце подпрограммы.

XSUB также могут возвращать списки, а не только скаляры. Это необходимо делать, манипулируя значениями стека ST(0), ST(1) и т. д., несколько по-другому. См. perlxs для получения подробностей.

XSUB также могут избегать автоматического преобразования аргументов Perl-функции в аргументы C-функции. См. perlxs для получения подробностей. Некоторые люди предпочитают ручное преобразование путём проверки ST(i) даже в тех случаях, когда автоматическое преобразование будет работать, утверждая, что это делает логику вызова XSUB более ясной. Сравните с "Избавлением от избыточного кода в XSUB" для аналогичного компромисса с полным разделением частей "Perl-клей" и "рабочая лошадка" XSUB.

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

Расширение вашего расширения

Иногда вам может понадобиться предоставить дополнительные методы или подпрограммы для упрощения или улучшения понимания интерфейса между Perl и вашим расширением. Эти подпрограммы должны находиться в файле .pm. Их автоматическая загрузка при загрузке самого расширения или только при вызове зависит от того, где в файле .pm определена подпрограмма. Вы также можете обратиться к AutoLoader для альтернативного способа хранения и загрузки ваших дополнительных подпрограмм.

Документирование вашего расширения

У вас нет никаких причин не документировать ваше расширение. Документация должна находится в файле .pm. Этот файл будет передан в pod2man, и встроенная документация будет преобразована в формат manpage, а затем помещена в каталог blib. Она будет скопирована в каталог manpage Perl при установке расширения.

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

См. perlpod для получения дополнительной информации о формате pod.

Установка вашего расширения

После того, как ваше расширение завершено и прошло все тесты, его установка довольно проста: вам просто нужно запустить «make install». Вам либо понадобится разрешение на запись в каталоги, где установлен Perl, либо попросите системного администратора запустить make за вас.

В качестве альтернативы вы можете указать точный каталог для размещения файлов расширения, поместив «PREFIX=/destination/directory» после make install (или между make и install, если у вас устаревшая версия make). Это может быть очень полезно, если вы создаёте расширение, которое в конечном итоге будет распространяться на несколько систем. Затем вы можете просто архивировать файлы в целевом каталоге и распространить их на целевые системы.

ПРИМЕР 5

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

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

Вернитесь в каталог Mytest и добавьте следующий код в конец файла Mytest.xs:

void
statfs(path)
        char *  path
    INIT:
        int i;
        struct statfs buf;

    PPCODE:
        i = statfs(path, &buf);
        if (i == 0) {
                XPUSHs(sv_2mortal(newSVnv(buf.f_bavail)));
                XPUSHs(sv_2mortal(newSVnv(buf.f_bfree)));
                XPUSHs(sv_2mortal(newSVnv(buf.f_blocks)));
                XPUSHs(sv_2mortal(newSVnv(buf.f_bsize)));
                XPUSHs(sv_2mortal(newSVnv(buf.f_ffree)));
                XPUSHs(sv_2mortal(newSVnv(buf.f_files)));
                XPUSHs(sv_2mortal(newSVnv(buf.f_type)));
        } else {
                XPUSHs(sv_2mortal(newSVnv(errno)));
        }

Вам также необходимо добавить следующий код в начало файла .xs, сразу после включения «XSUB.h»:

#include <sys/vfs.h>

Также добавьте следующий фрагмент кода в Mytest.t, увеличив количество тестов с «9» до «11»:

my @a;

    @a = Mytest::statfs("/blech");
    ok( scalar(@a) == 1 && $a[0] == 2 );

    @a = Mytest::statfs("/");
    is( scalar(@a), 7 );

Новые аспекты в этом примере

В этом примере было добавлено довольно много новых концепций. Мы рассмотрим их по отдельности.

  • Директива INIT содержит код, который будет размещен сразу после декодирования стека аргументов. C не позволяет объявлять переменные в произвольных местах внутри функции, поэтому это обычно лучший способ объявить локальные переменные, необходимые XSUB. (В качестве альтернативы можно поместить весь блок PPCODE: в фигурные скобки и поместить эти объявления вверху.)

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

    Мы делаем это, используя директиву PPCODE:, а не директиву CODE:. Это сообщает xsubpp, что мы будем сами управлять значениями возврата, которые будут помещены в стек аргументов.

  • Когда мы хотим поместить значения, которые должны быть возвращены вызывающей программе, в стек, мы используем серию макросов, начинающихся с «XPUSH». Существует пять различных версий, для размещения целых чисел, беззнаковых целых чисел, двойных значений, строк и перловских скаляров в стеке. В нашем примере мы поместили перловский скаляр в стек. (На самом деле это единственный макрос, который может использоваться для возвращения нескольких значений.)

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

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

  • Если мы были заинтересованы в производительности, а не в компактности кода, в успешном ветвлении мы бы не использовали макросы XPUSHs, а макросы PUSHs, и предварительно расширили бы стек, прежде чем помещать в него возвращаемые значения:

    EXTEND(SP, 7);

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

    Аналогично, в ветвлении ошибки мы могли бы использовать PUSHs без расширения стека: ссылка на функцию Perl попадает в XSUB в стеке, следовательно, стек всегда достаточно велик, чтобы принять одно возвращаемое значение.

ПРИМЕР 6

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

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

Вернитесь в каталог Mytest и добавьте следующий код в конец файла Mytest.xs:

SV *
multi_statfs(paths)
        SV * paths
    INIT:
        AV * results;
        SSize_t numpaths = 0, n;
        int i;
        struct statfs buf;

        SvGETMAGIC(paths);
        if ((!SvROK(paths))
            || (SvTYPE(SvRV(paths)) != SVt_PVAV)
            || ((numpaths = av_top_index((AV *)SvRV(paths))) < 0))
        {
            XSRETURN_UNDEF;
        }
        results = (AV *)sv_2mortal((SV *)newAV());
    CODE:
        for (n = 0; n <= numpaths; n++) {
            HV * rh;
            STRLEN l;
            SV * path = *av_fetch((AV *)SvRV(paths), n, 0);
            char * fn = SvPVbyte(path, l);

            i = statfs(fn, &buf);
            if (i != 0) {
                av_push(results, newSVnv(errno));
                continue;
            }

            rh = (HV *)sv_2mortal((SV *)newHV());

            hv_store(rh, "f_bavail", 8, newSVnv(buf.f_bavail), 0);
            hv_store(rh, "f_bfree",  7, newSVnv(buf.f_bfree),  0);
            hv_store(rh, "f_blocks", 8, newSVnv(buf.f_blocks), 0);
            hv_store(rh, "f_bsize",  7, newSVnv(buf.f_bsize),  0);
            hv_store(rh, "f_ffree",  7, newSVnv(buf.f_ffree),  0);
            hv_store(rh, "f_files",  7, newSVnv(buf.f_files),  0);
            hv_store(rh, "f_type",   6, newSVnv(buf.f_type),   0);

            av_push(results, newRV_inc((SV *)rh));
        }
        RETVAL = newRV_inc((SV *)results);
    OUTPUT:
        RETVAL

И добавьте следующий код в Mytest.t, увеличив количество тестов с «11» до «13»:

my $results = Mytest::multi_statfs([ '/', '/blech' ]);
ok( ref $results->[0] );
ok( ! ref $results->[1] );

Новые аспекты в этом примере

Здесь введено несколько новых концепций, описанных ниже:

  • Эта функция не использует typemap. Вместо этого мы объявляем её как принимающую один параметр SV* (скаляр) и возвращающую значение SV*, и мы позаботились о заполнении этих скаляров в коде. Поскольку мы возвращаем только одно значение, нам не нужна директива PPCODE: — вместо этого мы используем директивы CODE: и OUTPUT:.

  • При работе со ссылками важно обращаться с ними осторожно. Блок INIT: сначала вызывает SvGETMAGIC(paths), на случай, если paths является связанной переменной. Затем он проверяет, что SvROK возвращает true, что указывает, что paths является допустимой ссылкой. (Простая проверка SvROK не вызовет FETCH для связанной переменной.) Затем он проверяет, что объект, на который ссылается paths, является массивом, используя SvRV для разыменования paths и SvTYPE для определения его типа. В качестве дополнительной проверки он проверяет, что массив, на который ссылается paths, не пуст, используя функцию av_top_index (которая возвращает -1, если массив пустой). Макрос XSRETURN_UNDEF используется для прерывания XSUB и возвращения неопределенного значения всякий раз, когда не выполняются все три условия.

  • В этой XSUB мы работаем с несколькими массивами. Обратите внимание, что массив представлен внутренне указателем AV*. Функции и макросы для работы с массивами аналогичны функциям в Perl: av_top_index возвращает максимальный индекс в AV*, так же как $#array; av_fetch извлекает отдельное скалярное значение из массива по заданному индексу; av_push добавляет скалярное значение в конец массива, автоматически расширяя массив по мере необходимости.

    В частности, мы считываем имена путей по одному из входного массива и сохраняем результаты в выходном массиве (results) в том же порядке. Если statfs завершается неудачно, элемент, добавленный в возвращаемый массив, равен значению errno после ошибки. Если statfs завершается успешно, то добавленное в возвращаемый массив значение является ссылкой на хеш, содержащий некоторую информацию из структуры statfs.

    Как и со стеком возврата, было бы возможно (и небольшое преимущество в производительности) предварительно расширить возвращаемый массив, прежде чем помещать данные в него, поскольку мы знаем, сколько элементов мы вернем:

    av_extend(results, numpaths);
  • В этой функции мы выполнили только одну операцию с хешем, которая заключается в хранении нового скаляра под ключом с помощью hv_store. Хеш представлен указателем HV*. Как и массивы, функции для работы с хешами из XSUB отражают функциональность, доступную в Perl. См. perlguts и perlapi для получения подробностей.

  • Для создания ссылки мы используем функцию newRV_inc. Обратите внимание, что в этом случае вы можете преобразовать AV* или HV* в тип SV* (и во многих других). Это позволяет вам получать ссылки на массивы, хеши и скаляры с помощью одной функции. Наоборот, функция SvRV всегда возвращает SV*, который может потребоваться преобразовать в соответствующий тип, если это что-то другое, кроме скаляра (проверьте с SvTYPE).

  • На данный момент xsubpp выполняет очень мало работы — различия между Mytest.xs и Mytest.c минимальны.

ПРИМЕР 7 (Скоро)

XPUSH аргументы и установка RETVAL и присвоение возвращаемого значения массиву

ПРИМЕР 8 (Скоро)

Установка $!

ПРИМЕР 9 Передача открытых файлов в XSes

Вы могли бы подумать, что передача файлов в XS сложна, со всеми глобальными указателями и прочим. Нет.

Предположим, что по какой-то странной причине нам нужен оболочка вокруг стандартной функции библиотеки C fputs(). Это все, что нам нужно:

#define PERLIO_NOT_STDIO 0  /* For co-existence with stdio only */
#define PERL_NO_GET_CONTEXT /* This is more efficient */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <stdio.h>

int
fputs(s, stream)
  char *          s
  FILE *          stream

Действительная работа выполняется в стандартном typemap.

Для получения более подробной информации см. "Совместимость со stdio" в perlapio.

Но вы теряете все тонкости, выполняемые слоями perlio. Это вызывает функцию stdio fputs(), которая ничего не знает о них.

Стандартный typemap предлагает три варианта PerlIO *: InputStream (T_IN), InOutStream (T_INOUT) и OutputStream (T_OUT). Простой PerlIO * считается T_INOUT. Если это имеет значение в вашем коде (см. ниже, почему это может быть важно), определите или используйте один из конкретных имен и используйте его в качестве типа аргумента или результата в вашем файле XS.

Стандартный typemap не содержит PerlIO * перед perl 5.7, но он имеет три варианта потоков. Использование PerlIO * непосредственно не является обратной совместимостью, если вы не предоставите свой собственный typemap.

Для потоков, поступающих из perl, основное отличие заключается в том, что OutputStream получит выходной PerlIO * — это может повлиять на сокет. Как в нашем примере...

Для потоков, передаваемых в perl, создаётся новая дескриптор файла (то есть ссылка на новую переменную) и связывается с предоставленным PerlIO *. Если состояние чтения/записи PerlIO * неверно, вы можете получить ошибки или предупреждения при использовании дескриптора файла. Таким образом, если вы открыли PerlIO * как "w", то на самом деле это должно быть OutputStream, если открыто как "r", то это должно быть InputStream.

Теперь, предположим, что вы хотите использовать слои perlio в вашем XS. Мы будем использовать функцию perlio PerlIO_puts() в качестве примера.

В C-части файла XS (над первой строкой MODULE) у вас есть

    #define OutputStream    PerlIO *
or
    typedef PerlIO *        OutputStream;

И это код XS:

int
perlioputs(s, stream)
        char *          s
        OutputStream    stream
CODE:
        RETVAL = PerlIO_puts(stream, s);
OUTPUT:
        RETVAL

Мы должны использовать блок CODE, потому что PerlIO_puts() имеет обратный порядок аргументов по сравнению с fputs(), и мы хотим сохранить аргументы неизменными.

Желание тщательно изучить это, мы хотим использовать stdio fputs() на PerlIO *. Это означает, что мы должны запросить у системы perlio stdio FILE *:

int
perliofputs(s, stream)
        char *          s
        OutputStream    stream
PREINIT:
        FILE *fp = PerlIO_findFILE(stream);
CODE:
        if (fp != (FILE*) 0) {
                RETVAL = fputs(s, fp);
        } else {
                RETVAL = -1;
        }
OUTPUT:
        RETVAL

Примечание: PerlIO_findFILE() будет искать слой stdio. Если его не найдёт, он вызовет PerlIO_exportFILE() для создания нового слоя stdio FILE. Пожалуйста, вызывайте PerlIO_exportFILE() только если вам нужен новый FILE. Он будет создавать новый при каждом вызове и добавлять новый слой stdio. Поэтому не вызывайте его повторно для одного и того же файла. PerlIO_findFILE() извлечёт слой stdio после того, как он был создан PerlIO_exportFILE().

Это относится только к системе perlio. Для версий до 5.7 PerlIO_exportFILE() эквивалентно PerlIO_findFILE().

Решение проблем с этими примерами

Как упоминалось в начале этого документа, если у вас возникли проблемы с этими примерами расширений, вы можете попробовать найти решение здесь.

  • В версиях 5.002 до версии gamma, скрипт теста в Примере 1 не будет работать должным образом. Вам нужно изменить строку «use lib» на:

    use lib './blib';
  • В версиях 5.002 до версии 5.002b1h файл test.pl не создавался автоматически с помощью h2xs. Это означает, что вы не можете сказать «make test» для запуска скрипта теста. Вам нужно добавить следующую строку перед оператором «use extension»:

    use lib './blib';
  • В версиях 5.000 и 5.001 вместо вышеприведенной строки вам нужно использовать следующую строку:

    BEGIN { unshift(@INC, "./blib") }
  • Этот документ предполагает, что исполняемый файл под названием «perl» — это Perl версии 5. Некоторые системы могут иметь установленный Perl версии 5 под именем «perl5».

См. также

Для получения дополнительной информации обратитесь к perlguts, perlapi, perlxs, perlmod, perlapio и perlpod

Автор

Jeff Okamoto <okamoto@corp.hp.com>

Обзор и помощь от Dean Roehrich, Ilya Zakharevich, Andreas Koenig и Tim Bunce.

Материалы по PerlIO предоставлены Lupe Christoph с некоторыми уточнениями от Nick Ing-Simmons.

Изменения для h2xs, начиная с Perl 5.8.x, от Renee Baecker

Этот документ теперь поддерживается как часть самого Perl.

Последнее изменение

2020-10-05

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

Spec-Zone.ru

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