聚合国内IT技术精华文章,分享IT技术精华,帮助IT从业人士成长

XS 初体验

2014-05-20 23:50 浏览: 4617 次 我要评论(0 条) 字号:

今天翻 ganglia 源代码发现两年前加上了 perl_module支持,不过跟 python_module 相比,descriptors 里的 call_back 不是真的写作回调函数而是写作和实际函数同名的字符串,这点让我觉得很别扭和奇怪,于是想到去看看 gmond 里内嵌的 perl 解释程序是怎么做这步的。顺带就第一次动手写了一点 XS 代码,这里一并发上来,留作存档。

示例代码框架源自上周 Dancer 作者 SawyerX 发布的 XS-Fun 项目。所以这里如何使用 h2xs 命令创建 XS 模块文件就不讲解了。

主要分作五个小示例,由最简单到很简单依次如下:

返回一个字符串

编辑 XSFun.xs 内容如下:

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
/* C functions */
MODULE = XSFunPACKAGE = XSFun
# XS code
SV *
runcb()
CODE:
STRLEN len;
const char* str = "testsub";
SV* val = newSVpv(str, len);
RETVAL = val;
OUTPUT: RETVAL

这个其实就相当于 sub runcb { return "testsub" }

返回一个哈希的指定键的值

因为起因是 gmond 里的代码,所以这里就开始主要研究如何解析 descriptor 哈希的键值对了。下面是 runcb() 的代码片段:

SV *
runcb(SV *sref)
CODE:
HV* plhash = (HV*)(SvRV(sref));
const char* key = "call_back";
SV* val = *hv_fetch(plhash, key, strlen(key), 0);
RETVAL = val;
OUTPUT: RETVAL

这里两个要点,一个是传递进来的哈希引用如何解引用(perl程序里任何时候都不应该直接传递哈希或者数组,而应该传递引用,所以这里直接就研究这步了);一个是 hv_fetch 的返回值是 SV** 而不是 SV*

发现 XS 语法里比较有意思的一点,就是变量类型转换的时候,大小写的意义。像 SvRV 就是从 SV 变成 RV,而 SViv 就是从 IV 变成 SV,基本是谁大写就是转变成谁。

调用 Perl 函数并获取其返回值

刚才说到了 descriptor 里的 “call_back” 键的值其实是函数名,所以这一步就试图运行这个 Perl 函数。

SV *
runcb(SV *sref)
CODE:
HV* plhash = (HV*)(SvRV(sref));
const char* key = "call_back";
SV* cb = *hv_fetch(plhash, key, strlen(key), 0);
int count = call_sv(cb, G_SCALAR);
RETVAL = POPs;
OUTPUT: RETVAL

这里的要点:

  • call_sv 函数(传递的是函数引用)。在 gmond 源码里用的是 call_pv 函数(传递的是函数名字符串)。可见原来在代码层这里写起来几乎是一样的,看来定义成写字符串纯粹是作者个人偏好了。

  • 这里要给被调用的函数设定上下文,我这里要求返回字符串,就是 G_SCALAR,还有 G_VOID 等等,详见 perlcall文档

  • POPs 操作。call_sv 函数返回值只代表调用的函数的返回值个数,调用函数的返回值本身,需要另外逐一获取,这个获取就是通过 POPs( 这个是取SV,类似的还有 POPi 等)来完成。

给被调用的 Perl 函数传参

在上面我们可以看到 call_sv 函数也没有传递参数的地方。那么怎么传递参数给被调用的 Perl 函数呢?

SV *
runcb(SV *sref, SV *argv)
CODE:
HV* plhash = (HV*)(SvRV(sref));
const char* key = "callback";
SV* cb = *hv_fetch(plhash, key, strlen(key), 0);
STRLEN len;
PUSHMARK(SP);
XPUSHs(sv_2mortal(argv));
PUTBACK;
int ret = call_sv(cb, G_SCALAR);
SPAGAIN;
if (ret != 1) {
croak("error");
};
SV* s = POPs;
printf("Here: %d %sn", ret, SvPV(s, len));
RETVAL = s;
PUTBACK;
OUTPUT: RETVAL

比较复杂啦~~

这里需要有一系列处理 Perl 堆栈的命令来完成传参处理,命令以 dSP 开头,不过如果编写的是 XS 函数,这步会自动处理可以省略,所以我们这里只需要从 PUSHMARK 开始。

PUSHMARK 标示开始推入参数到临时区域,然后具体的推入命令是 XPUSHs(多个就重复推),最后以 PUTBACK 标示参数推入完成。这时候 Perl 解释器就明白,给下面的 sub 准备的 @_ 已经完毕了,具体大小就是这么多不会再多了。

SPAGAIN 的作用是清理临时区域,因为说不准被调用函数里对临时区域做了什么操作。

同样是 POPs 取出,这里如果直接在 C 代码里 printf 的话,要注意把 SV 转换成 PV,否则是看不对的。

遍历哈希和返回数组

前面都是单个变量操作,最后我们来试试哈希遍历,然后返回数组变量。

AV *
runcb(SV *href)
CODE:
HV* plhash = (HV*)(SvRV(href));
char *key;
SV* sv_value;
I32 ret;
RETVAL = newAV();
hv_iterinit(plhash);
while ((sv_value = hv_iternextsv(plhash, &key, &ret))) {
av_push(RETVAL, sv_value);
}
OUTPUT: RETVAL

这里几个要点:

  • runcb() 函数的返回类型要改成 AV* 了。
  • RETVAL 需要单独声明复制才行。

写到这里我顺带想到,虽然 Perl5 一直都不对函数传参做什么验证,但是其实 XS 是 C 的自定义语言,所以写 XS 的时候,传参是会自动验证的。Perl5 二十年轮回,今年终于把传参验证给加上了,只能说一代人有一代人的想法啊。。。




网友评论已有0条评论, 我也要评论

发表评论

*

* (保密)

Ctrl+Enter 快捷回复