XS 语言是一种用来在 Perl 和需要在 Perl 内使用的 C 代码(或者 C 库)之间创建扩展的接口描述文件格式。XS 接口为 C 库链接创建了一个静态链接到 Perl 或者能被 Perl 动态导入的新库。本文的主要目的是介绍如何在 Unix 环境下编写 XS 接口并成功通过编译,从而实现 Perl 对 C 的扩展,提高代码重用率。
引言
本文面向 Perl 和 C 的开发人员,旨在通过对 Perl 与 C 之间的 XS 扩展接口的介绍,让读者了解到通过 Perl 调用 C 函数的一种方法。为了更好的理解本文,读者需要具备一定的 Perl 和 C 编程经验,并对 Unix 环境下库文件的编译过程和 Makefile 语法有所了解。
什么是 XS 语言
XS 是一个用来在 Perl 和需要在 Perl 内使用的 C 代码(或者 C 库)之间创建扩展的接口描述文件格式。XS 接口为 C 库链接创建了一个静态链接到 Perl 或者能被 Perl 动态导入的新库。XS 接口描述是用 XS 语言写的,是 Perl 扩展接口的核心部分。
当 Perl 代码调用 C 函数时,XS 从 Perl 堆栈中获取参数,将这些参数转化为 C 函数所要求的正确格式,调用相应的 C 函数,并将返回值转化为 Perl 的参数格式,压入 Perl 堆栈供程序读取,或者直接修改 Perl 所提供的变量值。
由于 Perl 提供了比 C 更为自由的变量定义和调用规则,在参数转换过程中,XS 还必须验证参数合法性,抛出异常(或返回 undef 或空值列表),根据参数的数目和类型的不同调用不同的 C 函数,提供面向对象接口等等。
XS 语言的编译器叫做 xsubpp,它为接口创建必要的数据结构和调用关系,xsubpp 根据 typemaps 来确定如何在 Perl 与 C 之间转换函数参数和返回值。标准 Perl 库自带的 typemap 定义了大部分常用的 C 变量类型,但一些特殊的数据结构和类型需要开发人员通过自定义的 typemap 来实现。
.XS 文件
XSUB 解析
XS 接口文件以 .xs 为后缀名,里面定义了 Perl 与 C 之间的接口函数。XSUB 是 XS 接口的基本结构单元,通过 xsubpp 编译后,每个 XSUB 都为相应的 C 函数提供了 Perl 与 C 之间的调用接口。
清单 1 . 一个简单的 .xs 文件
#include "EXTERN.h" #include "perl.h" #include "XSUB.h" MODULE = TEST PACKAGE = TEST void hello() CODE: printf("Hello, world!\n"); |
其中前三个 #include 声明:EXTERN.h,perl.h 和 XSUB.h 应该始终出现在每个 XS 文件的开头。其后是其他的头文件 #include 声明。
MODULE= 定义了该 XS 文件所属的 Perl 模块(.pm),同一个 .xs 文件中所有的 MODULE= 都应该保持一致。每个 MODULE= 之后则是对应的 XSUB 定义,直到文件结束或者下一个 MODULE= 语句。
PACKAGE= 定义了该函数所在的 Package,当同一个 .xs 文件需要被划分为多个 Package 时 PACKAGE= 则需要被显式指定。PACKAGE= 应该和 MODULE= 放在一起并紧随其后。
一个最简单的 XSUB 由三部分(section)组成:返回值定义;XSUB 函数名和参数名;以及参数类型。复杂的 XSUB 还包括其他部分,如 CODE:(代码段),IUPUT:(输入值),OUTPUT:(输出值)等等。其中返回值和函数名必须位于每个 XSUB 的开头,分行书写并左对齐顶格,其余部分格式则没有严格要求。
清单 2 .XSUB 格式
double sin(x) double double double x sin(x) sin(x) double x double x 错误 错误 正确 |
Perl 变量堆栈和参数
Perl 变量堆栈(argument stack)用于存放发送给 XSUB 的参数值及其返回值。XSUB 可以通过宏 ST(x) 访问该堆栈,其中 ST(0) 为该堆栈的起始地址。
清单 3 . 操作 ST(x)
membername = (char*)SvPV(ST(2), na); ST(0) = newSVpv("Hello World", 0); |
而宏 SP 代表当前 Perl 堆栈指针,当程序从 XSUB 返回时,处理堆栈上的数据。
清单 4 . 操作 SP
EXTEND(SP, 2); |
变量 RETVAL 是一个特殊的 C 变量,它的类型对应于 C 函数的返回值类型。xsubpp 编译器会自动为每个 non-void 返回值类型的函数声明该变量,用于存放被调用的 C 函数的返回值。通常情况下,RETVAL 会作为对应 XSUB 的返回值存放到 Perl 变量堆栈的 ST(0)。
清单 5 .RETVAL 变量
int is_even(input) int input CODE: RETVAL = (input % 2 == 0); OUTPUT: RETVAL |
注意: 当 XSUB 返回值为 void 时,编译器不会为该函数声明 RETVAL 变量;当存在 PPCODE: 关键字时,不能对 RETVAL 变量进行操作,而应该直接操作对应的 Perl 变量堆栈 ST(x)。
XSUB 的一些关键字
OUTPUT: 关键字指定了当 XSUB 结束时应该返回给调用方 Perl 的参数值。在没有 CODE: 段和 PPCODE: 段时,RETVAL 变量会被自动指定为 OUTPUT 变量,否则需要显式指定 OUTPUT 变量。该关键字也能用于指定函数输入参数为 OUTPUT 变量,这在当函数体改变了某个输入参数值并希望将新值返回给调用方 Perl 的情况下十分有用。
清单 6 .C 函数原型
bool_t gettime(const char *host, time_t *timep); |
其函数将指定 host 上的当前系统时间存入指针 timp 对应的地址中,同时返回布尔型状态值。
清单 7 . 对应的 XSUB 函数定义
bool_t gettime(host,timep) char *host time_t &timep OUTPUT: Timep |
CODE: 关键字用于对相应的 C 函数做额外的操作处理,此时 RETVAL 变量仍被声明,但并不作为返回值,除非被 OUTPUT: 关键字显式指定。仍以上面的 C 函数 gettime (host,timep) 为例,如果在 Perl 代码中存在以下的调用:
Perl 代码调用
$status = gettime( "localhost", $timep );
其中 $status 和 $timep 都用于接收 C 函数的返回值,则需要对相应的 C 函数做额外的处理。
清单 9. 对应的 XSUB 函数定义
bool_t gettime(host,timep) char *host time_t timep CODE: RETVAL = gettime( host, &timep ); OUTPUT: timep RETVAL |
通过一元运算符 &,当 xsubpp 编译器调用 C 函数 gettime() 时,传给 C 函数的参数 &timep 是指向 time_t 的指针 time_t *,同时将得到的时间值存储在 timep 中返回给 Perl。
PPCODE: 关键字是对 CODE: 的补充,用于直接操作 Perl 变量堆栈。这在当 XSUB 存在多个返回值时十分有用。此时必须在 PPCODE: 中显式的将返回值列表压入堆栈顶。需要注意的是,在同一个 XSUB 中 CODE: 和 PPCODE: 不能同时出现。
PPCODE: 通常直接操作 SP,通过 PUSH*() 宏将返回值列表压入 Perl 堆栈,而不是将其作为返回值传送给 Perl,因此其函数返回值类型一般为 void,用于告诉 xsubpp 编译器不需要声明和创建 VETVAL 变量。
清单 10.PPCODE: 关键字
void gettime(host) char *host PREINIT: time_t timep; bool_t status; PPCODE: status = gettime( host, &timep ); EXTEND(SP, 2); PUSHs(sv_2mortal(newSViv(status))); PUSHs(sv_2mortal(newSViv(timep))); |
通过 PUSH() 宏将 C 函数 gettime() 的返回值 status 和 timep 依次压入 Perl 堆栈,有了上面的 XSUB 定义,则在 Perl 代码中可以这样调用上面的 C 函数 gettime()。
Perl 代码调用
($status, $timep) = gettime("localhost");
时间:2009-08-07 09:15
来源:developerWorks 中国
作者:唐 明
原文链接
好文,顶一下
(2)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------
把开源带在你的身边-精美linux小纪念品
|