Spec-Zone.ru › Perl 5.30

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» в примерах, которые следуют за этим, вам может потребоваться заменить его на программу 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          => '',     # e.g., '-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». Это создаст необходимый для make Makefile. Его вывод примерно такой:

% 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" и добавьте следующие тесты:

$i = -1.5; &Mytest::round($i); is( $i, -2.0 );
$i = -1.1; &Mytest::round($i); is( $i, -1.0 );
$i = 0.0; &Mytest::round($i);  is( $i,  0.0 );
$i = 0.5; &Mytest::round($i);  is( $i,  1.0 );
$i = 1.2; &Mytest::round($i);  is( $i,  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 (или секции), вы увидите, что double имеют тип 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", следует заменить на следующие три строки:

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 и измените количество тестов на "4", а затем добавьте следующие строки в конец скрипта:

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 для перехода в подкаталог и запуска 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), если это был первый аргумент) изменяется. Вы можете убедиться в этом, посмотрев на сгенерированный код для Примера 3. Код для процедуры round() XSUB содержит строки, похожие на эту:

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=/путь/к/каталогу» после 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":

@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". Существуют пять различных версий для размещения целых чисел, беззнаковых целых чисел, чисел с плавающей запятой, строк и скаляров Perl в стеке. В нашем примере мы поместили скаляр Perl в стек. (Фактически, это единственный макрос, который может использоваться для возврата нескольких значений.)

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

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

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

    EXTEND(SP, 7);

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

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

ПРИМЕР 6

В этом примере мы будем принимать ссылку на массив в качестве входного параметра и возвращать ссылку на массив хешей. Это продемонстрирует манипуляции со сложными типами данных Perl из 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;
            char * fn = SvPV(*av_fetch((AV *)SvRV(paths), n, 0), 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":

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

Новые возможности в этом примере

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

  • Эта функция не использует типмап. Вместо этого мы объявляем ее как принимающую один параметр 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 args И УСТАНОВКА RETVAL И присвоение возвращаемого значения массиву

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

Установление $!

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

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

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

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

#include <stdio.h>

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

Фактическая работа выполняется в стандартном typemap.

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

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

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

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

Для потоков, передаваемых в Perl, создается новый дескриптор файла (т.е. ссылка на новую переменную glob) и ассоциируется с предоставленным 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 и perlpod.

Автор

Джефф Окамото <okamoto@corp.hp.com>

Проверено и помогали Дином Роэрихом, Ильей Захаревичем, Андреасом Кёнигом и Тимом Бэнсом.

Материал по PerlIO внес Лупе Кристоф, с некоторыми уточнениями Ника Инг-Симмонса.

Изменения для h2xs, начиная с Perl 5.8.x, внесла Рене Беккер

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

2012-01-20

© 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.30.3/perlxstut

Spec-Zone.ru

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