XS入门指南
可扩展子例程(XS)是用C语言编写的子例程,可以从Perl代码中调用。您可能希望使用XS的两个常见原因:您想使用与Perl一起使用的C库,或者您想通过在C中而不是在Perl中处理来加快子例程的速度。
本教程将带您了解使用基本XS示例启动所需的所有组件。这里会有很多新术语和概念
如果您想编写XS,就必须学习它。学习XS非常困难
Steven W. McDougall
如果一开始事情没有顺利点击,请不要气馁:我保证您学习XS会非常有回报:您将能够编写闪电般快速的课程;更好地理解Perl的内部工作原理,并能够集成您选择的任何C库,并从Perl中使用它。
组件
编写xsub需要一些基本组件。第一个是提供任何XS函数命名空间的Perl模块。这就是所需的一切
package XS::Tutorial::One;
require XSLoader;
XSLoader::load();
1;
该文件应保存为lib/XS/Tutorial/One.pm
。默认情况下,XSLoader::load
在从其调用的包名中搜索匹配的XS代码。
让我们也创建一个主分布模块
package XS::Tutorial;
BEGIN { our $VERSION = 0.01 }
1;
=encoding utf8
=head1 NAME
XS::Tutorial - documentation with examples for learning Perl XS
=cut
该文件应保存为lib/XS/Tutorial.pm
。
接下来,我们需要一个.xs文件,该文件定义了将被XS::Tutorial::One
加载的xsubs
#define PERL_NO_GET_CONTEXT // we'll define thread context if necessary (faster)
#include "EXTERN.h" // globals/constant import locations
#include "perl.h" // Perl symbols, structures and constants definition
#include "XSUB.h" // xsubpp functions and macros
#include <stdlib.h> // rand()
// additional c code goes here
MODULE = XS::Tutorial::One PACKAGE = XS::Tutorial::One
PROTOTYPES: ENABLE
# XS code goes here
# XS comments begin with " #" to avoid them being interpreted as pre-processor
# directives
unsigned int
rand()
该文件应保存为lib/XS/Tutorial/One.xs
。文件上半部分是纯C代码。以MODULE = XS::Tutorial::One
开始的行指示XS代码的开始。此部分将由xsubpp
解析和编译为C代码。
MODULE
和PACKAGE
指令定义了将加载我们定义的任何xsubs的Perl模块和包。PROTOTYPES: ENABLE
行告诉xsubpp
为任何我们创建的xsubs定义子程序原型。这通常是您想要的:原型可以帮助Perl捕获编译时错误。
文件的最后两行是一个xsub
unsigned int
rand()
第一行定义了返回类型。第二行做两件事:它指示要调用的C函数的名称,并定义了xsub的签名。
在这种情况下,我们调用rand
而不接受任何参数。这不是Perl的内置rand函数,这个rand来自stdlib.h。
最后,我们需要一个Makefile.PL
脚本——因为XS代码是编译的,我们需要一个在我们可以使用它之前构建它的工具
use 5.008005;
use ExtUtils::MakeMaker 7.12; # for XSMULTI option
WriteMakefile(
NAME => 'XS::Tutorial',
VERSION_FROM => 'lib/XS/Tutorial.pm',
PREREQ_PM => { 'ExtUtils::MakeMaker' => '7.12' },
ABSTRACT_FROM => 'lib/XS/Tutorial.pm',
AUTHOR => 'David Farrell',
CCFLAGS => '-Wall -std=c99',
OPTIMIZE => '-O3',
LICENSE => 'freebsd',
XSMULTI => 1,
);
ExtUtils::MakeMaker 文档解释了这些选项。
但让我们谈谈XSMULTI
。这是一个相对较新的特性,它允许您为模块拥有单独的.xs文件。默认情况下,EUMM假定xs文件与分发名称匹配。在这种情况下,这意味着有一个单一的Tutorial.xs文件,其中包含多个xs MODULE
和PACKAGE
声明。通过使用XSMULTI
,我们可以有多个XS文件,每个文件对应分发中的一个模块。
实际上它搜索编译的C代码,但效果是一样的。
构建
现在我们应该有四个文件
lib/XS/Tutorial.pm
lib/XS/Tutorial/One.pm
lib/XS/Tutorial/One.xs
Makefile.PL
以下命令将构建分布
$ perl Makefile.PL
$ make
一篇关于理解xsubpp生成的C代码的简要文章
make
创建了一大批文件,但看看lib/XS/Tutorial/One.c
。这是xsubpp
的输出。如果您仔细观察,您可以在其中找到来自lib/XS/Tutorial/One.xs
的C代码行。但看看我们的rand
xsub发生了什么
XS_EUPXS(XS_XS__Tutorial__One_rand); /* prototype to pass -Wmissing-prototypes */
XS_EUPXS(XS_XS__Tutorial__One_rand)
{
dVAR; dXSARGS;
if (items != 0)
croak_xs_usage(cv, "");
{
unsigned int RETVAL;
dXSTARG;
RETVAL = rand();
XSprePUSH; PUSHu((UV)RETVAL);
}
XSRETURN(1);
}
xsubpp
已经用一些相当丑陋的 C 宏替换了我们的 XS 代码!这些宏是 Perl 解释器 C API 的一部分。许多宏在 perlapi 中有文档说明,通常在 Perl 源代码中的 XSUB.h
或 perl.h
中定义。
那么这些宏在做什么呢?从高层次来看,dVAR
和 dXSARGS
设置全局指针栈和一些局部变量。 items
是传递给 xsub 的参数的数量。由于 rand
是一个 void 函数,如果这个值不为零,它将崩溃。 croak_xs_usage
接受一个 coderef 和一个 args 字符串。在这个上下文中,cv
是 xsub,没有参数,所以字符串是空的。
接下来,代码声明了 RETVAL
,这是 xsub 的返回值。 dXTARG
初始化 TARG
指针。接下来调用 rand()
并将返回值赋给 RETVAL
。 XSprePUSH
将栈指针回退一个位置,PUSHu
将 RETVAL
复制到 TARG
并将其推入全局栈指针。 XSRETURN
从 xsub 返回,表示它向栈中添加了多少个参数,在这个例子中是 1。
编写 XS 时,通常不需要研究生成的 C 代码,但了解这个过程是有帮助的。
安装
现在代码已编译,使用以下命令安装:
$ make install
如果你使用的是系统 Perl,你可能需要使用 sudo
来安装。现在我们可以使用单行命令测试该模块
$ perl -MXS::Tutorial::One -E 'say XS::Tutorial::One::rand()'
1804289383
它工作了!但你试过运行两次吗?
$ perl -MXS::Tutorial::One -E 'say XS::Tutorial::One::rand()'
1804289383
每次我们都得到相同的伪随机序列……我们需要调用 srand
来生成序列。该函数已经由 stdlib.h
提供,所以我们只需要将以下文本追加到 lib/XS/Tutorial/One.xs
void
srand(seed)
unsigned int seed
这个 xsub 与第一个不同:它的返回类型是 void
,这意味着它不返回任何内容。它还包括一个名为 seed
的参数,最后一行将其定义为无符号整数。
重新构建并安装发行版
$ make && make install
现在我们可以在调用 rand
之前调用 srand
来生成伪随机序列。
$ perl -MXS::Tutorial::One -E 'XS::Tutorial::One::srand(777);\
say XS::Tutorial::One::rand()'
947371799
我们使用了幸运的(777)种子数字,并且 rand
输出了一个不同的数字,太好了!
我们打败了 Perl 吗?
正如你所知,xsubs 通常比纯 Perl 代码快。我们为 rand
和 srand
构建了两个 xsubs,它们也可以作为 Perl 的内置函数使用。你认为 xsubs 更快吗?这是我机器上的基准测试
Rate xs_rand bi_rand
xs_rand 15691577/s -- -64%
bi_rand 43095739/s 175% --
哦,不!尽管我们的 rand
xsub 直接调用 C 的 stdlib
函数,但它的速度比 Perl 的内置 rand
慢得多。这并不是因为 xsubs 慢,而是因为 Perl 的内置函数非常快。调用 xsubs 有一些开销,这是内置函数不需要支付的。
测试
我们可以编写单元测试来代替运行单行命令来检查我们的代码是否正常工作。这是一个基本脚本
#!/usr/bin/perl
use Test::More;
BEGIN { use_ok 'XS::Tutorial::One' }
ok my $rand = XS::Tutorial::One::rand(), 'rand()';
like $rand, qr/^\d+$/, 'rand() returns a number';
ok !defined XS::Tutorial::One::srand(5), 'srand()';
ok $rand ne XS::Tutorial::One::rand(), 'after srand, rand returns different number';
done_testing;
将此文件保存为 t/one.t
。假设你已经构建并安装了发行版,你可以这样做
$ perl t/one.t
ok 1 - use XS::Tutorial::One;
ok 2 - rand()
ok 3 - rand() returns a number
ok 4 - srand()
ok 5 - after srand, rand returns different number
1..5
现在在将来构建发行版时,你应该这样做
$ perl Makefile.PL && make && make test
这将重新构建并测试发行版。由于 XS 代码是编译的,编写测试和使用单行命令,你可以快速在编码和测试之间切换。
不要忘记将 Test::More 添加到 Makefile.PL
中的 PREREQ_PM
条目。当你没有特定最低版本时,只需使用 0
PREREQ_PM => { 'Test::More' => 0, 'ExtUtils::MakeMaker' => '7.12' },
清理
构建发行版会生成大量的临时文件。ExtUtils::MakeMaker 提供了一个 realclean 例程
$ make realclean
这将删除所有构建文件并将工作目录重置为正常。
参考
- 这篇文章和代码在 CPAN 上作为 XS::Tutorial::One 提供
- 由 Steven W. McDougall 编写的
XS Mechanics
是我最喜欢的第二本书 :) XS 教程 - perlxs 定义了由 xsubpp 识别的关键词
- perlapi : 用于与 Perl 数据结构(和解释器)交互的 C 宏
- 《stdlib.h》手册页定义了 C 标准库函数和类型
- 编写 Makefile.PL 文件时:ExtUtils::MakeMaker 文档 非常宝贵
- Perl 的内置 rand 和 srand 函数
本文最初发布在 PerlTricks.com 上。
标签
反馈
这篇文章有什么问题吗?请在 GitHub 上打开一个问题或拉取请求来帮助我们。