这是命令 perlxstut,可以使用我们的多个免费在线工作站之一在 OnWorks 免费托管服务提供商中运行,例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器
程序:
您的姓名
perlxstut - 编写 XSUB 的教程
商品描述
本教程将向读者介绍创建 Perl 扩展所涉及的步骤。
假定读者可以访问 perlguts、perlapi 和 perlxs。
本教程从非常简单的示例开始,然后随着每个新示例变得更加复杂
例如添加新功能。 某些概念可能要到以后才能完全解释
在教程中,以便让读者慢慢地轻松构建扩展。
本教程是从 Unix 的角度编写的。 我知道他们不是这样的
其他平台(例如Win32)不同,我将列出它们。 如果你发现一些东西
错过了,请告诉我。
特别 附注
使
本教程假设 Perl 配置使用的 make 程序被调用
“制作”。 在接下来的示例中,您可能需要替换为,而不是运行“make”
任何已配置为 Perl 使用的 make 程序。 跑步 perl的 -V:制作 应该告诉
你这是什么。
版本 警告
在为一般消费编写 Perl 扩展时,应该期望
扩展将与 Perl 版本一起使用,这与您的可用版本不同
机器。 由于您正在阅读本文档,因此您机器上的 Perl 版本是
可能是 5.005 或更高版本,但您的扩展程序的用户可能有更古老的版本。
了解可能会出现哪些类型的不兼容,以及在极少数情况下
您机器上的 Perl 版本比本文档旧,请参阅有关
“对这些示例进行故障排除”了解更多信息。
如果您的扩展程序使用了旧版本的 Perl 中不可用的某些功能
Perl,您的用户会喜欢早期有意义的警告。 你可能会放
这些信息进入 读我 文件,但现在安装扩展可能是
自动执行,由 CPAN.pm 模块或其他工具。
在基于 MakeMaker 的安装中, 生成文件 提供最早的表演机会
版本检查。 可以把这样的东西放进去 生成文件 以此目的:
评估 { 需要 5.007 }
要么死<
############
### 该模块使用了不可用的 frobnication 框架
### 在 Perl 5.007 版本之前。 之前升级你的 Perl
### 安装 Kara::Mba。
############
EOD
动态 装载 而不是 静止 装载
通常认为,如果系统没有能力动态加载
库,您不能构建 XSUB。 这是不正确的。 你 能够 建造它们,但你必须
将 XSUB 子例程与 Perl 的其余部分链接起来,创建一个新的可执行文件。 这
情况类似于 Perl 4。
本教程仍然可以在这样的系统上使用。 XSUB 构建机制将检查
系统并尽可能构建一个可动态加载的库,否则构建一个静态库和
然后,可选地,一个新的静态链接的可执行文件,其中链接了该静态库。
如果您希望在一个可以动态运行的系统上构建一个静态链接的可执行文件
加载库,您可以在以下所有示例中,其中没有命令 ""make""
参数被执行,运行命令““make perl””代替。
如果您选择生成了这样一个静态链接的可执行文件,那么代替
说 ""make test"",你应该说 ""make test_static""。 在无法构建的系统上
完全可动态加载的库,只需说“制作测试”就足够了。
Threads 和 PERL_NO_GET_CONTEXT
对于线程构建,perl 需要当前线程的上下文指针,而不需要
"PERL_NO_GET_CONTEXT",perl 会调用一个函数来检索上下文。
为了提高性能,包括:
#定义 PERL_NO_GET_CONTEXT
如下所示。
有关更多详细信息,请参阅 perlguts。
在线课程
现在让我们继续表演吧!
例 1
我们的第一个扩展将非常简单。 当我们调用扩展中的例程时,它
将打印出一条众所周知的消息并返回。
运行""h2xs -A -n Mytest""。 这将创建一个名为 Mytest 的目录,可能在 ext/ 下,如果
该目录存在于当前工作目录中。 将创建几个文件
Mytest目录下,包括MANIFEST、Makefile.PL、lib/Mytest.pm、Mytest.xs、
t/Mytest.t 和更改。
MANIFEST 文件包含刚刚在 Mytest 中创建的所有文件的名称
目录。
文件 Makefile.PL 应如下所示:
使用 ExtUtils::MakeMaker;
# 影响细节见lib/ExtUtils/MakeMaker.pm
# 写入的 Makefile 的内容。
写Makefile(
NAME => 'Mytest',
VERSION_FROM => 'Mytest.pm', # 找到 $VERSION
LIBS => [''], # 例如,'-lm'
DEFINE => '', # 例如,'-DHAVE_SOMETHING'
INC => '', # 例如,'-I/usr/include/other'
);
文件 Mytest.pm 应以如下内容开头:
包Mytest;
使用 5.008008;
用严格;
使用警告;
要求出口商;
我们的@ISA = qw(Exporter);
我们的 %EXPORT_TAGS = ( 'all' => [ qw(
) ]);
我们的@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
我们的@EXPORT = qw(
);
我们的 $VERSION = '0.01';
需要 XSLoader;
XSLoader::load('Mytest', $VERSION);
# 预加载的方法放在这里。
1;
__结尾__
# 下面是你的模块的文档存根。 你最好
# 编辑它!
.pm 文件的其余部分包含用于提供文档的示例代码
延期。
最后,Mytest.xs 文件应如下所示:
#定义 PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
模块 = 我的测试包 = 我的测试
让我们通过将以下内容添加到文件末尾来编辑 .xs 文件:
无效
你好()
代码:
printf("你好,世界!\n");
以“CODE:”开头的行不缩进是可以的。 然而,对于
出于可读性考虑,建议您缩进 CODE: 一级和行
再下一层。
现在我们将运行""perl Makefile.PL""。 这将创建一个真正的 Makefile,它需要 make。
它的输出类似于:
% perl 生成文件.PL
检查您的套件是否完整...
看起来不错
为 Mytest 编写 Makefile
%
现在,运行 make 将产生看起来像这样的输出(一些长行有
为清楚起见已缩短,并删除了一些无关的行):
% 制作
cp lib/Mytest.pm blib/lib/Mytest.pm
perl xsubpp -typemap typemap Mytest.xs > Mytest.xsc && \
mv Mytest.xsc Mytest.c
请指定 Mytest.xs 的原型设计行为(请参阅 perlxs 手册)
cc -c Mytest.c
为 Mytest 运行 Mkbootstrap ()
chmod 644 我的测试.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 文件
修改 blib/man3/Mytest.3pm
%
你可以放心地忽略关于“原型设计行为”的那一行——它在“The
原型:perlxs 中的关键字”。
Perl 有自己的特殊方式来轻松编写测试脚本,但仅针对此示例,
我们将创建我们自己的测试脚本。 创建一个名为 hello 的文件,如下所示:
#! /opt/perl5/bin/perl
使用 ExtUtils::testlib;
使用我的测试;
我的测试::你好();
现在我们使脚本可执行(“chmod +x hello”),运行脚本,我们应该看到
以下输出:
% 。/你好
您好,世界!
%
例 2
现在让我们向我们的扩展添加一个子程序,它将接受一个数字参数作为
输入并返回 1 如果数字是偶数或 0 如果数字是奇数。
将以下内容添加到 Mytest.xs 的末尾:
INT
is_even(输入)
整数输入
代码:
RETVAL = (输入 % 2 == 0);
输出:
返回值
""int input"" 行的开头不需要空格,但它是
有助于提高可读性。 在该行的末尾放置一个分号也是
选修的。 任何数量和种类的空格都可以放在 ""int"" 和
““输入””。
现在重新运行 make 来重建我们新的共享库。
现在执行与之前相同的步骤,从 Makefile.PL 文件生成一个 Makefile,然后
运行制作。
为了测试我们的扩展是否有效,我们现在需要查看文件 Mytest.t。 这
文件被设置为模仿 Perl 本身具有的相同类型的测试结构。 在里面
测试脚本,您执行许多测试以确认扩展的行为,
测试正确时打印“ok”,不正确时打印“not ok”。
使用测试::更多测试=> 4;
开始 { use_ok('Mytest') };
##########################
# 在下面插入您的测试代码,Test::More 模块在此处使用()ed
# 所以阅读它的手册页( perldoc Test::More )以获得帮助
# 测试脚本。
是(&Mytest::甚至(0), 1);
是(&Mytest::甚至(1), 0);
是(&Mytest::甚至(2), 1);
我们将通过命令“make test”调用测试脚本。 你应该看到
输出看起来像这样:
%进行测试
PERL_DL_NOLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e"
"test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/Mytest ....好的
所有测试成功。
文件=1,测试=4,0 挂钟秒(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”
确保您将测试您的新扩展,而不是已经存在的版本。
当 Perl 看到“use extension;”时,它会搜索与
“使用”后缀为 .pm 的扩展名。 如果找不到那个文件,Perl 会死
致命错误。 默认搜索路径包含在@INC 数组中。
在我们的例子中,Mytest.pm 告诉 perl 它需要导出器和动态加载器
扩展名。 然后设置@ISA 和@EXPORT 数组以及$VERSION 标量; 终于
告诉 perl 引导模块。 Perl 将调用它的动态加载程序(如果有
是一个)并加载共享库。
@ISA 和@EXPORT 这两个数组非常重要。 @ISA 数组包含一个列表
在其中搜索不存在的方法(或子例程)的其他包
当前包。 这通常只对面向对象的扩展很重要(我们
稍后会谈到),所以通常不需要修改。
@EXPORT 数组告诉 Perl 扩展的哪些变量和子程序应该是
放置在调用包的命名空间中。 因为你不知道用户是否有
已经使用了你的变量和子程序名称,仔细阅读是非常重要的
选择要导出的内容。 做 而不去 导出方法或变量名 by 默认 没有好的
原因。
作为一般规则,如果模块试图面向对象,则不要导出
任何事物。 如果只是函数和变量的集合,那么可以导出
通过另一个名为@EXPORT_OK 的数组。 此数组不会自动放置其
除非用户特别要求,否则子程序和变量名称进入命名空间
做到这一点。
有关详细信息,请参阅 perlmod。
$VERSION 变量用于确保 .pm 文件和共享库“在
彼此同步”。任何时候对 .pm 或 .xs 文件进行更改时,都应该
增加这个变量的值。
撰稿 非常好 测试 脚本
编写好的测试脚本的重要性怎么强调都不为过。 你应该密切
遵循 Perl 本身使用的“ok/not ok”风格,这样它就非常容易和
明确确定每个测试用例的结果。 当您发现并修复错误时,请执行
确保你为它添加了一个测试用例。
通过运行“make test”,您可以确保您的 Mytest.t 脚本运行并使用正确的
您的扩展程序的版本。 如果您有很多测试用例,请将您的测试文件保存在“t”中
目录并使用后缀“.t”。 当您运行“make test”时,所有这些测试文件
将被执行。
例 3
我们的第三个扩展将接受一个参数作为其输入,将该值四舍五入,并设置
论点 到四舍五入的值。
将以下内容添加到 Mytest.xs 的末尾:
无效
轮(参数)
双参数
代码:
如果(参数> 0.0){
arg = 楼层(arg + 0.5);
} else if (arg < 0.0) {
arg = ceil(arg - 0.5);
} {
参数 = 0.0;
}
输出:
ARG
编辑 Makefile.PL 文件,使相应的行如下所示:
'LIBS' => ['-lm'], # 例如,'-lm'
生成 Makefile 并运行 make。 将 Mytest.t 中的测试编号更改为“9”并添加
以下测试:
$i = -1.5; &Mytest::round($i); 是( $i, -2.0 );
$i = -1.1; &Mytest::round($i); 是( $i, -1.0 );
$i = 0.0; &Mytest::round($i); 是( $i, 0.0 );
$i = 0.5; &Mytest::round($i); 是( $i, 1.0 );
$i = 1.2; &Mytest::round($i); 是( $i, 1.0 );
运行 ""make test"" 现在应该打印出所有九个测试都正常。
请注意,在这些新测试用例中,传递给 round 的参数是一个标量变量。
您可能想知道是否可以舍入常量或文字。 看看会发生什么,
在 Mytest.t 中临时添加以下行:
&我的测试::圆(3);
运行 ""make test"" 并注意 Perl 因致命错误而终止。 Perl 不会让你改变
常量的值!
什么是 新 这里?
· 我们对 Makefile.PL 进行了一些更改。 在这种情况下,我们指定了一个额外的
要链接到扩展的共享库中的库,数学库 libm 在
这个案例。 稍后我们将讨论如何编写可以调用每个例程的 XSUB
图书馆。
· 函数的值不是作为函数的返回值传回,而是
通过更改传递给函数的变量的值。 你可能
当你看到round的返回值是“void”类型时,你已经猜到了。
输入 和 输出 参数
您指定将在您之后的行上传递到 XSUB 的参数
声明函数的返回值和名称。 每个输入参数行以
可选的空格,并且可能有一个可选的终止分号。
输出参数列表出现在函数的最后,就在
输出:指令。 RETVAL 的使用告诉 Perl 您希望将该值作为
XSUB 函数的返回值。 在示例 3 中,我们希望放置“返回值”
在我们传入的原始变量中,所以我们在
输出:部分。
- XSUBPP 教学计划
- xsubpp 程序将 .xs 文件中的 XS 代码转换为 C 代码,
将它放在一个后缀为 .c 的文件中。 创建的 C 代码大量使用了 C
Perl 中的函数。
- 类型图 文件
- xsubpp 程序使用规则将 Perl 的数据类型(标量、数组等)转换为
C 的数据类型(int、char 等)。 这些规则存储在类型映射文件中
($PERLLIB/ExtUtils/typemap)。 下面有一个简短的讨论,但所有的细节
详细信息可以在 perlxstypemap 中找到。 如果您有足够新的 perl 版本(5.16 和
up) 或升级的 XS 编译器("ExtUtils::ParseXS" 3.13_01 或更高版本),然后您可以
XS 中的内联类型映射,而不是编写单独的文件。 无论哪种方式,这个类型图
事情分为三部分:
第一部分将各种 C 数据类型映射到一个名称,这与
各种 Perl 类型。 第二部分包含 C 代码 xsubpp 用于处理输入
参数。 第三部分包含 C 代码 xsubpp 用于处理输出
参数。
让我们看一下为我们的扩展创建的 .c 文件的一部分。 文件名是
我的测试.c:
XS(XS_Mytest_round)
{
dXSARGS;
如果(项目!= 1)
Perl_croak(aTHX_ "用法:Mytest::round(arg)");
PERL_UNUSED_VAR(cv); /* -W */
{
双参数 = (双)SvNV(ST(0)); /* XXXXXX */
如果(参数> 0.0){
arg = 楼层(arg + 0.5);
} else if (arg < 0.0) {
arg = ceil(arg - 0.5);
} {
参数 = 0.0;
}
sv_setnv(ST(0), (double)arg); /* XXXXXX */
SvSETMAGIC(ST(0));
}
XSRETURN_EMPTY;
}
请注意用“XXXXX”注释的两行。 如果您检查类型图的第一部分
文件(或部分),你会看到双打是 T_DOUBLE 类型。 在输入部分
类型映射,一个 T_DOUBLE 参数被分配给变量 arg 通过调用
例行 SvNV 对某事,然后将其转换为双精度,然后分配给变量 arg。
类似地,在 OUTPUT 部分,一旦 arg 有了它的最终值,它就会被传递给
sv_setnv 函数被传递回调用子程序。 这两个函数是
解释在perlguts; 我们稍后会详细讨论那是什么“ST(0)" 表示在该部分
在参数堆栈上。
警告 关于 输出 参数
一般来说,编写修改其输入参数的扩展不是一个好主意,
如示例 3 中所示。相反,您可能应该在数组中返回多个值并让
调用者处理它们(我们将在后面的示例中执行此操作)。 然而,为了更好地
适应调用预先存在的 C 例程,这些例程经常修改它们的输入参数,
这种行为是可以容忍的。
例 4
在这个例子中,我们现在将开始编写将与预定义的 C 交互的 XSUB
图书馆。 首先我们自己搭建一个小库,然后让h2xs写
我们的 .pm 和 .xs 文件。
在与目录 Mytest 相同的级别上创建一个名为 Mytest2 的新目录。 在里面
Mytest2 目录,创建另一个名为 mylib 的目录,然后 cd 进入该目录。
在这里,我们将创建一些将生成测试库的文件。 这些将包括一个 C
源文件和头文件。 我们还将在此目录中创建一个 Makefile.PL。 然后
我们将确保在 Mytest2 级别运行 make 会自动运行这个
Makefile.PL 文件和生成的 Makefile。
在 mylib 目录中,创建一个文件 mylib.h,如下所示:
#define 测试值 4
extern double foo(int, long, const char*);
还要创建一个文件 mylib.c,如下所示:
#包括
#include "./mylib.h"
翻番
foo(int a, long b, const char *c)
{
返回 (a + b + atof(c) + TESTVAL);
}
最后创建一个 Makefile.PL 文件,如下所示:
使用 ExtUtils::MakeMaker;
$详细= 1;
写Makefile(
NAME => 'Mytest2::mylib',
跳过 => [qw(all static static_lib dynamic dynamic_lib)],
干净 => {'FILES' => 'libmylib$(LIB_EXT)'},
);
子 MY::top_targets {
'
全部 :: 静态
pure_all :: 静态
静态 :: libmylib$(LIB_EXT)
libmylib$(LIB_EXT): $(O_FILES)
$(AR) cr libmylib$(LIB_EXT) $(O_FILES)
$(RANLIB) libmylib$(LIB_EXT)
';
}
确保在以“$(AR)”开头的行上使用制表符而不是空格
“$(RANLIB)”。 如果使用空格,Make 将无法正常运行。 它也被
据报道,在 Win32 系统上,$(AR) 的“cr”参数是不必要的。
我们现在将创建主要的顶级 Mytest2 文件。 切换到上面的目录
Mytest2 并运行以下命令:
% h2xs -O -n Mytest2 ./Mytest2/mylib/mylib.h
这将打印出关于覆盖 Mytest2 的警告,但没关系。 我们的文件是
存储在 Mytest2/mylib 中,不会被改动。
h2xs 生成的普通 Makefile.PL 不知道 mylib 目录。 我们
需要告诉它有一个子目录,我们将在
它。 让我们将参数 MYEXTLIB 添加到 WriteMakefile 调用中,使其看起来像这样:
写Makefile(
'NAME' => 'Mytest2',
'VERSION_FROM' => 'Mytest2.pm', # 找到 $VERSION
'LIBS' => [''], # 例如,'-lm'
'DEFINE' => '', # 例如,'-DHAVE_SOMETHING'
'INC' => '', # 例如,'-I/usr/include/other'
'MYEXTLIB' => 'mylib/libmylib$(LIB_EXT)',
);
然后在最后添加一个子程序(它将覆盖预先存在的子程序)。
请记住使用制表符来缩进以“cd”开头的行!
子我::postamble {
'
$(MYEXTLIB): mylib/生成文件
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 文件的末尾:
翻番
富(a,b,c)
内部
长 b
常量字符 * c
输出:
返回值
现在我们还需要创建一个类型映射,因为默认的 Perl 当前不支持
“const char *”类型。 在上述之前的 XS 代码中包含一个新的 TYPEMAP 部分
功能:
类型图:<
常量字符 * T_PV
END
现在在顶层 Makefile.PL 上运行 perl。 请注意,它还在
mylib 目录。 运行 make 并观察它是否 cd 进入 mylib 目录并运行 make
在那里。
现在编辑 Mytest2.t 脚本并将测试数量更改为“4”,并添加以下内容
到脚本末尾的行:
is( &Mytest2::foo(1, 2, "Hello, world!"), 7 );
is( &Mytest2::foo(1, 2, "0.0"), 7 );
ok( abs(&Mytest2::foo(0, 0, "-3.4") - 0.6) <= 0.01 );
(处理浮点比较时,最好不要检查相等性,但
而是预期结果与实际结果之间的差异低于某个
数量(称为 epsilon),在这种情况下为 0.01)
运行""make test"",一切都会好起来的。 有一些关于缺少测试的警告
Mytest2::mylib 扩展名,但您可以忽略它们。
什么是 具有 发生 这里?
与之前的示例不同,我们现在在真实的包含文件上运行 h2xs。 这引起了一些
出现在 .pm 和 .xs 文件中的额外好处。
· 在 .xs 文件中,现在有一个带有绝对路径的 #include 指令
mylib.h 头文件。 我们将其更改为相对路径,以便我们可以移动
如果我们愿意,扩展目录。
· 现在有一些新的 C 代码被添加到 .xs 文件中。 的目的
“常量”例程是在头文件中生成#define'd的值
可由 Perl 脚本访问(通过调用“TESTVAL”或 &Mytest2::TESTVAL)。
还有一些 XS 代码允许调用“常量”例程。
· .pm 文件最初在@EXPORT 数组中导出名称“TESTVAL”。 这可以
导致名称冲突。 一个好的经验法则是,如果 #define 只会是
由 C 例程本身使用,而不是由用户使用,它们应该从
@EXPORT 数组。 或者,如果您不介意使用“完全限定名称”
一个变量,您可以将@EXPORT 数组中的大部分或所有项目移动到
@EXPORT_OK 数组。
· 如果我们的包含文件包含#include 指令,这些指令就不会
由 h2xs 处理。 目前没有很好的解决方案。
· 我们还向 Perl 介绍了我们在 mylib 子目录中构建的库。 那
只需要将“MYEXTLIB”变量添加到 WriteMakefile 调用中,并且
将 postamble 子例程替换为 cd 进入子目录并运行 make。
库的 Makefile.PL 稍微复杂一些,但也不过分。
我们再次替换了 postamble 子程序以插入我们自己的代码。 这段代码很简单
指定这里要创建的库是一个静态归档库(而不是
到可动态加载的库)并提供构建它的命令。
解剖学 of .xs 文件
“示例 4”的 .xs 文件包含一些新元素。 要理解的意思
这些元素,请注意读取的行
模块 = Mytest2 包 = Mytest2
此行之前的任何内容都是纯 C 代码,它描述了要包含哪些标头,以及
定义了一些方便的函数。 这部分没有翻译,除了
从跳过嵌入式 POD 文档(参见 perlpod),它进入
按原样生成输出 C 文件。
此行之后的任何内容都是对 XSUB 函数的描述。 这些描述是
被某某人翻译 xsubpp 使用 Perl 调用实现这些功能的 C 代码
约定,并使这些函数在 Perl 解释器中可见。
特别注意函数“常量”。 这个名字出现了两次
生成的 .xs 文件:一次在第一部分,作为静态 C 函数,然后另一次在
第二部分,当定义此静态 C 函数的 XSUB 接口时。
这对于 .xs 文件来说是非常典型的:通常 .xs 文件提供一个接口
现有的 C 函数。 然后这个 C 函数被定义在某个地方(或者在一个外部
库,或在 .xs 文件的第一部分),以及该函数的 Perl 接口(即
“Perl 胶水”)在 .xs 文件的第二部分中进行了描述。 “示例1”中的情况,
“示例 2”和“示例 3”,当所有工作都在“Perl 胶水”内完成时,是
有点例外而不是规则。
得到 这些因素包括原料奶的可用性以及达到必要粉末质量水平所需的工艺。 脂肪 输出 of XSUB
在“示例 4”中,.xs 文件的第二部分包含对 XSUB 的以下描述:
翻番
富(a,b,c)
内部
长 b
常量字符 * c
输出:
返回值
请注意,与“示例 1”、“示例 2”和“示例 3”相比,此描述不
不包含实际 码 在调用 Perl 函数期间做了什么 富()。 至
了解这里发生了什么,可以向这个 XSUB 添加一个 CODE 部分:
翻番
富(a,b,c)
内部
长 b
常量字符 * c
代码:
RETVAL = foo(a,b,c);
输出:
返回值
但是,这两个 XSUB 提供几乎相同的生成 C 代码: xsubpp 编译器是
足够聪明,可以从描述的前两行找出“代码:”部分
XSUB 的。 “输出:”部分怎么样? 事实上,这完全一样! 这
“输出:”部分也可以删除, as 远 as “代码:” 部分 or “PPCODE:” 部分
未指定: xsubpp 可以看到它需要生成一个函数调用部分,并且
也会自动生成 OUTPUT 部分。 因此可以将 XSUB 快捷方式变为:
翻番
富(a,b,c)
内部
长 b
常量字符 * c
我们可以用 XSUB 做同样的事情吗?
INT
is_even(输入)
整数输入
代码:
RETVAL = (输入 % 2 == 0);
输出:
返回值
的“示例 2”? 为此,需要定义一个 C 函数“int is_even(int input)”。
正如我们在“.xs 文件剖析”中看到的,这个定义的合适位置是在第一个
.xs 文件的一部分。 实际上是一个 C 函数
INT
is_even(整数参数)
{
返回 (arg % 2 == 0);
}
这可能是矫枉过正。 像“#define”这样简单的东西也可以:
#define is_even(arg) ((arg) % 2 == 0)
在 .xs 文件的第一部分有了这个之后,“Perl 胶水”部分就变得简单了
INT
is_even(输入)
整数输入
这种将胶水部分与主力部分分离的技术具有明显的
权衡:如果你想改变一个 Perl 界面,你需要改变你的两个地方
代码。 然而,它消除了很多混乱,并使主力部分独立于
Perl 调用约定的特性。 (事实上,Perl 中没有任何特定于
上面的描述,不同版本的 xsubpp 可能已将其翻译为 TCL
胶水或 Python 胶水。)
更多 关于 XSUB 参数
随着示例 4 的完成,我们现在可以轻松地模拟一些现实生活
接口可能不是世界上最干净的库。 我们现在继续
讨论传递给 xsubpp 编译器。
当您在 .xs 文件中为例程指定参数时,您实际上是在传递三个
列出的每个参数的信息。 第一部分是那个顺序
相对于其他人的论点(第一、第二等)。 第二个是论据的类型,
并且由参数的类型声明组成(例如,int、char* 等)。 第三
piece 是库函数调用中参数的调用约定。
Perl 通过引用将参数传递给函数,而 C 通过值传递参数; 到
实现一个 C 函数,该函数修改“参数”之一的数据,即实际参数
这个 C 函数的指针将是一个指向数据的指针。 因此两个带有声明的 C 函数
int string_length(char *s);
int upper_case_char(char *cp);
可能有完全不同的语义:第一个可能检查字符数组
由 s 指向,第二个可以立即取消引用“cp”并仅操作 *cp
(使用返回值作为成功指标)。 从 Perl 开始,人们会使用这些
以完全不同的方式发挥作用。
一个人将此信息传达给 xsubpp 通过用“&”替换参数前的“*”。 “&“ 方法
参数应该通过它的地址传递给库函数。 以上两
函数可以被 XSUB 化为
INT
字符串长度
字符 * s
INT
大写字符(cp)
字符&cp
例如,考虑:
INT
富(一,乙)
字符 &a
字符 * b
此函数的第一个 Perl 参数将被视为字符并分配给
变量 a,它的地址将被传递到函数 foo 中。 第二个 Perl
参数将被视为字符串指针并分配给变量 b。 这 折扣值 of
b 将被传递到函数 foo 中。 对函数 foo 的实际调用 xsubpp
生成看起来像这样:
foo(&a, b);
xsubpp 将同样解析以下函数参数列表:
字符 &a
字符&a
字符 & 一个
但是,为了便于理解,建议您在
变量名并远离变量类型),并在变量类型附近放置一个“*”,
但远离变量名(如上面对 foo 的调用)。 通过这样做,很容易
准确了解将传递给 C 函数的内容; 它将是任何东西
“最后一栏”。
你应该煞费苦心地尝试将函数传递给它想要的变量类型,
如果可能。 从长远来看,它会为您节省很多麻烦。
- 争论 堆
如果我们查看由除示例 1 之外的任何示例生成的任何 C 代码,您
会注意到许多对 ST(n) 的引用,其中 n 通常为 0。“ST”实际上是一个
指向参数堆栈中第 n 个参数的宏。 ST(0) 因此是第一个
堆栈上的参数,因此第一个参数传递给 XSUB, ST(1) 是
第二个参数,依此类推。
当您在 .xs 文件中列出 XSUB 的参数时,这会告诉您 xsubpp 哪个论点
对应于参数堆栈中的哪个(即列出的第一个是第一个
论证等)。 如果您没有按照与以下相同的顺序列出它们,您就会招来灾难
函数期待它们。
参数栈上的实际值是指向传入值的指针。
参数被列为 OUTPUT 值,它在堆栈中的对应值(即,
ST(0) 如果它是第一个参数)被更改。 您可以通过查看 C 来验证这一点
为示例 3 生成的代码。 圆形的() XSUB 例程包含的行
看起来像这样:
双参数 = (双)SvNV(ST(0));
/* 四舍五入变量 arg 的内容 */
sv_setnv(ST(0), (双)arg);
arg 变量最初是通过从以下位置获取值来设置的 ST(0),然后存储回
ST(0) 在例程结束时。
XSUB 也可以返回列表,而不仅仅是标量。 这必须由
操作堆栈值 ST(0) ST(1) 等,以一种微妙的不同方式。 见 perlxs
细节。
还允许 XSUB 避免将 Perl 函数参数自动转换为 C
函数参数。 有关详细信息,请参阅 perlxs。 有些人更喜欢手动转换
即使在可以自动转换的情况下检查 ST(i),认为这
使 XSUB 调用的逻辑更加清晰。 与“从 XSUB 中去除脂肪”相比
完全分离“Perl 胶水”和“主力”部分的类似权衡
XSUB。
虽然专家可能会争论这些习语,但 Perl 的新手可能更喜欢一种方式
尽可能少的 Perl-guts 特定的,这意味着自动转换和自动
调用生成,如“消除 XSUB 中的脂肪”。 这种方法有额外的
保护 XSUB 编写器免受 Perl API 未来更改的好处。
扩展 您的 延期
有时你可能想提供一些额外的方法或子程序来帮助制作
Perl 和您的扩展之间的接口更简单或更容易理解。 这些
例程应该存在于 .pm 文件中。 是否在加载时自动加载
扩展本身是加载还是仅在调用时加载取决于 .pm 文件中的位置
子程序定义被放置。 您还可以咨询 AutoLoader 以获取替代方法
存储和加载额外的子程序。
记录文件 您的 延期
绝对没有理由不记录您的延期。 文档所属
在 .pm 文件中。 这个文件将被提供给 pod2man,嵌入的文档将被
转换成manpage格式,然后放到blib目录下。 它将被复制到
安装扩展时 Perl 的联机帮助页目录。
您可以在 .pm 文件中散布文档和 Perl 代码。 事实上,如果你想
要使用方法自动加载,您必须这样做,正如 .pm 文件中的注释所解释的那样。
有关 pod 格式的更多信息,请参阅 perlpod。
安装 您的 延期
一旦您的扩展完成并通过所有测试,安装它就非常简单:
您只需运行“make install”。 您要么需要具有写入权限
安装 Perl 的目录,或要求您的系统管理员运行 make for
你。
或者,您可以通过放置来指定放置扩展文件的确切目录
在 make install 之后(或在 make 和
如果你有一个死脑筋的 make 版本,请安装)。 如果您是,这可能非常有用
构建最终将分发到多个系统的扩展。 你可以
然后只需将文件存档在目标目录中并将它们分发到您的
目标系统。
例 5
在这个例子中,我们将对参数堆栈做一些更多的工作。 前面的例子
都只返回了一个值。 我们现在将创建一个返回一个扩展
数组。
这个扩展是非常面向 Unix 的(struct statfs 和 statfs 系统调用)。 如果你
不在 Unix 系统上运行,您可以用任何其他功能代替 statfs
返回多个值,您可以硬编码要返回给调用者的值(尽管
这将更难测试错误情况),或者您可以不执行此示例。
如果您更改 XSUB,请务必修复测试用例以匹配更改。
返回 Mytest 目录,在 Mytest.xs 末尾添加以下代码:
无效
statfs(路径)
字符 * 路径
在里面:
int i;
结构 statfs buf;
代码:
i = statfs(path, &buf);
如果(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)));
} {
XPUSHs(sv_2mortal(newSVnv(errno)));
}
您还需要将以下代码添加到 .xs 文件的顶部,就在
包括“XSUB.h”:
#包括
还将以下代码段添加到 Mytest.t,同时将“9”测试增加到“11”:
@a = &Mytest::statfs("/blech");
ok( 标量(@a) == 1 && $a[0] == 2 );
@a = &Mytest::statfs("/");
是(标量(@a),7);
全新 事情 in Free Introduction 例如:
这个例子添加了很多新概念。 我们一次带他们一个。
· INIT: 指令包含将立即放置在参数之后的代码
堆栈被解码。 C 不允许在任意位置声明变量
在函数内部,所以这通常是声明所需局部变量的最佳方式
由 XSUB。 (或者,可以将整个“PPCODE:”部分放入大括号中,
并将这些声明放在最上面。)
· 此例程还返回不同数量的参数取决于成功或
对 statfs 的调用失败。 如果有错误,则返回错误号为
单元素数组。 如果调用成功,则一个 7 元素的数组为
回。 由于只有一个参数被传递给这个函数,我们需要在
堆栈以保存可能返回的 7 个值。
我们通过使用 PPCODE: 指令而不是 CODE: 指令来做到这一点。 这
告诉 xsubpp 我们将管理将放在
我们自己的参数堆栈。
· 当我们想把返回给调用者的值放到栈上时,我们使用
以“XPUSH”开头的一系列宏。 有五个不同的版本,用于
将整数、无符号整数、双精度数、字符串和 Perl 标量放在堆栈上。
在我们的示例中,我们将 Perl 标量放置到堆栈中。 (实际上这是唯一的
可用于返回多个值的宏。)
XPUSH* 宏将自动扩展返回堆栈以防止它被
超限。 您按照您希望它们看到的顺序将值压入堆栈
调用程序。
· 推入 XSUB 返回堆栈的值实际上是凡人的 SV。 他们
是致命的,这样一旦调用程序复制了这些值,SV 的
持有返回值的可以被释放。 如果他们不是凡人,那么他们
在 XSUB 例程返回后将继续存在,但将无法访问。
这是内存泄漏。
· 如果我们在成功分支中对性能感兴趣,而不是对代码紧凑性感兴趣
我们不会使用“XPUSHs”宏,而是使用“PUSHs”宏,并且会预先扩展堆栈
在推送返回值之前:
扩展(SP,7);
权衡是需要提前计算返回值的数量
(虽然过度扩展堆栈通常不会伤害任何东西,但内存
消耗)。
同样,在失败分支中,我们可以使用“PUSHs” 也完全不需要 扩展堆栈:
Perl 函数引用来到堆栈上的 XSUB,因此堆栈是 时刻 大
足以取一个返回值。
例 6
在这个例子中,我们将接受对数组的引用作为输入参数,并返回
对哈希数组的引用。 这将演示对复杂 Perl 的操作
来自 XSUB 的数据类型。
这个扩展有点做作。 它基于前面示例中的代码。
它多次调用 statfs 函数,接受对数组的引用
文件名作为输入,并返回对包含数据的哈希数组的引用
对于每个文件系统。
返回 Mytest 目录,在 Mytest.xs 末尾添加以下代码:
净值 *
multi_statfs(路径)
SV * 路径
在里面:
AV * 结果;
SSize_t numpaths = 0, n;
int i;
结构 statfs buf;
SvGETMAGIC(路径);
如果((!SvROK(路径))
|| (SvTYPE(SvRV(路径))!= SVt_PVAV)
|| ((numpaths = av_top_index((AV *)SvRV(paths))) < 0))
{
XSRETURN_UNDEF;
}
结果 = (AV *)sv_2mortal((SV *)newAV());
代码:
for (n = 0; n <= numpaths; n++) {
HV*rh;
斯特林 l;
char * fn = SvPV(*av_fetch((AV *)SvRV(路径), n, 0), l);
我 = statfs(fn, &buf);
如果(我!= 0){
av_push(结果,newSVnv(errno));
继续;
}
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(结果,newRV((SV *)rh));
}
RETVAL = newRV((SV *)结果);
输出:
返回值
并将以下代码添加到 Mytest.t,同时将“11”测试增加到“13”:
$results = Mytest::multi_statfs([ '/', '/blech' ]);
好的( ref $results->[0] );
好的( ! ref $results->[1] );
全新 事情 in Free Introduction 例如:
这里引入了许多新概念,描述如下:
· 此函数不使用类型映射。 相反,我们将其声明为接受一个 SV*
(标量)参数,并返回一个 SV* 值,我们负责填充这些
代码中的标量。 因为我们只返回一个值,所以我们不需要一个
"PPCODE:" 指令 - 相反,我们使用 "CODE:" 和 "OUTPUT:" 指令。
· 在处理引用时,谨慎处理它们很重要。 这
"INIT:" 块首先调用 SvGETMAGIC(paths),以防路径是绑定变量。 然后
它检查“SvROK”是否返回 true,这表明路径是一个有效的引用。
(简单地检查“SvROK”不会在绑定变量上触发 FETCH。)然后验证
路径引用的对象是一个数组,使用“SvRV”取消引用路径,
和“SvTYPE”来发现它的类型。 作为附加测试,它检查数组
路径引用的非空,使用“av_top_index”函数(返回-1
如果数组为空)。 XSRETURN_UNDEF 宏用于中止 XSUB 并返回
未满足所有三个条件时的未定义值。
· 我们在这个 XSUB 中操作了几个数组。 请注意,表示的是一个数组
内部通过 AV* 指针。 操作数组的函数和宏是
类似于 Perl 中的函数:“av_top_index”返回 AV* 中的最高索引,
很像 $#array; “av_fetch”从数组中获取单个标量值,给定它的
指数; "av_push" 自动将标量值推送到数组的末尾
根据需要扩展数组。
具体来说,我们从输入数组中一次读取一个路径名,并存储
以相同的顺序产生一个输出数组(结果)。 如果 statfs 失败,则元素
推入返回数组的是失败后 errno 的值。 如果 statfs
成功,但推入返回数组的值是对散列的引用
包含 statfs 结构中的一些信息。
与返回堆栈一样,有可能(并且在性能上有所提升)预先
在将数据推入之前扩展返回数组,因为我们知道有多少元素
我们会回来的:
av_extend(结果,numpaths);
· 我们在这个函数中只执行一个哈希运算,即存储一个新的
使用“hv_store”的键下的标量。 散列由 HV* 指针表示。 喜欢
数组,用于操作来自 XSUB 的哈希的函数反映了功能
可从 Perl 获得。 有关详细信息,请参阅 perlguts 和 perlapi。
· 要创建引用,我们使用“newRV”函数。 请注意,您可以投射 AV* 或
在这种情况下(以及许多其他情况)输入 SV* 的 HV*。 这使您可以参考
到具有相同功能的数组、散列和标量。 相反,“SvRV”函数
总是返回一个 SV*,如果它是,则可能需要转换为适当的类型
标量以外的其他东西(检查“SvTYPE”)。
· 此时,xsubpp 做的工作很少——Mytest.xs 之间的差异
和 Mytest.c 是最小的。
例 7 (未来 很快)
XPUSH 参数 AND 设置 RETVAL 并将返回值分配给数组
例 8 (未来 很快)
设置 $!
例 9 通过 open 档 至 服务对象
您会认为将文件传递给 XS 很困难,包含所有类型团和其他东西。
好吧,它不是。
假设出于某种奇怪的原因我们需要对标准 C 库进行包装
函数“fputs()”。 这就是我们所需要的:
#定义PERLIO_NOT_STDIO 0
#定义 PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#包括
INT
fputs(s, 流)
字符 * s
文件 * 流
真正的工作是在标准类型映射中完成的。
但是, 你失去了 perlio 层所做的所有美好的事情。 这将调用 stdio 函数
“fputs()”,它对它们一无所知。
标准类型映射提供了 PerlIO * 的三种变体:“InputStream”(T_IN)、
"InOutStream" (T_INOUT) 和 "OutputStream" (T_OUT)。 一个裸的“PerlIO *”被认为是一个
T_INOUT。 如果它在您的代码中很重要(请参阅下面的原因)#define 或 typedef 之一
特定名称并将其用作 XS 文件中的参数或结果类型。
perl 5.7 之前的标准类型映射不包含 PerlIO *,但它具有三个
流变体。 除非您提供,否则直接使用 PerlIO * 不向后兼容
你自己的类型图。
对于即将到来的流 在 perl 的主要区别在于“OutputStream”将获得
输出 PerlIO * - 这可能会对套接字产生影响。 就像在我们的例子中...
对于正在处理的流 至 perl 创建了一个新的文件句柄(即对新文件的引用
glob) 并与提供的 PerlIO * 相关联。 如果 PerlIO 的读/写状态 *
不正确,那么您可能会在使用文件句柄时收到错误或警告。 所以
如果你打开 PerlIO * 为“w”它应该是一个“OutputStream”,如果打开为“r”它
应该是“InputStream”。
现在,假设您想在 XS 中使用 perlio 层。 我们将使用 perlio
以“PerlIO_puts()”函数为例。
在 XS 文件的 C 部分(在第一个 MODULE 行之上)你有
#define 输出流 PerlIO *
or
typedef PerlIO * 输出流;
这是 XS 代码:
INT
perlioputs(s, 流)
字符 * s
输出流
代码:
RETVAL = PerlIO_puts(流, s);
输出:
返回值
我们必须使用“CODE”部分,因为“PerlIO_puts()”的参数颠倒了
与“fputs()”相比,我们希望参数保持不变。
为了彻底探索这一点,我们想在 PerlIO * 上使用 stdio "fputs()"。 这
意味着我们必须向 perlio 系统询问 stdio "FILE *":
INT
perliofputs(s, 流)
字符 * s
输出流
先决条件:
文件 *fp = PerlIO_findFILE(stream);
代码:
如果(fp !=(文件*)0){
RETVAL = fputs(s, fp);
} {
返回值 = -1;
}
输出:
返回值
注意:“PerlIO_findFILE()”将在层中搜索 stdio 层。 如果找不到,
它会调用“PerlIO_exportFILE()”来生成一个新的stdio“FILE”。 请只拨打
"PerlIO_exportFILE()" 如果你想要一个 新 “文件”。 它会在每次调用时生成一个并推送
一个新的 stdio 层。 所以不要在同一个文件上重复调用它。 “PerlIO_findFILE()”将
一旦“PerlIO_exportFILE()”生成stdio层,就检索它。
这仅适用于 perlio 系统。 对于 5.7 之前的版本,“PerlIO_exportFILE()”是
相当于“PerlIO_findFILE()”。
故障排除 这些 例子
如本文档顶部所述,如果您在使用这些示例时遇到问题
扩展,您可能会看到这些是否对您有帮助。
· 在伽马版本之前的 5.002 版本中,示例 1 中的测试脚本将不会
功能正常。 您需要将“use lib”行更改为:
使用 lib './blib';
· 在 5.002b5.002h 之前的 1 版本中,test.pl 文件不会自动
由 h2xs 创建。 这意味着您不能说“make test”来运行测试脚本。
您需要在“use extension”语句之前添加以下行:
使用 lib './blib';
· 在 5.000 和 5.001 版本中,您需要使用
以下行:
开始 { unshift(@INC, "./blib") }
· 本文档假设名为“perl”的可执行文件是 Perl 版本 5。一些
系统可能已将 Perl 版本 5 安装为“perl5”。
参见 还
有关更多信息,请参阅 perlguts、perlapi、perlxs、perlmod 和 perlpod。
作者
杰夫冈本[电子邮件保护]>
由 Dean Roehrich、Ilya Zakharevich、Andreas Koenig 和 Tim Bunce 审阅和协助。
PerlIO 材料由 Lupe Christoph 提供,并由 Nick Ing- 进行了一些澄清
西蒙斯。
从 Perl 2.x 开始,由 Renee Baecker 对 h5.8xs 所做的更改
(姓) 更改
2012-01-20
使用 onworks.net 服务在线使用 perlxstut