前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在Perl中扩展C库(1):XS语言(更新中)

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

作者头像
Homqyy
发布2023-03-06 13:23:02
3.4K0
发布2023-03-06 13:23:02
举报
文章被收录于专栏:知行合一知行合一

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 = %d\n", 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 "$data\n";

如上所示,$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中安全地存储静态数据

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022年12月6日2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 前言
    • 1.1 代码规范
    • 2 概要
    • 3 剖析XSUB
      • 3.1 参数栈
        • 3.2 变量:RETVAL
          • 3.2.1 通过RETVAL返回SVs, AVs和HVs
        • 3.3 关键字:MODULE
          • 3.4 关键字:PACKAGE
            • 3.5 关键字 PREFIX
              • 3.6 章节:OUTPUT
                • 3.7 关键字:NO_OUTPUT
                  • 3.8 章节:CODE
                    • 3.9 章节:INIT
                      • 3.10 关键字:NO_INIT
                        • 3.11 语法:初始化函数参量
                          • 3.12 语法:默认参量值
                            • 3.13 章节:PREINIT
                              • 3.14 章节:SCOPE
                                • 3.15 章节:INPUT
                                  • 3.16 关键字:IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT
                                    • 3.17 函数:length(NAME)
                                      • 3.18 语法:可变参数列表
                                        • 3.19 章节:C_ARGS
                                          • 3.20 章节:PPCODE
                                            • 3.21 语法:返回undef和空列表
                                              • 3.22 章节:REQUIRE
                                                • 3.23 章节:CLEANUP
                                                  • 3.24 章节:POSTCALL
                                                    • 3.25 章节:BOOT
                                                      • 3.26 章节:VERSIONCHECK
                                                        • 3.27 章节:PROTOTYPES
                                                          • 3.28 章节:PROTOTYPE
                                                            • 3.29 章节:ALIAS
                                                              • 3.30 章节:OVERLOAD
                                                                • 3.31 章节:FALLBACK
                                                                  • 3.32 章节:INTERFACE
                                                                    • 3.33 章节:INTERFACE_MACRO
                                                                      • 3.34 章节:INCLUDE
                                                                        • 3.35 章节:CASE
                                                                          • 3.36 语法:&
                                                                          • 4 插入POD、注释和预处理器指令
                                                                            • 5 扩展C++
                                                                              • 6 接口策略
                                                                                • 7 Perl Objects和C Structures
                                                                                  • 8 Typemap
                                                                                    • 9 在XS中安全地存储静态数据
                                                                                    领券
                                                                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档