在Perl中扩展C库(1):XS语言(更新中)

2023-03-06 13:23:02 浏览数 (2)

1 前言

XS是Perl与C的胶水语言,通过它能在Perl中创建方法,以此扩展C库中的函数或新定义的C函数,详情可参阅《官方手册:perlxs》。

XS的编译器叫做xsubpp,它用typemaps去决定如何映射C函数的参量和输出值到Perl的值中并返回。“XSUB结构(XSUB forms)”是XS接口的基本单元,一个XSUB被编译后等效于一个C函数,其转化过程如下:

  1. XS从Perl栈中获取参数并转化为C函数期望的格式;
  2. 调用C函数;
  3. 将C函数的“输出值”翻译回Perl值。这里的“输出值”指的是C函数的返回值或出参。
    • 返回值:通过将返回值放回Perl栈来返回到Perl中
    • 出参:直接在Perl侧修改参数值

XSUB实际上还可以做很多事,比如:

  1. 检测入参是否有效;
  2. 抛出异常或返回undef()
  3. 基于参数个数或类型而调用不同的C函数;
  4. 提供一个面向对象的接口等。

1.1 代码规范

注意:这是作者本人在开发过程中总结出来的经验

缩进(单位为空格):

  • 章节:2
  • 代码:4
  • 参数:2

章节与章节之间间隔一个个空行

代码语言:javascript复制
function(a)
  INPUT:
    char *a;

  PREINIT:
    char *b;

参数列表的名称左对齐,*贴近名称,&贴近类型:

代码语言:javascript复制
function(a, b, c)
    char    *a;
    char     b;
    char &   c;

2 概要

假设有个C接口为:

代码语言:javascript复制
bool_t rpcb_gettime(const char *host, time_t *timep);

C函数的使用方法:

代码语言:javascript复制
#include <rpc/rpc.h>

bool_t status;

time_t timep;

status = rpcb_gettime("localhost", &timep);

期望在Perl中的调用为:

代码语言:javascript复制
use RPC;

$status = rpcb_gettime("localhost", $timep);

那么需要编写XS文件(XSUB)以扩展C中的rpcb_gettime函数,内容:

代码语言:javascript复制
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <rpc/rpc.h>

MODULE = RPC PACKAGE = RPC

bool_t
rpcb_gettime(host,timep)
    char *host
    time_t &timep
  OUTPUT:
    timep

当然,任何Perl扩展(即XS)想要插入到Perl中都需要通过Perl模块(.pm)来引导,因此还需要编写一个.pm文件:

代码语言:javascript复制
package RPC;

require Exporter;
require DynaLoader;

@ISA = qw(Exporter DynaLoader);

@EXPORT = qw( rpcb_gettime ); # 导出扩展中的 rpcb_gettime 方法

bootstrap RPC;                # 引导一个RPC的扩展

1;

3 剖析XSUB

最简单的XSUB由3个部分组成:

  1. 返回值类型
  2. 函数名
  3. 参数列表

比如:

代码语言:javascript复制
double
sin(x)
    double x

上述的XSUB意思是“允许Perl去访问一个同名的C函数sin(),并将double x作为其入参传入,同时返回一个double值给Perl”。

对C函数的指针类型,在XSUB中有两种表达方式,分别是*&;比如有下面的C函数:

代码语言:javascript复制
bool string_looks_as_a_number(char *s);
bool make_char_uppercase(char *c);

那么在XSUB中的参数列表中可以分别表示为:

代码语言:javascript复制
char *s
char &c

它们都表达着C语言中的指针,当然仍旧有一些细微的差别,在后续“The & Unary Operator“章节中讲述。

在书写格式上要求“返回值类型”、“函数名”和“参数列表”是需要在不同行的,且“返回值类型”与“函数名”需要左对齐,而“参数列表”则既可以保持左对齐,也可以缩进:

缩进(推荐):

代码语言:javascript复制
double
sin(x)
    double x

不缩进:

代码语言:javascript复制
double
sin(x)
double x

在“函数名”后面默认紧跟的参数列表其实是一个默认的章节INPUT:,在“函数名”之后的内容都由章节(section)声明后定义,比如:

代码语言:javascript复制
double
sin(x)
  PREINIT:
    char *host = "127.0.0.1"
  INPUT:
    double x

如上所示,在声明章节(section)的时候会紧跟着一个冒号(:),并在下面去定义内容。

3.1 参数栈

Perl用参数栈去存储Perl发送给XSUB的参数,以及XSUB要返回给Perl的返回值。XSUB用宏ST(x)来使用栈,比如在函数中的首个参数可以用ST(0)表示。无论是参数还是返回值都由ST(x)表达,并且总是从ST(0)作为开始,比如有“3个参数”的话,它们分别为ST(0)ST(1)ST(2)

3.2 变量:RETVAL

RETVAL变量是一个自动声明的特殊C变量。它会自动的匹配C库函数的返回值类型。xsubpp会为每个有返回值的XSUB都声明一个这样的变量。在默认情况下,XSUB创建的C函数会用RETVAL去存储调用C库函数时得到的返回值。

在简单的情况下,RETVAL的值会被放在ST(0)中,最终作为XSUB的返回值被Perl接收。

有两种情况下不会使用到该变量:

  1. 返回值类型为void
  2. 声明了PPCODE:
3.2.1 通过RETVAL返回SVs, AVs和HVs

如果返回值类型是SV *的话,会自动的把RETVAL标识为mortal,即退出函数后自动释放空间。比如下面的写法是等效的:

代码语言:javascript复制
void
alpha()
  PPCODE:
    ST(0) = newSVpv("Hello World",0);
    sv_2mortal(ST(0));
    XSRETURN(1);

SV *
beta()
  CODE:
    RETVAL = newSVpv("Hello World",0);

  OUTPUT:
    RETVAL

但是对于AV *HV *来说则不行,这是一个已知但又不能修复的Bug(修复它会导致CPAN模块出现问题),因此对于上面两种情况只能手动调用sv_2mortal才可以:

代码语言:javascript复制
AV *
array()
  CODE:
    RETVAL = newAV();
    sv_2mortal((SV*)RETVAL);
    /* do something with RETVAL */

  OUTPUT:
    RETVAL

3.3 关键字:MODULE

MODULE关键字用来标识XS代码的开始,同时在.pm文件中指令bootstrap引导的模块名就是由该指令指定的。如果没有用PACKAGE关键字设置包名(package),则默认使用MODULE的值作为package

在首个MODULE之前的代码都被当成C代码处理,当前如果其中有POD语句的话则会被识别并跳过。

这个指令在相同的XS文件中应当保持不变,仅最后一个MOUDLE名称有效。

代码语言:javascript复制
MODULE = RPC

3.4 关键字:PACKAGE

如果想把不同的函数分配给不同的package则使用此关键字,且该关键字必须跟在MODULE后面。

相同的PACKAGE值可以被使用超过一次,这样可以处理非连续情况的代码。通常该指令建议总是存在的,这样能够显示的表示出函数所属的package

代码语言:javascript复制
MODULE = RPC  PACKAGE = RPC
[ XS code in package RPC ]

MODULE = RPC  PACKAGE = RPCB
[ XS code in package RPCB ]

MODULE = RPC  PACKAGE = RPC
[ XS code in package RPC ]

3.5 关键字 PREFIX

PREFIX必须在PACKAGE之后,如果没有PACKAGE的话则必须在MODULE之后。

代码语言:javascript复制
MODULE = RPC  PREFIX = rpc_
MODULE = RPC  PACKAGE = RPCB  PREFIX = rpcb_

PREFIX的功能是用来移除固定前缀,它标注的前缀会在Perl中移除。比如:PREFIX = rpcb_,则对于rpcb_gettime()来说,在Perl中的调用则是gettime()

XS文件如下:

代码语言:javascript复制
MODULE = RPC  PACKAGE = RPCB  PREFIX = rpcb_

bool_t
rpcb_gettime(host,timep)
    char      *host
    time_t &   timep
  OUTPUT:
    timep

Perl调用如下:

代码语言:javascript复制
my $status = gettime($host, $time);

3.6 章节:OUTPUT

OUTPUT:章节用来指示哪些变量作为输出值返回到Perl中,通常有以下两种用途:

  1. 指示变量为函数返回值
  2. 指示参量列表中的变量为出参

在那些没有包含CODE:PPCODE:章节的简单函数中,RETVAL变量会被自动指示为函数出返回值,而在其它情况下,则需要OUTPUT去告诉xsubpp编译器哪些变量要作为输出值返回到Perl中。

代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
    char      *host
    time_t &   timep
  OUTPUT:
    timep

上述代码指出timep作为函数的出参,在Perl中的表现即是更新其变量的值。由于没有CODE:章节,因此RETVAL变量默认被作为为函数的返回值定义并使用。

3.7 关键字:NO_OUTPUT

NO_OUTPUT放置的位置是XSUB的开头。它表示如果C函数有返回一个非空值的话,应当被忽略。(我们知道C函数的返回值默认会被赋值到RETVAL变量,如果声明了此关键字,则RETVAL变量的值会被忽略掉,不会被返回给Perl)

这个关键字的意义在于生成一个更贴合Perl风格的函数,比如:

代码语言:javascript复制
NO_OUTPUT int
delete_file(char *name)
  POSTCALL:
    if (RETVAL != 0)
        croak("Error %d while deleting file '%s'", RETVAL, name);

上述代码在Perl中的表现是,成功的时候不会有值返回,但在失败的时候会die。这种风格是贴近Perl风格的,即把一个带有返回值的C函数,改为一个没有返回值但会抛出异常的Perl函数。

3.8 章节:CODE

该章节用于复杂的XSUB,在章节中写入一些C语句。如果使用了CODE:章节,RETVAL不再默认返回,需要显示的在OUTPUT:章节中指定。

代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
    char    *host
    time_t   timep
  CODE:
    RETVAL = rpcb_gettime( host, &timep );

  OUTPUT:
    timep
    RETVAL

3.9 章节:INIT

本章节允许在产生“调用到C函数”之前去做一些初始化动作。但它跟CODE:章节不同,它不会影响RETVAL的默认行为。

代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
    char      *host
    time_t &   timep
  INIT:
    if (host == NULL) XSRETURN_UNDEF;

  OUTPUT:
    timep

另一种使用本章节的场景是检查前置条件:

代码语言:javascript复制
long
lldiv(a,b)
    long long a
    long long b
  INIT:
    if (a == 0 && b == 0)
        XSRETURN_UNDEF;
    if (b == 0)
        croak("lldiv: cannot divide by 0");

3.10 关键字:NO_INIT

本关键字用来指示参量仅作为出参使用。默认情况下,xsubpp编译器会从参数栈中读取所有参量的值,并将它作为C函数的入参值使用。如果有参量被标记了此关键字,则xsubpp编译器将忽略它的初始值:

代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
    char *host
    time_t &timep = NO_INIT
  OUTPUT:
    timep

3.11 语法:初始化函数参量

C函数的参量的值通常是从Perl传过来的(XSUB负责将Perl类型值转化为C类型值)。通过“初始化函数参量”语法,可以去自定义初始化方法。

它的语法有三种,用=; 紧跟在参量名称后面,作为初始化语句的开头,比如:

代码语言:javascript复制
char *host = (char *)SvPV($arg,PL_na);
char *host   SvOK($v{timep}) ? SvPV($arg,PL_na) : NULL;
char *host ; (char *)SvPV($arg,PL_na);
  • =:正常的初始化语句
  • :当INPUT:语句执行完后再进行初始化,有滞后执行的效果
  • ;:除了做了 的操作之外,会使原本参量的初始化值过程不被执行。比如host原本在perl中传进来的值会被忽略掉。

该语法主要是用于如下场景:参量的值必须调用其它库获取

代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
    char *host = (char *)SvPV($arg,PL_na);
    time_t &timep = 0;
  OUTPUT:
    timep

此外,该语句在Perl中会自动的被双引号括起来,即可以使用变量,因此如果有任何包含特殊字符$@的都需要用进行转义。

3.12 语法:默认参量值

允许指定参量的默认值,可以设置的有效默认值为“数字”、“字符串”或者“NO_INIT”。

比如,用以下语句来设置host的默认值为"localhost"

代码语言:javascript复制
bool_t
rpcb_gettime(timep,host="localhost")
    char    *host
    time_t   timep = NO_INIT
  CODE:
    RETVAL = rpcb_gettime( host, &timep );

  OUTPUT:
    timep
    RETVAL

则在Perl中可以有以下两种方式调用:

代码语言:javascript复制
$status = rpcb_gettime( $timep, $host );
$status = rpcb_gettime( $timep );

3.13 章节:PREINIT

PREINIT:章节允许在INPUT:之前或之后添加变量。虽然INIT:CODE:都能添加变量,但是只有PREINIT:可以在INPUT:之前添加。并且该章节可以被多次使用。

代码语言:javascript复制
MyObject
mutate(o)
  PREINIT:
    MyState st = global_state;

  INPUT:
    MyObject o;

  CLEANUP:
    reset_to(global_state, st);

3.14 章节:SCOPE

SCOPE:章节用来决定某个特定的XSUB是否允许定义代码范围。代码范围理解起来就好像一堆花括号,花括号中间的临时变量的生命周期仅在花括号之间。当我们使用ENTER和LEAVE的时候就好比是分别填入了一个左括号和右括号。

因此,如果SCOPE:的值为DISABLE的话,则无法使用ENTERLEAVE

代码语言:javascript复制
SCOPE: ENABLE
代码语言:javascript复制
SCOPE: DISABLE

3.15 章节:INPUT

XSUB的参量通常都是在进入XSUB之后就立刻转换的,而INPUT:章节则可以有一些延后。该章节可以被多次使用。

代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
  PREINIT:
    time_t tt;

  INPUT:
    char *host

  PREINIT:
    char *h;

  INPUT:
    time_t timep

  CODE:
    h = host;
    RETVAL = rpcb_gettime( h, &tt );
    timep = tt;

  OUTPUT:
    timep
    RETVAL

如上所示,INPUT:章节可以添加非变量,因此也可以这么写:

代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
    time_t   tt;
    char    *host
    char    *h = host;
    time_t   timep
  CODE:
    RETVAL = rpcb_gettime( h, &tt );
    timep = tt;

  OUTPUT:
    timep
    RETVAL

3.16 关键字:IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT

以上关键字放在参量名前面修饰参量的处理方式。

  • IN:代表它是一个Perl入参
  • OUT:代表它是一个Perl出参
  • OUTLIST:代表它是一个Perl返回值
  • IN_OUTLIST:代表它既是入参,又是一个返回值
  • IN_OUT:代表它既是入参,又是出参

上述除了IN修饰的参量之外,其它都会当作指针传入到C接口中。

假设有以下C接口:

代码语言:javascript复制
void day_month(int *day, int unix_time, int *month);

我们期望如此扩展它为Perl接口:

代码语言:javascript复制
my ($day) = day_month(time, $month);

那么可以编写如下XSUB:

代码语言:javascript复制
void
day_month(OUTLIST day, IN unix_time, OUT month)
    int day
    int unix_time
    int month
  • day是Perl函数的返回值
  • unix_time是Perl函数的入参
  • month是Perl函数的出参

它与下面的写法等效:

代码语言:javascript复制
void
day_month(day, unix_time, month)
    int   *day = NO_INIT
    int    unix_time
    int &  month = NO_INIT
  OUTPUT:
    day
    month

3.17 函数:length(NAME)

C接口函数对字节或字符串参数很多时候都会需要额外传入一个长度,这跟Perl是不同的(Perl的函数不需要)。有一个便捷的写法来完成这样的转换,比如有一个如下的C接口:

代码语言:javascript复制
void dump_chars(char *s, short len);

这时候可以实现如下XSUB

代码语言:javascript复制
void dump_chars(char *s, short length(s))

在Perl中的使用如下:

代码语言:javascript复制
dump_chars($string)

3.18 语法:可变参数列表

XSUB支持可变参数列表,用...表示,总的参数个数用变量items保存:

代码语言:javascript复制
bool_t
rpcb_gettime(timep, ...)
    time_t timep = NO_INIT
  PREINIT:
    char *host = "localhost";
    STRLEN n_a;
  CODE:
    if( items > 1 )
        host = (char *)SvPV(ST(1), n_a);
    RETVAL = rpcb_gettime( host, &timep );
  OUTPUT:
    timep
    RETVAL

为此我们可以在Perl中如下使用:

代码语言:javascript复制
$status = rpcb_gettime( $timep, $host );

# 或者

$status = rpcb_gettime( $timep );

3.19 章节:C_ARGS

C_ARGS:章节可以定义调用C接口的参数和顺序,这样的好处是在一些情况下可以避免写一个CODE:或者PPCODE:章节。比如有如下C接口:

代码语言:javascript复制
symbolic nth_derivative(int n, symbolic function, int flags);

期望实现的Perl程序:

代码语言:javascript复制
$second_deriv = $function->nth_derivative(2);

可以如下实现XSUB

代码语言:javascript复制
symbolic
nth_derivative(function, n)
    symbolic        function
    int             n
  C_ARGS:
    n, function, default_flags

3.20 章节:PPCODE

PPCODE:CODE:的区别在于:

  • PPCODE:自定义返回的参数列表
  • PPCODE:不会产生RETVAL变量
  • CODE:会保留入参在堆栈中,并将SP指向末尾。而PPCODE:则将SP重新指向开头。

用法上,CODE:用于返回0或1个值。PPCODE:用于返回2个及以上的值。在PPCODE:中通过[X]PUSH*()宏来设置返回值的个数。

代码语言:javascript复制
void
rpcb_gettime(host)
    char *host
  PREINIT:
    time_t  timep;
    bool_t  status;

  PPCODE:
    status = rpcb_gettime( host, &timep );
    EXTEND(SP, 2); # 增加两个返回值的空间
    PUSHs(sv_2mortal(newSViv(status))); # 推入第一个返回值
    PUSHs(sv_2mortal(newSViv(timep)));  # 推入第二个返回值

如上所示,定义的Perl函数将返回两个值:

代码语言:javascript复制
my ($status, $timep) = rpcb_gettime("localhost");

3.21 语法:返回undef和空列表

返回值设置为SV *,再调用sv_newmortal去初始化一个返回值(默认值为undef),或则显示的返回PL_sv_undef

代码语言:javascript复制
SV *
rpcb_gettime(host)
    char *  host
  PREINIT:
    time_t  timep;
    bool_t x;

  CODE:
    ST(0) = sv_newmortal(); // 初始化返回值,默认值为 undef
    if( rpcb_gettime( host, &timep ) )
        sv_setnv( ST(0), (double)timep); // 填充返回值

或者显示的设置:

代码语言:javascript复制
SV *
rpcb_gettime(host)
    char *  host
  PREINIT:
    time_t  timep;
    bool_t x;
  CODE:
    if( rpcb_gettime( host, &timep ) )
    {
        ST(0) = sv_newmortal();
        sv_setnv( ST(0), (double)timep);
    }
    else
    {
        ST(0) = &PL_sv_undef; // 返回 undef
    }

如果想返回空列表必须使用PPCODE:章节:

代码语言:javascript复制
void
rpcb_gettime(host)
    char *host
  PREINIT:
    time_t  timep;

  PPCODE:
    if( rpcb_gettime( host, &timep ) )
    {
        PUSHs(sv_2mortal(newSViv(timep)));
    }
    else
    {
        /* Nothing pushed on stack, so an empty
        * list is implicitly returned. */
    }

还有一些宏可以显示的返回undef()XSRETURN_UNDEFXSRETURN_EMPTY。如下代码所示将返回undef

代码语言:javascript复制
int
rpcb_gettime(host)
    char *host
    time_t  timep;
  POSTCALL:
    if (RETVAL == 0)
          XSRETURN_UNDEF;

3.22 章节:REQUIRE

该章节用来设置xsubpp编译器的最小版本号,比如:

代码语言:javascript复制
REQUIRE: 1.922

3.23 章节:CLEANUP

CLEANUP:将会作为XSUB的最后一个语句执行,顾名思义,它就是用来做最后的释放操作的,当在程序退出前被调用。如果使用了CLEANUP:章节,那么它必须在CODE:/PPCODE:OUTPUT:章节的后面。

3.24 章节:POSTCALL

POSTCALL:章节在调用了C函数之后立刻执行,它必须再OUTPUT:CLEANUP:之前。

3.25 章节:BOOT

BOOT:章节可以插入一些代码在bootstrap函数中。

3.26 章节:VERSIONCHECK

是否版本检测。它对应于xsubpp-versioncheck-noversioncheck选项。该值会覆盖xsubpp的选项值。如果开启的话,则会尝试去检查当前XS的版本是否跟PM设置的版本是否匹配。

代码语言:javascript复制
VERSIONCHECK: ENABLE
代码语言:javascript复制
VERSIONCHECK: DISABLE

3.27 章节:PROTOTYPES

是否使用Perl函数的原型。它对应于xsubpp-prototypes-noprototypes选项。该值会覆盖xsubpp的选项值。该值可以使用多次,用于开启和关闭不同的部分。如果开启了的话,对应的XSUB将会使用Perl提供的prototypes。可以理解为将会根据参数列表来限制函数的入参。

代码语言:javascript复制
PROTOTYPES: ENABLE
代码语言:javascript复制
PROTOTYPES: DISABLE

3.28 章节:PROTOTYPE

自定义Perl函数的原型:

代码语言:javascript复制
bool_t
rpcb_gettime(timep, ...)
    time_t timep = NO_INIT
  PROTOTYPE: $;$

  PREINIT:
    char *host = "localhost";
    STRLEN n_a;

  CODE:
    if( items > 1 )
          host = (char *)SvPV(ST(1), n_a);
    RETVAL = rpcb_gettime( host, &timep );

  OUTPUT:
    timep
    RETVAL

当然也支持局部的关闭:

代码语言:javascript复制
void
rpcb_gettime_noproto()
    PROTOTYPE: DISABLE
...

3.29 章节:ALIAS

ALIAS:用来设置函数别名,设置方式是一个全称,包括了包名。同时XSUB会用变量ix存储当前调用的别名的索引值。比如:

  • 调用的是原函数声明的名称,则ix0
  • 调用的是别名,则ix为别名对应的索引值
代码语言:javascript复制
bool_t
rpcb_gettime(host,timep)
    char *host
    time_t &timep
  ALIAS:
    FOO::gettime = 1
    BAR::getit = 2

  INIT:
    printf("# ix = %dn", ix );

  OUTPUT:
    timep

如上所示,设置了两个别名FOO::gettimeBAR::getit,他们的索引值分别为12。如果此时调用的是FOO::gettime,则ix的值为1

3.30 章节:OVERLOAD

函数重载。

如果重载的是函数的话,必须是3个参数(除了nomethod())。如果是重载操作的话,必须确保操作不能有字母,引号,以及空格。

代码语言:javascript复制
SV *
cmp (lobj, robj, swap)
    My_Module_obj    lobj
    My_Module_obj    robj
    IV               swap
  OVERLOAD: cmp <=>
    { /* function defined here */}

3.31 章节:FALLBACK

对OVERLOAD:的补充。可以自动产生缺少的重载操作符。

代码语言:javascript复制
FALLBACK: TRUE

3.32 章节:INTERFACE

本章节跟ALIAS:有些相同的地方,都是可以定义一个额外的声明,但是在实现上却有些不同:

  1. 本章节定义的XSUB不需要switch语句去做区分,它们将共用同一个XSUB
  2. 可以被其他的XSUB调用。(此处还有盲点,下文暂不做解释)

比如想要定义4个perl函数multiplydivideaddsubtract,它们的C接口声明都是symbolic f(symbolic, symbolic);,那么可以如下定义XSUB

代码语言:javascript复制
symbolic
interface_s_ss(arg1, arg2)
    symbolic arg1
    symbolic arg2
  INTERFACE:
    multiply divide
    add subtract

3.33 章节:INTERFACE_MACRO

注:本章节暂时不知道如何使用,后续再展开

3.34 章节:INCLUDE

引用其他XS代码。有两种使用方式:

直接引用XS文件;

  • 假设有一个文件rpcb1.xsh
代码语言:javascript复制
bool_t
rpcb_gettime(host, timep)
    char       *host
    time_t &    timep
  • 定义以下语句来引用上述xs文件:
代码语言:javascript复制
INCLUDE: rpcb1.xsh

作为命令的方式,将执行结果作为引用的内容。

  • 在最后追加pipe(|)符号:
代码语言:javascript复制
INCLUDE: cat rpcb1.xsh |

3.35 章节:CASE

此章节跟C语言中的switch case有点类似,它允许一个XSUB中有多个不同的部分去执行特定的行为,最后一个CASE:相当于C语言中的default。如果使用了本章节,那么其他所有的XS关键字都必须被CASE:包含。可以被switch的变量有ixitems

代码语言:javascript复制
long
rpcb_gettime(a,b)
  CASE: ix == 1
    ALIAS:
      x_gettime = 1
    INPUT:
      # 'a' is timep, 'b' is host
      char *b
      time_t a = NO_INIT
    CODE:
           RETVAL = rpcb_gettime( b, &a );
    OUTPUT:
      a
      RETVAL
  CASE:
      # 'a' is host, 'b' is timep
      char *a
      time_t &b = NO_INIT
    OUTPUT:
      b
      RETVAL

上述示例在perl中可以如下调用:

代码语言:javascript复制
$status = rpcb_gettime($host, $timep);
代码语言:javascript复制
$status = x_gettime($timep, $host);

3.36 语法:&

与C 语言的引用类似,它表示将Perl变量转换为指针传入到C函数中,返回时再以变量的形式返回回去。效果与perl的recv接口类似:

代码语言:javascript复制
my $n = recv($data, 65536, 0);

print "$datan";

如上所示,$data是一个非引用的perl标量,但是却可以在接口中被修改并回传出来。

它能在INPUT:章节使用,比如:

代码语言:javascript复制
bool_t
rpcb_gettime(host, timep)
    char      *host
    time_t &   timep
  OUTPUT:
    timep

上述XSUB将会以rpcb_gettime(char *host, time_t *timep)的方式调用C函数。

4 插入POD、注释和预处理器指令

C预处理器指令允许在BOOT:PREINIT:CODE:PPCODE:POSTCALL:CLEANUP:章节中使用,也允许在函数外使用。

POD允许在任何地方使用,但必须用=cut结束POD。

注释以#开头表示,但之前不能有空格。应避免跟预处理器指令相同,最好的方式就是在预处理器的#前面面添加空格。

#else#endif之前,最好加一个空行,用来区分函数本身的内容

如果你的预处理器指令是用来做二选一的话,最好使用以下方式:

代码语言:javascript复制
#if ... version1
#else /* ... version2  */
#endif

而不是:

代码语言:javascript复制
#if ... version1
#endif
#if ... version2
#endif

5 扩展C

6 接口策略

7 Perl Objects和C Structures

8 Typemap

9 在XS中安全地存储静态数据

1 人点赞