Spec-Zone.ru › Perl 5.34

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          => '-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
%

Вы можете спокойно пропустить строку о "поведении прототипирования" — она объяснена в "Раздел о ключевом слове PROTOTYPES:" в 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». Если вы попытаетесь запустить скрипт теста сам по себе, вы получите ошибку с фатальным исходом. Ещё одна причина, по которой важно использовать «make test» для запуска скрипта теста, заключается в том, что если вы тестируете обновление уже существующей версии, использование «make test» гарантирует, что вы протестируете ваше новое расширение, а не уже существующую версию.

Когда Perl видит use extension;, он ищет файл с таким же именем, как use расширение с расширением .pm. Если этот файл не найден, Perl завершается с фатальной ошибкой. Путь поиска по умолчанию содержится в массиве @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 завершится с фатальной ошибкой. 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, вместо написания отдельных файлов. В любом случае, этот типmap состоит из трёх частей:

Первый раздел сопоставляет различные типы данных 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 (или раздела), вы увидите, что doubles имеют тип 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, чтобы перейти в подкаталог и запустить 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, встроенная документация будет преобразована в формат man, а затем размещена в каталоге blib. Он будет скопирован в каталог man страниц Perl при установке расширения.

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

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

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

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

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

ПРИМЕР 5

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

Это расширение ориентировано на Unix (структура 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". Существует пять различных версий для размещения целых чисел, беззнаковых целых чисел, чисел с плавающей точкой, строк и скаляров 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;
            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. Если это имеет значение в вашем коде (см. ниже, почему это может быть важно), #define или typedef одно из конкретных имен и используйте это имя в качестве аргумента или типа результата в вашем файле 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

Автор

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

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

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

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

Этот документ теперь поддерживается как часть самого 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.34.0/perlxstut

Spec-Zone.ru

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