编写自己的XS函数
在第一部分中,我们学习了XS的基本组件,并将两个C函数集成到Perl中。本章将向您展示如何定义接受多个参数的xsubs,并定义自己的逻辑,而不是使用XS作为访问C库的外部函数接口。
要执行本文中的代码,您需要第一部分中的文件。
模块代码
与之前一样,我们将定义模块代码以加载我们的XS。这需要以下内容
package XS::Tutorial::Two;
require XSLoader;
XSLoader::load();
1;
应该保存为lib/XS/Tutorial/Two.pm
。
XS代码
XS文件的开头将与上一章类似
#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 "stdint.h" // portable integer types
MODULE = XS::Tutorial::Two PACKAGE = XS::Tutorial::Two
PROTOTYPES: ENABLE
请记住,在PROTOTYPES
行之后追加任何XS代码。应该保存为lib/XS/Tutorial/Two.xs
。
添加数字
这是一个简单的声明,声明了一个添加两个整数的xsub
int
add_ints (addend1, addend2)
int addend1
int addend2
CODE:
RETVAL = addend1 + addend2;
OUTPUT:
RETVAL
这声明了一个名为add_ints
的xsub,它接受两个整数,返回类型为int
。注意函数定义的K&R风格。这也可以写成
add_ints (int addend1, int addend2)
但在现实中很少这样做。我不知道这是否是货船崇拜的事情,或者是否有我未知的xsub编译器的边缘案例。为了安全起见,我会继续像其他人一样做(崇拜持续存在!)。
而之前在之前,我们本质上是在将C函数如srand
映射到Perl,而在这里,我们在声明自己的逻辑:add_ints
没有从任何地方导入,我们将其声明为一个新函数。
由于add_ints
是一个新函数,我们需要定义它的逻辑,这就是CODE
部分的作用所在。在这里,我们可以编写C代码,它构成了函数的主体。在这个例子中,我将两个子程序参数相加,并将结果赋给RETVAL
。
RETVAL(“返回值”)是一个由xsub处理器(xsubpp)声明的特殊变量。输出部分接受xsub的返回变量,将其放置在堆栈上,以便调用代码可以接收它。
添加超过两个数字
添加两个数字很好,但是列表是Perl的语言。让我们更新add_ints
xsub以接受n个值
int32_t
add_ints (...)
CODE:
uint32_t i;
for (i = 0; i < items; i++) {
if (!SvOK(ST(i)) || !SvIOK(ST(i)))
croak("requires a list of integers");
RETVAL += SvIVX(ST(i));
}
OUTPUT:
RETVAL
首先,请注意我已更新了返回值。使用C中的int
的一个问题是它可能在不同的机器体系结构中具有不同的大小。int32_t
来自stdint.h
库,并保证为32位有符号整数。
我用...
替换了函数参数,这表示该函数接受可变数量的参数,就像在C中一样。在CODE
部分,我声明了一个名为i
的uint32_t
整数(uint32_t
是32位无符号整数)。
循环使用特殊变量items
(传递给函数的参数数量)来迭代参数。如果测试失败,则代码调用croak
以抛出一个致命异常。
否则,将整数值从标量中提取出来(SvIVX
)并添加到RETVAL
中。如果你觉得这些C宏看起来很奇怪,不用担心,它们很奇怪!它们是Perl C API的一部分,在perlapi中有文档说明。
边缘情况
现在可能是一个为这个函数编写一些测试的好时机,下面是一个开始
use Test::More;
BEGIN { use_ok 'XS::Tutorial::Two' }
cmp_ok XS::Tutorial::Two::add_ints(7,3), '==', 10;
cmp_ok XS::Tutorial::Two::add_ints(1500, 21000, -1000), '==', 21500;
done_testing;
我将该文件保存为t/two.t
,并通过使用make
构建发行版来运行它
perl Makefile.PL && make && make test
你知道如果用无参数调用add_ints
,返回值会是什么吗?可能是undef
,因为没有参数时,for循环将不会进行任何迭代。这里有一个测试这个条件的示例
ok !defined XS::Tutorial::Two::add_ints(), 'empty list returns undef';
使用以下命令重新构建和运行测试
make clean && perl Makefile.PL && make && make test
这个测试失败了,因为返回值是零!这是C的一个怪癖:未初始化的整数可以是零。让我们修复xsub,使其在未接收任何参数时返回undef
SV *
add_ints (...)
PPCODE:
uint32_t i;
int32_t total = 0;
if (items > 0) {
for (i = 0; i < items; i++) {
if (!SvOK(ST(i)) || !SvIOK(ST(i)))
croak("requires a list of integers");
total += SvIVX(ST(i));
}
PUSHs(sv_2mortal(newSViv(total)));
}
else {
PUSHs(sv_newmortal());
}
哇,有很多改动!首先,我将返回类型从int32_t
更改为SV *
。原因将在下面变得清晰。现在,CODE
部分被称为PPCODE
,这告诉xsubpp我们将自己管理xsub的返回值,因此去掉了OUTPUT
部分。
我声明了一个新变量total
来捕获随着参数的添加而运行的累计总和。如果我们至少接收到了一个参数,total就会复制到一个新的标量整数值(newSViv
),它的引用计数被修正(sv_2mortal
),然后被推入栈指针(PUSHs
)。
否则,使用sv_newmortal
声明一个新的undef
标量,并将其推入栈指针。所以在这两种情况下,我们都在返回一个SV
。由于我们返回的是Perl类型而不是C类型(int32_t
),因此不需要xsubpp将我们的返回值强制转换为Perl标量,我们已经做了。
总结
本教程涵盖了一些编写xsubs的关键技能:如何接受多个参数,如何编写自己的逻辑,以及如何管理栈指针。如果你掌握了所有这些,并且已经理解了第一部分的内容,你就有了编写自己的XS代码的基础。
参考文献
- 此文档和代码位于CPAN(XS::Tutorial)
- perlxs定义了xsubpp所识别的关键字
- perlapi列出了用于与Perl数据结构(和解释器)交互的C宏
- stdint.h C库提供了一系列可移植的整数类型
标签
反馈
这篇文章有什么问题吗?请在GitHub上打开一个问题或pull request来帮助我们。