perlxstut
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- СПЕЦИАЛЬНЫЕ ЗАМЕЧАНИЯ
- УРОК
- ПРИМЕР 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". Это создаст реальный 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". Если вы попытаетесь запустить скрипт теста самостоятельно, вы получите ошибку 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 вместо записи отдельных файлов. В любом случае, этот тип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 (или раздела), вы увидите, что числа с плавающей точкой имеют тип 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 ); (При работе с сравнениями чисел с плавающей точкой лучше не проверять на равенство, а проверять, что разница между ожидаемым и фактическим результатом меньше определённого значения (называемого эпсилон), которое в данном случае составляет 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 и замены подпрограммы постскрипта на cd в подкаталог и выполнение make. Makefile.PL для библиотеки немного сложнее, но не чрезмерно. Опять же, мы заменили подпрограмму постскрипта на свой код. Этот код просто указал, что создаваемая здесь библиотека — это статическая архивная библиотека (в отличие от динамически загружаемой библиотеки), и предоставил команды для ее построения.
Структура файла .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
Во второй части файла .xs из "ПРИМЕР 4" содержалось следующее описание 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-деталях может предпочесть способ, который максимально не зависит от Perl-деталей, то есть автоматическое преобразование и автоматическое создание вызова, как в "Удалении лишнего из XSUB". Этот подход имеет дополнительное преимущество в том, что защищает автора XSUB от будущих изменений в API Perl.
Расширение вашего расширения
Иногда вам может потребоваться предоставить дополнительные методы или подпрограммы, чтобы упростить или сделать более понятным интерфейс между Perl и вашим расширением. Эти подпрограммы должны располагаться в файле .pm. Загрузка их автоматически при загрузке самого расширения или только при вызове зависит от того, где в файле .pm определена подпрограмма. Также вы можете обратиться к AutoLoader для альтернативного способа хранения и загрузки дополнительных подпрограмм.
Документирование вашего расширения
Нет абсолютно никаких оправданий для того, чтобы не документировать ваше расширение. Документация должна находиться в файле .pm. Этот файл будет передан в pod2man, встроенная документация будет преобразована в формат man-страницы, а затем размещена в каталоге blib. При установке расширения она будет скопирована в каталог man-страниц Perl.
Вы можете перемежать документацию и код Perl в файле .pm. На самом деле, если вы хотите использовать автоматическую загрузку методов, вам необходимо это сделать, как объясняется в комментарии внутри файла .pm.
См. perlpod для получения дополнительной информации о формате pod.
Установка вашего расширения
После завершения разработки вашего расширения и успешного прохождения всех тестов его установка очень проста: просто запустите команду «make install». Вам потребуется разрешение на запись в каталоги, где установлен Perl, или попросите системного администратора запустить make за вас.
В качестве альтернативы вы можете указать точный каталог для размещения файлов расширения, добавив «PREFIX=/destination/directory» после make install (или между make и install, если у вас очень примитивная версия make). Это может быть очень полезно, если вы разрабатываете расширение, которое в конечном итоге будет распространено на несколько систем. Затем вы можете просто архивировать файлы в целевом каталоге и распространить их на ваши целевые системы.
ПРИМЕР 5
В этом примере мы выполним дополнительную работу со стеком аргументов. Все предыдущие примеры возвращали только одно значение. Теперь мы создадим расширение, которое возвращает массив.
Это расширение очень ориентировано на Unix (struct statfs и системный вызов statfs). Если вы работаете не на Unix-системе, вы можете заменить statfs любой другой функцией, возвращающей несколько значений, вы можете жестко закодировать значения для возврата вызывающей стороне (хотя это будет немного сложнее при тестировании ошибок) или просто не выполнять этот пример. Если вы измените XSUB, обязательно исправьте тестовые случаи, чтобы они соответствовали изменениям.
Возвращайтесь в каталог Mytest и добавьте следующий код в конец файла Mytest.xs:
void
statfs(path)
char * path
INIT:
int i;
struct statfs buf;
PPCODE:
i = statfs(path, &buf);
if (i == 0) {
XPUSHs(sv_2mortal(newSVnv(buf.f_bavail)));
XPUSHs(sv_2mortal(newSVnv(buf.f_bfree)));
XPUSHs(sv_2mortal(newSVnv(buf.f_blocks)));
XPUSHs(sv_2mortal(newSVnv(buf.f_bsize)));
XPUSHs(sv_2mortal(newSVnv(buf.f_ffree)));
XPUSHs(sv_2mortal(newSVnv(buf.f_files)));
XPUSHs(sv_2mortal(newSVnv(buf.f_type)));
} else {
XPUSHs(sv_2mortal(newSVnv(errno)));
} Вам также необходимо добавить следующий код в начало файла .xs, сразу после включения «XSUB.h»:
#include <sys/vfs.h> Также добавьте следующий фрагмент кода в Mytest.t, увеличив количество тестов с «9» до «11»:
@a = &Mytest::statfs("/blech");
ok( scalar(@a) == 1 && $a[0] == 2 );
@a = &Mytest::statfs("/");
is( scalar(@a), 7 ); Новые возможности в этом примере
Этот пример добавил довольно много новых концепций. Мы рассмотрим их по одной.
-
Директива INIT содержит код, который будет помещён сразу после декодирования стека аргументов. C не позволяет объявлять переменные в произвольных местах внутри функции, поэтому это обычно лучший способ объявления локальных переменных, необходимых для XSUB. (В качестве альтернативы, можно поместить весь раздел
PPCODE:в фигурные скобки и поместить эти объявления вверху.) -
Эта подпрограмма также возвращает разное количество аргументов в зависимости от успешного или неудачного выполнения вызова statfs. При ошибке возвращается номер ошибки в виде массива из одного элемента. Если вызов успешен, возвращается массив из 7 элементов. Поскольку в эту функцию передаётся только один аргумент, нам нужно место в стеке для хранения 7 значений, которые могут быть возвращены.
Мы делаем это, используя директиву PPCODE, а не CODE. Это сообщает xsubpp, что мы будем сами управлять значениями возврата, которые будут помещены в стек аргументов.
-
Когда мы хотим поместить значения, возвращаемые вызывающей стороне, в стек, мы используем серию макросов, начинающихся с «XPUSH». Существует пять различных версий для помещения целых чисел, беззнаковых целых чисел, чисел с плавающей точкой, строк и перловых скаляров в стек. В нашем примере мы поместили перловый скаляр в стек. (На самом деле это единственный макрос, который можно использовать для возвращения нескольких значений.)
Макросы XPUSH* автоматически расширяют стек возврата, чтобы предотвратить его переполнение. Вы помещаете значения в стек в том порядке, в котором вы хотите их видеть вызывающей программе.
-
Значения, помещенные в стек возврата XSUB, фактически являются временными SV. Они сделаны временными, чтобы после того, как значения скопированы вызывающей программой, SV, содержащие возвращаемые значения, можно было освободить. Если они не были бы временными, они продолжали бы существовать после возврата подпрограммы XSUB, но не были бы доступны. Это утечка памяти.
-
Если мы были заинтересованы в производительности, а не в компактности кода, в успешном случае мы бы не использовали макросы
XPUSHs, а макросыPUSHs, и предварительно расширили бы стек перед помещением возвращаемых значений:EXTEND(SP, 7);Компромисс заключается в том, что нужно предварительно рассчитать количество возвращаемых значений (хотя переполнение стека обычно не причинит никакого вреда, кроме потребления памяти).
Аналогично, в случае неудачи мы могли бы использовать
PUSHsбез расширения стека: ссылка на перловую функцию попадает в 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] ); Новые возможности в этом примере
Здесь представлено несколько новых концепций, описанных ниже:
-
Эта функция не использует 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 сложна из-за всех 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(), которая ничего не знает о них.
Стандартный 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 и perlpod.
Автор
Jeff Okamoto <okamoto@corp.hp.com>
Проверено и поддержано Dean Roehrich, Ilya Zakharevich, Andreas Koenig и Tim Bunce.
Материал по PerlIO предоставлен Lupe Christoph, с некоторыми уточнениями Nick Ing-Simmons.
Изменения для h2xs начиная с Perl 5.8.x от Renee Baecker
Последнее изменение
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.32.0/perlxstut