内容

名称

perlxs - XS 语言参考手册

描述

简介

XS 是一种接口描述文件格式,用于创建 Perl 和 C 代码(或 C 库)之间的扩展接口,以便在 Perl 中使用。XS 接口与库结合在一起,创建一个新的库,该库可以动态加载或静态链接到 perl 中。XS 接口描述是用 XS 语言编写的,是 Perl 扩展接口的核心组件。

在编写 XS 之前,请阅读下面的 "注意事项" 部分。

一个 XSUB 构成了 XS 接口的基本单元。在由 xsubpp 编译器编译后,每个 XSUB 等同于一个 C 函数定义,该函数将提供 Perl 调用约定和 C 调用约定之间的粘合代码。

粘合代码从 Perl 堆栈中提取参数,将这些 Perl 值转换为 C 函数期望的格式,调用此 C 函数,然后将 C 函数的返回值传递回 Perl。这里的返回值可以是传统的 C 返回值,也可以是任何用作输出参数的 C 函数参数。这些返回值可以通过将它们放到 Perl 堆栈上或修改从 Perl 端提供的参数来传递回 Perl。

以上是对实际发生情况的简化描述。由于 Perl 允许比 C 更灵活的调用约定,因此 XSUB 在实践中可能做更多的事情,例如检查输入参数的有效性,如果 C 函数的返回值指示失败则抛出异常(或返回 undef/空列表),根据参数的数量和类型调用不同的 C 函数,提供面向对象的接口等。

当然,可以将这种粘合代码直接用 C 语言编写。但是,这将是一项繁琐的任务,尤其是在需要为多个 C 函数编写粘合代码,或者对 Perl 堆栈规则和其他类似的深奥知识不熟悉的情况下。XS 在这里可以提供帮助:与其用长代码编写这种粘合 C 代码,不如编写一个更简洁的简短描述,说明粘合代码应该做什么,然后让 XS 编译器xsubpp处理剩下的工作。

XS 语言允许描述 C 函数的使用方式与对应 Perl 函数的使用方式之间的映射。它还允许创建直接转换为 C 代码的 Perl 函数,这些函数与预先存在的 C 函数无关。在 C 接口与 Perl 接口一致的情况下,XSUB 声明几乎与 C 函数的声明相同(以 K&R 风格)。在这种情况下,还有一个名为h2xs的工具,它可以将整个 C 头文件转换为相应的 XS 文件,该文件将为头文件中描述的函数/宏提供粘合代码。

XS 编译器称为xsubpp。此编译器创建必要的结构,使 XSUB 能够操作 Perl 值,并创建必要的粘合代码,使 Perl 能够调用 XSUB。编译器使用类型映射来确定如何将 C 函数参数和输出值映射到 Perl 值,反之亦然。默认类型映射(随 Perl 提供)处理许多常见的 C 类型。可能还需要补充类型映射来处理正在链接的库的任何特殊结构和类型。有关类型映射的更多信息,请参阅perlxstypemap

XS 格式的文件以 C 语言部分开头,一直持续到第一个MODULE =指令。其他 XS 指令和 XSUB 定义可能在此行之后。此文件部分中使用的“语言”通常称为 XS 语言。xsubpp识别并跳过 POD(参见perlpod)在 C 和 XS 语言部分,这允许 XS 文件包含嵌入式文档。

有关整个扩展创建过程的教程,请参阅perlxstut

注意:对于某些扩展,Dave Beazley 的 SWIG 系统可能提供了一种更方便的机制来创建扩展粘合代码。有关更多信息,请参阅http://www.swig.org/

对于简单的 C 库绑定以及其他机器代码库,请考虑使用更简单的libffi接口,通过 CPAN 模块,例如FFI::PlatypusFFI::Raw

在路上

以下许多示例将集中于创建 Perl 和 ONC+ RPC 绑定库函数之间的接口。rpcb_gettime() 函数用于演示 XS 语言的许多功能。此函数有两个参数;第一个是输入参数,第二个是输出参数。该函数还返回一个状态值。

bool_t rpcb_gettime(const char *host, time_t *timep);

从 C 语言中,这个函数将使用以下语句调用。

#include <rpc/rpc.h>
bool_t status;
time_t timep;
status = rpcb_gettime( "localhost", &timep );

如果创建一个 XSUB 来提供此函数与 Perl 之间的直接转换,那么这个 XSUB 将使用以下代码从 Perl 中调用。$status 和 $timep 变量将包含函数的输出。

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

以下 XS 文件展示了一个 XS 子例程,即 XSUB,它演示了与 rpcb_gettime() 函数交互的一种可能接口。这个 XSUB 代表了 C 和 Perl 之间的直接转换,因此即使从 Perl 中调用也保留了接口。这个 XSUB 将使用上面显示的使用方法从 Perl 中调用。请注意,前三个 #include 语句,用于 EXTERN.hperl.hXSUB.h,将始终出现在 XS 文件的开头。这种方法和其他方法将在本文档的后面部分进行扩展。应该存在一个用于 PERL_NO_GET_CONTEXT 的 #define,以便更有效地获取解释器上下文,有关详细信息,请参见 perlguts

#define PERL_NO_GET_CONTEXT
#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 的扩展,包括包含 XSUB 的扩展,都应该有一个 Perl 模块作为引导程序,将扩展引入 Perl。这个模块将把扩展的函数和变量导出到 Perl 程序中,并将导致扩展的 XSUB 被链接到 Perl 中。以下模块将用于本文档中的大多数示例,并且应该使用前面显示的 use 命令从 Perl 中使用。Perl 模块将在本文档的后面部分进行更详细的解释。

package RPC;

require Exporter;
require DynaLoader;
@ISA = qw(Exporter DynaLoader);
@EXPORT = qw( rpcb_gettime );

bootstrap RPC;
1;

在本文档中,将探索各种与 rpcb_gettime() XSUB 交互的接口。XSUB 将以不同的顺序接收参数,或者接收不同数量的参数。在每种情况下,XSUB 都是 Perl 和实际的 C rpcb_gettime() 函数之间的抽象,并且 XSUB 必须始终确保实际的 rpcb_gettime() 函数使用正确的参数调用。这种抽象将允许程序员为 C 函数创建更类似 Perl 的接口。

XSUB 的结构

最简单的 XSUB 包含 3 个部分:返回值的描述、XSUB 例程的名称及其参数的名称,以及参数的类型或格式的描述。

以下 XSUB 允许 Perl 程序访问名为 sin() 的 C 库函数。该 XSUB 将模仿 C 函数,该函数接受单个参数并返回单个值。

double
sin(x)
  double x

可选地,可以合并类型描述和参数名称列表,将其重写为

double
sin(double x)

这使得该 XSUB 看起来类似于 ANSI C 声明。在参数列表之后允许使用可选的分号,如

double
sin(double x);

具有 C 指针类型的参数可能具有不同的语义:具有类似声明的 C 函数

bool string_looks_as_a_number(char *s);
bool make_char_uppercase(char *c);

以完全不兼容的方式使用。这些函数的参数可以像这样描述给 xsubpp

char *  s
char    &c

这两个 XS 声明都对应于 char* C 类型,但它们具有不同的语义,请参阅 "The & Unary Operator"

方便地认为间接运算符 * 应该被视为类型的一部分,而地址运算符 & 应该被视为变量的一部分。有关在 C 类型中处理限定符和一元运算符的更多信息,请参阅 perlxstypemap

函数名称和返回值类型必须放在单独的行上,并且应该左对齐。

  INCORRECT                        CORRECT

  double sin(x)                    double
    double x                       sin(x)
				     double x

函数描述的其余部分可以缩进或左对齐。以下示例显示了一个函数及其左对齐的函数体。本文档中的大多数示例将缩进函数体以提高可读性。

CORRECT

double
sin(x)
double x

更复杂的 XSUB 可能包含许多其他部分。XSUB 的每个部分都以相应的关键字开头,例如 INIT: 或 CLEANUP:。但是,XSUB 的前两行始终包含相同的数据:返回值类型和函数及其参数的名称。紧随其后的任何内容都被视为 INPUT: 部分,除非明确标记为其他关键字。(请参阅 "The INPUT: Keyword"。)

XSUB 部分一直持续到找到另一个部分开始关键字。

参数栈

Perl 参数栈用于存储作为参数发送给 XSUB 的值以及存储 XSUB 的返回值。实际上,所有 Perl 函数(包括非 XSUB 函数)都将它们的值保存在此栈上,每个函数都限制在栈上的特定位置范围内。在本文档中,属于活动函数的栈上的第一个位置将被称为该函数的第 0 位。

XSUB 使用宏 **ST(x)** 来引用其堆栈参数,其中 *x* 指的是该 XSUB 在堆栈中所占部分的位置。对于该函数,位置 0 将被 XSUB 识别为 ST(0)。XSUB 的传入参数和传出返回值始终从 ST(0) 开始。对于许多简单的情况,**xsubpp** 编译器将生成处理参数堆栈所需的代码,方法是在类型映射中嵌入代码片段。在更复杂的情况下,程序员必须提供代码。

RETVAL 变量

RETVAL 变量是一个特殊的 C 变量,它会自动为您声明。RETVAL 的 C 类型与 C 库函数的返回类型匹配。**xsubpp** 编译器将在每个返回类型不为 void 的 XSUB 中声明此变量。默认情况下,生成的 C 函数将使用 RETVAL 来保存被调用的 C 库函数的返回值。在简单情况下,RETVAL 的值将被放置在参数堆栈的 ST(0) 中,在那里它可以被 Perl 接收,作为 XSUB 的返回值。

如果 XSUB 的返回类型为 void,则编译器不会为该函数声明 RETVAL 变量。当使用 PPCODE: 部分时,不需要对 RETVAL 变量进行任何操作,该部分可以使用直接堆栈操作将输出值放置在堆栈上。

如果未使用 PPCODE: 指令,则 void 返回值应仅用于不返回值的子例程,即使 使用了设置 ST(0) 的 CODE: 指令。

此文档的旧版本建议在这种情况下使用 void 返回值。发现这会导致 XSUB 真正void 时出现段错误。此做法现已弃用,并且在将来的某个版本中可能不再受支持。在这种情况下,使用返回值 SV *。(目前,xsubpp 包含一些启发式代码,试图区分“真正-void”和“旧做法-声明为-void”函数。因此,除非您使用 SV * 作为返回值,否则您的代码将受制于此启发式方法。)

通过 RETVAL 返回 SVs、AVs 和 HVs

当您使用 RETVAL 返回 SV * 时,幕后会发生一些需要提到的魔法。例如,当您使用 ST(x) 宏操作参数堆栈时,通常需要特别注意引用计数。(有关引用计数的更多信息,请参阅 perlguts。)为了让您的生活更轻松,类型映射文件会在您返回 SV * 时自动使 RETVAL 成为临时变量。因此,以下两个 XSUB 或多或少是等效的

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

这很有用,因为它通常可以提高可读性。虽然这对于 SV * 来说很好用,但不幸的是,将 AV *HV * 作为返回值并不容易。你应该能够写

AV *
array()
    CODE:
        RETVAL = newAV();
        /* do something with RETVAL */
    OUTPUT:
        RETVAL

但是由于 typemap 文件中存在一个无法修复的错误(修复它会破坏许多现有的 CPAN 模块),AV * 的引用计数没有被正确地递减。因此,上面的 XSUB 在每次被调用时都会发生内存泄漏。HV *CV *SVREF(表示标量引用,而不是一般的 SV *)也存在同样的问题。在 perl 5.16 及更高版本的 XS 代码中,你可以用具有正确引用计数处理的版本覆盖这些类型的 typemap。在你的 TYPEMAP 部分,执行

AV*	T_AVREF_REFCOUNT_FIXED

以获得修复后的变体。为了向后兼容旧版本的 perl,你也可以在使用 sv_2mortal 返回上述类型之一时手动递减引用计数

AV *
array()
    CODE:
        RETVAL = newAV();
        sv_2mortal((SV*)RETVAL);
        /* do something with RETVAL */
    OUTPUT:
        RETVAL

请记住,你不需要对 SV * 执行此操作。所有核心 typemap 的参考文档可以在 perlxstypemap 中找到。

MODULE 关键字

MODULE 关键字用于启动 XS 代码并指定正在定义的函数的包。所有在第一个 MODULE 关键字之前的文本都被视为 C 代码,并被传递到输出中,并去除 POD,但其他方面保持不变。每个 XS 模块都将有一个引导函数,用于将 XSUB 连接到 Perl。此引导函数的包名将与 XS 源文件中最后一个 MODULE 语句的值匹配。MODULE 的值在同一个 XS 文件中应该始终保持不变,尽管这不是必需的。

以下示例将启动 XS 代码,并将所有函数放在名为 RPC 的包中。

MODULE = RPC

PACKAGE 关键字

当 XS 源文件中的函数必须被分离到不同的包中时,应该使用 PACKAGE 关键字。此关键字与 MODULE 关键字一起使用,并且在使用时必须紧跟在 MODULE 关键字之后。

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 ]

同一个包名可以被使用多次,允许非连续的代码。如果你有一个比包名更强的排序原则,这将非常有用。

虽然此关键字是可选的,并且在某些情况下提供了冗余信息,但它应该始终被使用。此关键字将确保 XSUB 出现在所需的包中。

PREFIX 关键字

PREFIX 关键字指定应该从 Perl 函数名中删除的前缀。如果 C 函数是 rpcb_gettime() 并且 PREFIX 值是 rpcb_,那么 Perl 将看到此函数为 gettime()

使用此关键字时,它应紧随 PACKAGE 关键字。如果未使用 PACKAGE,则 PREFIX 应紧随 MODULE 关键字。

MODULE = RPC  PREFIX = rpc_

MODULE = RPC  PACKAGE = RPCB  PREFIX = rpcb_

OUTPUT: 关键字

OUTPUT: 关键字表示当 XSUB 终止时,某些函数参数应更新(对 Perl 可见的新值),或者某些值应返回给调用 Perl 函数。对于没有 CODE: 或 PPCODE: 部分的简单函数,例如上面的 sin() 函数,RETVAL 变量会自动被指定为输出值。对于更复杂的函数,xsubpp 编译器需要帮助来确定哪些变量是输出变量。

此关键字通常用于补充 CODE: 关键字。当存在 CODE: 关键字时,RETVAL 变量不被识别为输出变量。在这种情况下,使用 OUTPUT: 关键字告诉编译器 RETVAL 实际上是一个输出变量。

OUTPUT: 关键字也可以用于指示函数参数是输出变量。当参数在函数内部被修改,并且程序员希望 Perl 看到更新时,这可能很有必要。

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

OUTPUT: 关键字还允许将输出参数映射到匹配的代码段,而不是类型映射。

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t &timep
   OUTPUT:
     timep sv_setnv(ST(1), (double)timep);

xsubpp 为 XSUB 的 OUTPUT 部分中的所有参数(除了 RETVAL)发出自动的 SvSETMAGIC()。这通常是期望的行为,因为它负责在输出参数上正确调用“set”魔法(对于必须在不存在时创建的哈希或数组元素参数来说是必需的)。如果出于某种原因,不希望这种行为,OUTPUT 部分可以包含 SETMAGIC: DISABLE 行来为 OUTPUT 部分中剩余的参数禁用它。同样,SETMAGIC: ENABLE 可以用来为 OUTPUT 部分中剩余的参数重新启用它。有关“set”魔法的更多详细信息,请参见 perlguts

NO_OUTPUT 关键字

NO_OUTPUT 可以作为 XSUB 的第一个标记放置。此关键字表示,虽然我们提供的 C 子例程的接口具有非 void 返回类型,但此 C 子例程的返回值不应从生成的 Perl 子例程返回。

在存在此关键字的情况下,"RETVAL 变量" 被创建,并且在对子例程的生成调用中,此变量被分配给,但此变量的值不会在自动生成的代码中使用。

此关键字只有在 RETVAL 将被用户提供的代码访问时才有意义。它特别有用,可以使函数接口更像 Perl,尤其是在 C 返回值仅仅是一个错误条件指示器时。例如,

  NO_OUTPUT int
  delete_file(char *name)
    POSTCALL:
      if (RETVAL != 0)
	  croak("Error %d while deleting file '%s'", RETVAL, name);

这里生成的 XS 函数在成功时不返回任何内容,并在错误时使用有意义的错误消息终止。

CODE: 关键字

此关键字用于更复杂的 XSUB,这些 XSUB 需要对 C 函数进行特殊处理。RETVAL 变量仍然被声明,但除非在 OUTPUT: 部分中指定,否则它不会被返回。

以下 XSUB 用于需要对其参数进行特殊处理的 C 函数。首先给出 Perl 用法。

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

XSUB 如下。

bool_t
rpcb_gettime(host,timep)
     char *host
     time_t timep
   CODE:
          RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

INIT: 关键字

INIT: 关键字允许在编译器生成对 C 函数的调用之前将初始化插入到 XSUB 中。与上面的 CODE: 关键字不同,此关键字不会影响编译器处理 RETVAL 的方式。

    bool_t
    rpcb_gettime(host,timep)
          char *host
          time_t &timep
	INIT:
	  printf("# Host is %s\n", host );
        OUTPUT:
          timep

INIT: 部分的另一个用途是在调用 C 函数之前检查先决条件。

    long 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");

NO_INIT 关键字

NO_INIT 关键字用于指示函数参数仅用作输出值。xsubpp 编译器通常会生成代码,以在进入函数时从参数栈读取所有函数参数的值并将其分配给 C 变量。NO_INIT 将告诉编译器某些参数将用于输出而不是用于输入,并且它们将在函数终止之前进行处理。

以下示例显示了 rpcb_gettime() 函数的变体。此函数仅将 timep 变量用作输出变量,并且不关心其初始内容。

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

TYPEMAP: 关键字

从 Perl 5.16 开始,您可以将类型映射嵌入到 XS 代码中,而不是或除了单独文件中的类型映射之外。多个这样的嵌入式类型映射将按照其在 XS 代码中出现的顺序进行处理,并且与本地类型映射文件一样,优先于默认类型映射,嵌入式类型映射可能会覆盖 TYPEMAP、INPUT 和 OUTPUT 节的先前定义。嵌入式类型映射的语法为

TYPEMAP: <<HERE
... your typemap code here ...
HERE

其中 TYPEMAP 关键字必须出现在新行的第一列。

有关编写类型映射的详细信息,请参阅 perlxstypemap

初始化函数参数

C 函数参数通常使用来自参数栈的值进行初始化(参数栈包含从 Perl 传递给 XSUB 的参数)。类型映射包含用于将 Perl 值转换为 C 参数的代码段。但是,程序员可以覆盖类型映射并提供备用(或附加)初始化代码。初始化代码从 INPUT: 部分中一行上的第一个 =;+ 开始。唯一的例外是,如果此 ; 终止该行,则此 ; 会被静默忽略。

以下代码演示了如何为函数参数提供初始化代码。初始化代码在编译器将其添加到输出之前,在双引号内进行 eval,因此任何应该按字面解释的内容(主要是 $@\\)都必须用反斜杠进行保护。变量 $var$arg$type 可以像在类型映射中一样使用。

bool_t
rpcb_gettime(host,timep)
     char *host = (char *)SvPVbyte_nolen($arg);
     time_t &timep = 0;
   OUTPUT:
     timep

这不能用于为参数提供默认值。通常,当函数参数必须在使用之前由另一个库函数处理时,才会使用此方法。默认参数将在下一节中介绍。

如果初始化以 = 开头,则它将在输入变量的声明中输出,替换类型映射提供的初始化。如果初始化以 ;+ 开头,则它将在所有输入变量声明之后执行。在 ; 情况下,通常由类型映射提供的初始化不会执行。对于 + 情况,变量的声明将包括来自类型映射的初始化。一个全局变量 %v 可用于极少数情况下需要从一个初始化中获取信息以用于另一个初始化的情况。

这是一个非常模糊的例子

bool_t
rpcb_gettime(host,timep)
     time_t &timep; /* \$v{timep}=@{[$v{timep}=$arg]} */
     char *host + SvOK($v{timep}) ? SvPVbyte_nolen($arg) : NULL;
   OUTPUT:
     timep

在上面的例子中使用的结构 \$v{timep}=@{[$v{timep}=$arg]} 有两重目的:首先,当此行由 xsubpp 处理时,Perl 代码段 $v{timep}=$arg 会被执行。其次,执行的代码段的文本会输出到生成的 C 文件中(在 C 注释内)!在处理 char *host 行时,$arg 将评估为 ST(0),而 $v{timep} 将评估为 ST(1)

默认参数值

可以通过在参数列表中放置一个赋值语句来指定 XSUB 参数的默认值。默认值可以是数字、字符串或特殊字符串 NO_INIT。默认值应始终仅用于最右边的参数。

为了允许 rpcb_gettime() 的 XSUB 具有默认主机值,可以重新排列 XSUB 的参数。然后,XSUB 将以正确的顺序使用这些参数调用真正的 rpcb_gettime() 函数。可以使用以下任一语句从 Perl 调用此 XSUB

$status = rpcb_gettime( $timep, $host );

$status = rpcb_gettime( $timep );

XSUB 将类似于以下代码。CODE: 块用于以该函数的正确顺序调用真正的 rpcb_gettime() 函数。

bool_t
rpcb_gettime(timep,host="localhost")
     char *host
     time_t timep = NO_INIT
   CODE:
          RETVAL = rpcb_gettime( host, &timep );
   OUTPUT:
     timep
     RETVAL

PREINIT: 关键字

PREINIT: 关键字允许在从 INPUT: 部分发出参数声明之前或之后立即声明额外的变量。

如果在 CODE: 部分内声明变量,它将遵循为输入参数发出的任何类型映射代码。这可能导致声明最终出现在 C 代码之后,这是 C 语法错误。类似的错误也可能发生在使用显式 ; 类型或 + 类型参数初始化时(参见 "初始化函数参数")。在 INIT: 部分中声明这些变量不会有帮助。

在这种情况下,要强制将附加变量与其他变量的声明一起声明,请将声明放在 PREINIT: 部分中。PREINIT: 关键字可以在 XSUB 中使用一次或多次。

以下示例是等效的,但如果代码使用复杂的类型映射,则第一个示例更安全。

     bool_t
     rpcb_gettime(timep)
          time_t timep = NO_INIT
	PREINIT:
          char *host = "localhost";
        CODE:
	  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

对于这种情况,INIT: 关键字将生成与 PREINIT: 关键字相同的 C 代码。另一个正确但容易出错的示例

     bool_t
     rpcb_gettime(timep)
          time_t timep = NO_INIT
	CODE:
          char *host = "localhost";
	  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

声明 host 的另一种方法是在 CODE: 部分中使用 C 块

     bool_t
     rpcb_gettime(timep)
          time_t timep = NO_INIT
	CODE:
	  {
            char *host = "localhost";
	    RETVAL = rpcb_gettime( host, &timep );
	  }
        OUTPUT:
          timep
          RETVAL

在类型映射条目被处理之前添加额外声明的能力在类型映射转换操作某些全局状态的情况下非常有用

    MyObject
    mutate(o)
	PREINIT:
	    MyState st = global_state;
	INPUT:
	    MyObject o;
	CLEANUP:
	    reset_to(global_state, st);

这里我们假设 INPUT: 部分中的 MyObject 转换以及处理 RETVAL 时从 MyObject 的转换将修改全局变量 global_state。在执行这些转换后,我们将恢复 global_state 的旧值(例如,为了避免内存泄漏)。

还有另一种方法可以以清晰度换取简洁性:INPUT 部分允许声明子程序参数列表中未出现的 C 变量。因此,mutate() 的上述代码可以改写为

    MyObject
    mutate(o)
	  MyState st = global_state;
	  MyObject o;
	CLEANUP:
	  reset_to(global_state, st);

而 rpcb_gettime() 的代码可以改写为

     bool_t
     rpcb_gettime(timep)
	  time_t timep = NO_INIT
	  char *host = "localhost";
	C_ARGS:
	  host, &timep
	OUTPUT:
          timep
          RETVAL

SCOPE: 关键字

SCOPE: 关键字允许为特定 XSUB 启用作用域。如果启用,XSUB 将自动调用 ENTER 和 LEAVE。

为了支持可能复杂的类型映射,如果 XSUB 使用的类型映射条目包含类似 /*scope*/ 的注释,则将自动为该 XSUB 启用作用域。

要启用作用域

SCOPE: ENABLE

禁用作用域

SCOPE: DISABLE

INPUT: 关键字

XSUB 的参数通常在进入 XSUB 后立即进行评估。INPUT: 关键字可用于强制这些参数稍后进行评估。INPUT: 关键字可以在 XSUB 中使用多次,并且可以用于列出多个输入变量。此关键字与 PREINIT: 关键字一起使用。

以下示例展示了如何延迟评估输入参数 timep,在 PREINIT 之后进行评估。

    bool_t
    rpcb_gettime(host,timep)
          char *host
	PREINIT:
	  time_t tt;
	INPUT:
          time_t timep
        CODE:
               RETVAL = rpcb_gettime( host, &tt );
	       timep = tt;
        OUTPUT:
          timep
          RETVAL

下一个示例展示了每个输入参数的延迟评估。

    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 部分允许声明 C 变量,这些变量未出现在子例程的参数列表中,因此可以将其简化为

    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

(我们利用了对 char * 的输入转换是“简单”的这一知识,因此 host 在声明行上初始化,并且我们的赋值 h = host 不会过早执行。否则,需要在 CODE: 或 INIT: 部分中进行赋值 h = host。)

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

在 XSUB 的参数列表中,可以在参数名称前加上 IN/OUTLIST/IN_OUTLIST/OUT/IN_OUT 关键字。IN 关键字是默认值,其他关键字指示 Perl 接口应如何与 C 接口不同。

OUTLIST/IN_OUTLIST/OUT/IN_OUT 关键字开头的参数被视为由 C 子例程通过指针使用。OUTLIST/OUT 关键字指示 C 子例程不会检查此参数指向的内存,但将通过此指针写入以提供其他返回值。

OUTLIST 关键字开头的参数不会出现在生成的 Perl 函数的使用签名中。

IN_OUTLIST/IN_OUT/OUT 开头的参数作为 Perl 函数的参数出现。除了 OUT 参数外,这些参数将转换为相应的 C 类型,然后指向这些数据的指针将作为参数传递给 C 函数。预计 C 函数将通过这些指针写入。

生成的 Perl 函数的返回值列表由函数的 C 返回值组成(除非 XSUB 的返回类型为 void 或使用了 The NO_OUTPUT Keyword),后面跟着所有 OUTLISTIN_OUTLIST 参数(按出现的顺序)。在从 XSUB 返回时,IN_OUT/OUT Perl 参数将被修改为具有 C 函数写入的值。

例如,一个 XSUB

void
day_month(OUTLIST day, IN unix_time, OUTLIST month)
  int day
  int unix_time
  int month

应该从 Perl 中使用

my ($day, $month) = day_month(time);

对应函数的 C 签名应该是

void day_month(int *day, int unix_time, int *month);

IN/OUTLIST/IN_OUTLIST/IN_OUT/OUT 关键字可以与 ANSI 风格的声明混合使用,例如

void
day_month(OUTLIST int day, int unix_time, OUTLIST int month)

(这里省略了可选的 IN 关键字)。

IN_OUT 参数与使用 "The & Unary Operator" 引入并放入 OUTPUT: 部分的参数相同(参见 "The OUTPUT: Keyword")。IN_OUTLIST 参数非常相似,唯一的区别是 C 函数通过指针写入的值不会修改 Perl 参数,而是放在输出列表中。

OUTLIST/OUT 参数与 IN_OUTLIST/IN_OUT 参数的区别仅在于 Perl 参数的初始值不被读取(并且不被传递给 C 函数 - 它会得到一些垃圾)。例如,与上面相同的 C 函数可以作为

void day_month(OUT int day, int unix_time, OUT int month);

void
day_month(day, unix_time, month)
    int &day = NO_INIT
    int  unix_time
    int &month = NO_INIT
  OUTPUT:
    day
    month

然而,生成的 Perl 函数以非常 C 风格的方式调用

my ($day, $month);
day_month($day, time, $month);

length(NAME) 关键字

如果 C 函数的一个输入参数是字符串参数 NAME 的长度,则可以在 XSUB 声明中用 length(NAME) 替换长度参数的名称。当调用生成的 Perl 函数时,必须省略此参数。例如,

void
dump_chars(char *s, short l)
{
  short n = 0;
  while (n < l) {
      printf("s[%d] = \"\\%#03o\"\n", n, (int)s[n]);
      n++;
  }
}

MODULE = x		PACKAGE = x

void dump_chars(char *s, short length(s))

应该调用为 dump_chars($string)

此指令仅支持 ANSI 类型函数声明。

可变长度参数列表

XSUB 可以通过在参数列表中指定省略号 (...) 来拥有可变长度参数列表。省略号的这种用法类似于 ANSI C 中的用法。程序员可以通过检查 xsubpp 编译器为所有 XSUB 提供的 items 变量来确定传递给 XSUB 的参数数量。通过使用这种机制,可以创建一个接受未知长度参数列表的 XSUB。

rpcb_gettime() XSUB 的 host 参数可以是可选的,因此省略号可以用来表示 XSUB 将接受可变数量的参数。Perl 应该能够使用以下任一语句调用此 XSUB。

$status = rpcb_gettime( $timep, $host );

$status = rpcb_gettime( $timep );

带有省略号的 XS 代码如下。

     bool_t
     rpcb_gettime(timep, ...)
          time_t timep = NO_INIT
	PREINIT:
          char *host = "localhost";
        CODE:
	  if( items > 1 )
	       host = (char *)SvPVbyte_nolen(ST(1));
	  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

C_ARGS: 关键字

C_ARGS: 关键字允许创建从 Perl 到 C 的调用顺序不同的 XSUB,而无需编写 CODE: 或 PPCODE: 部分。C_ARGS: 段落的内容将作为参数传递给调用的 C 函数,没有任何更改。

例如,假设一个 C 函数被声明为

symbolic nth_derivative(int n, symbolic function, int flags);

并且默认标志存储在一个全局 C 变量 default_flags 中。假设您想创建一个接口,调用方式如下

$second_deriv = $function->nth_derivative(2);

为此,将 XSUB 声明为

    symbolic
    nth_derivative(function, n)
	symbolic	function
	int		n
      C_ARGS:
	n, function, default_flags

PPCODE: 关键字

PPCODE: 关键字是 CODE: 关键字的另一种形式,用于告诉 xsubpp 编译器程序员正在提供代码来控制 XSUB 返回值的参数栈。有时,您可能希望 XSUB 返回一个值列表而不是单个值。在这种情况下,您必须使用 PPCODE:,然后显式地将值列表压入栈。PPCODE: 和 CODE: 关键字不应该在同一个 XSUB 中一起使用。

PPCODE: 和 CODE: 部分之间的实际区别在于 SP 宏(代表当前 Perl 栈指针)的初始化,以及从 XSUB 返回时处理栈上的数据。在 CODE: 部分中,SP 保留进入 XSUB 时的值:SP 位于函数指针上(紧随最后一个参数之后)。在 PPCODE: 部分中,SP 向后移动到参数列表的开头,这允许 PUSH*() 宏将输出值放置在 XSUB 返回到 Perl 时 Perl 预期它们所在的位置。

为 CODE: 部分生成的尾部代码确保 Perl 将看到的返回值数量为 0 或 1(取决于 C 函数的返回值是否为 void,以及 "RETVAL 变量" 中提到的启发式方法)。为 PPCODE: 部分生成的尾部代码基于返回值的数量以及 SP[X]PUSH*() 宏更新的次数。

请注意,宏 ST(i)XST_m*()XSRETURN*() 在 CODE: 部分和 PPCODE: 部分中都能正常工作。

以下 XSUB 将调用 C 函数 rpcb_gettime(),并将它的两个输出值 timep 和 status 作为单个列表返回到 Perl。

     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)));

请注意,程序员必须提供必要的 C 代码来调用实际的 rpcb_gettime() 函数,并将返回值正确地放置在参数栈上。

此函数的 void 返回类型告诉 xsubpp 编译器 RETVAL 变量不需要或不使用,并且不应该创建它。在大多数情况下,void 返回类型应该与 PPCODE: 指令一起使用。

EXTEND() 宏用于在参数栈上为 2 个返回值腾出空间。PPCODE: 指令使 **xsubpp** 编译器创建一个可作为 `SP` 使用的栈指针,EXTEND() 宏正是使用此指针。然后使用 PUSHs() 宏将值压入栈中。

现在,可以使用以下语句从 Perl 中使用 rpcb_gettime() 函数。

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

在使用 PPCODE 部分处理输出参数时,请确保正确处理“set”魔法。有关“set”魔法的详细信息,请参阅 perlguts

返回未定义和空列表

有时,程序员希望在函数失败时仅返回 `undef` 或空列表,而不是单独的 状态值。rpcb_gettime() 函数恰好提供了这种情况。如果函数成功,我们希望它返回时间;如果失败,我们希望返回 undef。在以下 Perl 代码中,$timep 的值将是 undef 或有效时间。

$timep = rpcb_gettime( "localhost" );

以下 XSUB 使用 `SV *` 返回类型作为助记符,并使用 CODE: 块来指示编译器程序员已提供所有必要的代码。sv_newmortal() 调用将初始化返回值为 undef,使其成为默认返回值。

     SV *
     rpcb_gettime(host)
          char *  host
	PREINIT:
          time_t  timep;
          bool_t x;
        CODE:
          ST(0) = sv_newmortal();
          if( rpcb_gettime( host, &timep ) )
               sv_setnv( ST(0), (double)timep);

以下示例演示了如何在需要时将显式 undef 放入返回值中。

     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;
          }

要返回空列表,必须使用 PPCODE: 块,然后不要将返回值压入栈中。

     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. */
          }

有些人可能倾向于在上面的 XSUB 中包含一个显式的 `return`,而不是让控制流通过到末尾。在这种情况下,应使用 `XSRETURN_EMPTY`。这将确保 XSUB 栈被正确调整。有关其他 `XSRETURN` 宏,请参阅 perlapi

由于 `XSRETURN_*` 宏也可以与 CODE 块一起使用,因此可以将此示例改写为

     int
     rpcb_gettime(host)
          char *host
	PREINIT:
          time_t  timep;
        CODE:
          RETVAL = rpcb_gettime( host, &timep );
	  if (RETVAL == 0)
		XSRETURN_UNDEF;
	OUTPUT:
	  RETVAL

事实上,可以将此检查放入 POSTCALL: 部分。结合 PREINIT: 简化,这将导致

     int
     rpcb_gettime(host)
          char *host
          time_t  timep;
	POSTCALL:
	  if (RETVAL == 0)
		XSRETURN_UNDEF;

REQUIRE: 关键字

REQUIRE: 关键字用于指示编译 XS 模块所需的 **xsubpp** 编译器的最低版本。包含以下语句的 XS 模块将仅使用 **xsubpp** 版本 1.922 或更高版本进行编译

REQUIRE: 1.922

CLEANUP: 关键字

当 XSUB 需要在终止之前执行特殊的清理程序时,可以使用此关键字。当使用 CLEANUP: 关键字时,它必须在 XSUB 中存在的任何 CODE: 或 OUTPUT: 块之后。为清理块指定的代码将作为 XSUB 中的最后语句添加。

POSTCALL: 关键字

当 XSUB 需要在执行 C 子例程调用后执行特殊程序时,可以使用此关键字。当使用 POSTCALL: 关键字时,它必须位于 XSUB 中存在的 OUTPUT: 和 CLEANUP: 块之前。

请参阅 "NO_OUTPUT 关键字""返回未定义和空列表" 中的示例。

当用户通过提供 CODE: 或 PPCODE: 部分来提供 C 子例程调用时,POSTCALL: 块没有太大意义。

BOOT: 关键字

BOOT: 关键字用于向扩展的引导函数添加代码。引导函数由 xsubpp 编译器生成,通常包含将任何 XSUB 注册到 Perl 所需的语句。使用 BOOT: 关键字,程序员可以告诉编译器向引导函数添加额外的语句。

此关键字可以在第一个 MODULE 关键字之后随时使用,并且应该出现在单独的一行上。关键字后的第一个空行将终止代码块。

BOOT:
# The following message will be printed when the
# bootstrap function executes.
printf("Hello from the bootstrap!\n");

VERSIONCHECK: 关键字

VERSIONCHECK: 关键字对应于 xsubpp-versioncheck-noversioncheck 选项。此关键字会覆盖命令行选项。版本检查默认情况下启用。当启用版本检查时,XS 模块将尝试验证其版本是否与 PM 模块的版本匹配。

要启用版本检查

VERSIONCHECK: ENABLE

要禁用版本检查

VERSIONCHECK: DISABLE

请注意,如果 PM 模块的版本是 NV(浮点数),它将被转换为字符串,可能会丢失精度(目前截断到小数点后九位),因此可能不再与 XS 模块的版本匹配。如果使用长版本号,建议将 $VERSION 声明引起来使其成为字符串。

PROTOTYPES: 关键字

PROTOTYPES: 关键字对应于 xsubpp-prototypes-noprototypes 选项。此关键字会覆盖命令行选项。原型默认情况下禁用。当启用原型时,XSUB 将被赋予 Perl 原型。此关键字可以在 XS 模块中多次使用,以启用和禁用模块不同部分的原型。请注意,如果您没有明确启用或禁用原型,xsubpp 会提醒您,提示

Please specify prototyping behavior for Foo.xs (see perlxs manual)

要启用原型

PROTOTYPES: ENABLE

禁用原型

PROTOTYPES: DISABLE

PROTOTYPE: 关键字

此关键字类似于上面的 PROTOTYPES: 关键字,但可用于强制 xsubpp 为 XSUB 使用特定原型。此关键字会覆盖所有其他原型选项和关键字,但仅影响当前 XSUB。有关 Perl 原型的信息,请参阅 "perlsub 中的原型"

    bool_t
    rpcb_gettime(timep, ...)
          time_t timep = NO_INIT
	PROTOTYPE: $;$
	PREINIT:
          char *host = "localhost";
        CODE:
		  if( items > 1 )
		       host = (char *)SvPVbyte_nolen(ST(1));
		  RETVAL = rpcb_gettime( host, &timep );
        OUTPUT:
          timep
          RETVAL

如果启用了原型,则可以像以下示例一样在给定 XSUB 上本地禁用它

void
rpcb_gettime_noproto()
    PROTOTYPE: DISABLE
...

ALIAS: 关键字

ALIAS: 关键字允许 XSUB 具有两个或多个唯一的 Perl 名称,并知道在调用它时使用了哪些名称。Perl 名称可以是使用包名称完全限定的。每个别名都分配了一个索引。编译器将设置一个名为 ix 的变量,其中包含所用别名的索引。当使用声明的名称调用 XSUB 时,ix 将为 0。

以下示例将为此函数创建别名 FOO::gettime()BAR::getit()

    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

当您为同一个值创建多个别名时,会产生警告。可以通过创建解析为相同值的多个定义来以向后兼容的方式解决此问题,或者使用现代版本的 ExtUtils::ParseXS,您可以使用符号别名,符号别名用 => 表示,而不是 =。例如,您可以更改上面的内容,使别名部分看起来像这样

	ALIAS:
	    FOO::gettime = 1
	    BAR::getit = 2
            BAZ::gettime => FOO::gettime

这将与以下内容具有相同的效果

	ALIAS:
	    FOO::gettime = 1
	    BAR::getit = 2
            BAZ::gettime = 1

但后者将在构建过程中产生警告。在我们的工具链的旧版本中以向后兼容的方式工作的一种机制是执行以下操作

    #define FOO_GETTIME 1
    #define BAR_GETIT 2
    #define BAZ_GETTIME 1

    bool_t
    rpcb_gettime(host,timep)
          char *host
          time_t &timep
	ALIAS:
	    FOO::gettime = FOO_GETTIME
	    BAR::getit = BAR_GETIT
            BAZ::gettime = BAZ_GETTIME
	INIT:
	  printf("# ix = %d\n", ix );
        OUTPUT:
          timep

OVERLOAD: 关键字

除了使用纯 Perl 编写重载接口之外,您还可以使用 OVERLOAD 关键字为您的函数定义其他 Perl 名称(类似于上面的 ALIAS: 关键字)。但是,重载函数必须以接受 Perl 重载系统提供的参数数量的方式定义。对于大多数重载方法,它将是三个参数;对于 nomethod 函数,它将是四个。但是,按位运算符 &|^~ 可以用三个或五个参数调用(请参阅 overload)。

如果任何函数具有 OVERLOAD: 关键字,则 xsubpp 生成的 c 文件中将定义几行附加行,以便与重载魔法注册。

由于祝福对象实际上存储为 RV,因此使用类型映射功能预处理参数并提取祝福 RV 中存储的实际 SV 非常有用。请参阅下面 T_PTROBJ_SPECIAL 的示例。

要使用 OVERLOAD: 关键字,请创建一个接受三个输入参数的 XS 函数(或使用 C 样式的 '...' 定义),如下所示

SV *
cmp (lobj, robj, swap)
My_Module_obj    lobj
My_Module_obj    robj
IV               swap
OVERLOAD: cmp <=>
{ /* function defined here */}

在这种情况下,该函数将重载三个比较运算符中的两个。对于使用非字母字符的所有重载操作,您必须在不加引号的情况下键入参数,并用空格分隔多个重载。请注意,""(字符串化重载)应输入为 \"\"(即转义)。

由于如上所述,按位运算符可能需要额外的参数,因此您可能希望使用类似 (lobj, robj, swap, ...)(带字面量 ...)作为您的参数列表。

FALLBACK: 关键字

除了 OVERLOAD 关键字之外,如果您需要控制 Perl 如何自动生成缺少的重载运算符,您可以在模块头部分设置 FALLBACK 关键字,如下所示

MODULE = RPC  PACKAGE = RPC

FALLBACK: TRUE
...

其中 FALLBACK 可以取三个值中的任何一个:TRUE、FALSE 或 UNDEF。如果您在使用 OVERLOAD 时未设置任何 FALLBACK 值,则默认为 UNDEF。FALLBACK 仅在定义了一个或多个使用 OVERLOAD 的函数时才会使用。有关更多详细信息,请参阅 "fallback" in overload

INTERFACE: 关键字

此关键字将当前 XSUB 声明为给定调用签名的保管者。如果此关键字后跟一些文本,则该文本被视为具有此签名的函数列表,应附加到当前 XSUB。

例如,如果您有 4 个 C 函数 multiply()、divide()、add()、subtract(),它们都具有签名

symbolic f(symbolic, symbolic);

您可以使用以下方法使它们都使用相同的 XSUB

    symbolic
    interface_s_ss(arg1, arg2)
	symbolic	arg1
	symbolic	arg2
    INTERFACE:
	multiply divide
	add subtract

(这是 4 个 Perl 函数的完整 XSUB 代码!)四个生成的 Perl 函数与相应的 C 函数共享名称。

与 ALIAS: 关键字相比,这种方法的优势在于不需要编写 switch 语句,每个 Perl 函数(共享相同的 XSUB)都知道应该调用哪个 C 函数。此外,可以使用以下方法在运行时附加一个额外的函数 remainder()

    CV *mycv = newXSproto("Symbolic::remainder",
			  XS_Symbolic_interface_s_ss, __FILE__, "$$");
    XSINTERFACE_FUNC_SET(mycv, remainder);

例如,来自另一个 XSUB。(此示例假设没有 INTERFACE_MACRO: 部分,否则需要使用其他东西而不是 XSINTERFACE_FUNC_SET,请参阅下一节。)

INTERFACE_MACRO: 关键字

此关键字允许您使用不同的方法来从 XSUB 中提取函数指针来定义 INTERFACE。此关键字后的文本应给出用于提取/设置函数指针的宏的名称。提取器宏被赋予返回类型 CV*XSANY.any_dptr 用于此 CV*。设置器宏被赋予 cv 和函数指针。

默认值为XSINTERFACE_FUNCXSINTERFACE_FUNC_SET。如果使用INTERFACE_MACRO关键字,则可以省略带有空函数列表的INTERFACE关键字。

假设在前面的示例中,multiply()、divide()、add()、subtract()的函数指针保存在一个全局C数组fp[]中,偏移量分别为multiply_offdivide_offadd_offsubtract_off。然后可以使用

    #define XSINTERFACE_FUNC_BYOFFSET(ret,cv,f) \
	((XSINTERFACE_CVT_ANON(ret))fp[CvXSUBANY(cv).any_i32])
    #define XSINTERFACE_FUNC_BYOFFSET_set(cv,f) \
	CvXSUBANY(cv).any_i32 = CAT2( f, _off )

在C部分中,

    symbolic
    interface_s_ss(arg1, arg2)
	symbolic	arg1
	symbolic	arg2
      INTERFACE_MACRO:
	XSINTERFACE_FUNC_BYOFFSET
	XSINTERFACE_FUNC_BYOFFSET_set
      INTERFACE:
	multiply divide
	add subtract

在XSUB部分中。

INCLUDE: 关键字

此关键字可用于将其他文件拉入XS模块。其他文件可能包含XS代码。INCLUDE: 也可用于运行命令以生成要拉入模块的XS代码。

文件Rpcb1.xsh包含我们的rpcb_gettime()函数

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

XS模块可以使用INCLUDE: 将该文件拉入其中。

INCLUDE: Rpcb1.xsh

如果INCLUDE: 关键字的参数后面跟着管道(|),则编译器将参数解释为命令。此功能在一定程度上已过时,建议使用INCLUDE_COMMAND: 指令,如下所述。

INCLUDE: cat Rpcb1.xsh |

不要使用它来运行perl:INCLUDE: perl | 将运行路径中第一个perl,而不是运行xsubpp的perl。请参阅"INCLUDE_COMMAND: 关键字"

INCLUDE_COMMAND: 关键字

运行提供的命令并将输出包含到当前XS文档中。INCLUDE_COMMAND$^X 令牌赋予特殊含义,因为它运行与运行xsubpp 相同的perl解释器

INCLUDE_COMMAND: cat Rpcb1.xsh

INCLUDE_COMMAND: $^X -e ...

CASE: 关键字

CASE: 关键字允许XSUB具有多个不同的部分,每个部分充当虚拟XSUB。CASE: 是贪婪的,如果使用它,则所有其他XS关键字都必须包含在CASE: 中。这意味着在XSUB中第一个CASE: 之前不能有任何内容,最后一个CASE: 之后的所有内容都包含在该case中。

CASE: 可以通过XSUB的参数、ix ALIAS: 变量(请参阅"ALIAS: 关键字")或items 变量(请参阅"可变长度参数列表")进行切换。如果最后一个CASE: 没有与条件相关联,则它将成为默认 case。以下示例显示了通过ix 切换的CASE,其中函数rpcb_gettime() 有一个别名x_gettime()。当函数以rpcb_gettime() 的形式调用时,它的参数是通常的(char *host, time_t *timep),但是当函数以x_gettime() 的形式调用时,它的参数是反转的,(time_t *timep, char *host)

    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

可以使用以下任一语句调用该函数。请注意不同的参数列表。

$status = rpcb_gettime( $host, $timep );

$status = x_gettime( $timep, $host );

EXPORT_XSUB_SYMBOLS: 关键字

EXPORT_XSUB_SYMBOLS: 关键字很可能你永远不会用到。在 5.16.0 之前的 Perl 版本中,这个关键字没有任何作用。从 5.16 开始,XSUB 符号不再默认导出。也就是说,它们是 `static` 函数。如果你在你的 XS 代码中包含

EXPORT_XSUB_SYMBOLS: ENABLE

那么在这行代码之后的 XSUB 将不会被声明为 `static`。你之后可以用

EXPORT_XSUB_SYMBOLS: DISABLE

来禁用它,这又是你可能永远不应该改变的默认设置。你不能在 5.16 之前的 Perl 版本中使用这个关键字来使 XSUB 成为 `static`。

& 一元运算符

INPUT: 部分中的 `&` 一元运算符用于告诉 **xsubpp** 它应该使用 `&` 左侧的 C 类型将 Perl 值转换为 C 类型或从 C 类型转换为 Perl 值,但在调用 C 函数时提供该值的指针。

这对于避免为接受引用参数的 C 函数创建 CODE: 块很有用。通常,参数不应该是指针类型(一个 `int` 或 `long`,而不是一个 `int*` 或 `long*`)。

以下 XSUB 将生成错误的 C 代码。**xsubpp** 编译器将把它转换成调用 `rpcb_gettime()` 的代码,参数为 `(char *host, time_t timep)`,但真正的 `rpcb_gettime()` 希望 `timep` 参数的类型为 `time_t*` 而不是 `time_t`。

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

这个问题可以通过使用 `&` 运算符来解决。**xsubpp** 编译器现在将把它转换成调用 `rpcb_gettime()` 的代码,参数为 `(char *host, time_t *timep)`。它通过传递 `&` 来做到这一点,所以函数调用看起来像 `rpcb_gettime(host, &timep)`。

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

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

C 预处理器指令允许在 BOOT:、PREINIT: INIT:、CODE:、PPCODE:、POSTCALL: 和 CLEANUP: 块中,以及函数外部使用。注释允许在 MODULE 关键字之后的任何地方使用。编译器将原样传递预处理器指令,并删除注释行。POD 文档允许在任何地方使用,包括 C 和 XS 语言部分。POD 必须以 `=cut` 命令结束;如果 `xsubpp` 没有找到它,它将退出并报错。人类生成的 C 代码不太可能被误认为是 POD,因为大多数缩进风格都会在以 `=` 开头的任何行前面产生空格。机器生成的 XS 文件可能会陷入这个陷阱,除非小心确保空格打破了 "\n=" 序列。

可以通过将 `#` 作为一行中第一个非空格字符来向 XSUB 添加注释。应该注意避免使注释看起来像 C 预处理器指令,以免被解释为 C 预处理器指令。防止这种情况发生的最佳方法是在 `#` 前面加上空格。

如果使用预处理器指令选择两个函数版本中的一个,请使用

#if ... version1
#else /* ... version2  */
#endif

而不是

#if ... version1
#endif
#if ... version2
#endif

因为否则 xsubpp 会认为你对函数进行了重复定义。此外,在 #else/#endif 之前放置一个空行,这样它就不会被视为函数体的一部分。

使用 XS 与 C++

如果 XSUB 名称包含 ::,则它被视为 C++ 方法。生成的 Perl 函数将假设其第一个参数是对象指针。对象指针将存储在一个名为 THIS 的变量中。该对象应该由 C++ 使用 new() 函数创建,并应该由 Perl 使用 sv_setref_pv() 宏进行祝福。对象由 Perl 进行的祝福可以通过类型映射来处理。本节末尾显示了一个示例类型映射。

如果 XSUB 的返回类型包含 static,则该方法被视为静态方法。它将使用 class::method() 语法调用 C++ 函数。如果该方法不是静态的,则该函数将使用 THIS->method() 语法调用。

以下示例将使用以下 C++ 类。

class color {
     public:
     color();
     ~color();
     int blue();
     void set_blue( int );

     private:
     int c_blue;
};

blue() 和 set_blue() 方法的 XSUB 使用类名定义,但对象参数 (THIS 或 "self") 是隐式的,未列出。

int
color::blue()

void
color::set_blue( val )
     int val

两个 Perl 函数都将期望对象作为第一个参数。在生成的 C++ 代码中,对象称为 THIS,并且将在该对象上执行方法调用。因此,在 C++ 代码中,blue() 和 set_blue() 方法将被调用为 this

RETVAL = THIS->blue();

THIS->set_blue( val );

也可以使用可选参数编写单个 get/set 方法

int
color::blue( val = NO_INIT )
    int val
    PROTOTYPE $;$
    CODE:
        if (items > 1)
            THIS->set_blue( val );
        RETVAL = THIS->blue();
    OUTPUT:
        RETVAL

如果函数的名称为 DESTROY,则将调用 C++ delete 函数,并将 THIS 作为其参数传递。生成的 C++ 代码为

void
color::DESTROY()

将如下所示

color *THIS = ...;  // Initialized as in typemap

delete THIS;

如果函数的名称为 new,则将调用 C++ new 函数以创建动态 C++ 对象。XSUB 将期望类名 (将保存在名为 CLASS 的变量中) 作为第一个参数传递。

color *
color::new()

生成的 C++ 代码将调用 new

RETVAL = new color();

以下是一个可用于此 C++ 示例的类型映射示例。

TYPEMAP
color *  O_OBJECT

OUTPUT
# The Perl object is blessed into 'CLASS', which should be a
# char* having the name of the package for the blessing.
O_OBJECT
    sv_setref_pv( $arg, CLASS, (void*)$var );

INPUT
O_OBJECT
    if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) )
        $var = ($type)SvIV((SV*)SvRV( $arg ));
    else{
        warn(\"${Package}::$func_name() -- \"
            \"$var is not a blessed SV reference\");
        XSRETURN_UNDEF;
    }

接口策略

在设计 Perl 和 C 库之间的接口时,从 C 到 XS 的直接转换 (例如由 h2xs -x 创建) 通常就足够了。但是,有时接口看起来非常像 C,偶尔会不直观,尤其是在 C 函数修改其参数之一或在带内返回失败 (如 "负返回值表示失败") 时。在程序员希望创建更像 Perl 的接口的情况下,以下策略可能有助于识别接口中更关键的部分。

识别具有输入/输出或输出参数的 C 函数。这些函数的 XSUB 可能能够将列表返回给 Perl。

识别使用某些带内信息作为失败指示的 C 函数。它们可能是返回 undef 或空列表以指示失败的候选函数。如果可以在不调用 C 函数的情况下检测到失败,则可能需要使用 INIT: 部分来报告失败。对于在 C 函数返回后可检测到的失败,可能需要使用 POSTCALL: 部分来处理失败。在更复杂的情况下,使用 CODE: 或 PPCODE: 部分。

如果许多函数使用基于返回值的相同失败指示,则可能需要创建一个特殊的 typedef 来处理这种情况。将

typedef int negative_is_failure;

放在 XS 文件的开头,并为 negative_is_failure 创建一个 OUTPUT 类型映射条目,该条目将负值转换为 undef,或者可能调用 croak()。在此之后,类型为 negative_is_failure 的返回值将创建更类似 Perl 的接口。

识别仅由 C 和 XSUB 函数本身使用的值,例如,当函数的参数应该是全局变量的内容时。如果 Perl 不需要访问该值的內容,则可能不需要提供从 C 到 Perl 的该值的转换。

识别 C 函数参数列表和返回值中的指针。一些指针可能用于实现输入/输出或输出参数,它们可以在 XS 中使用 & 一元运算符处理,并且可能使用 NO_INIT 关键字。其他一些则需要处理 int * 等类型,并且需要确定在这种情况下有用的 Perl 转换将执行什么操作。当语义明确时,建议将转换放入类型映射文件。

识别 C 函数使用的结构。在许多情况下,使用 T_PTROBJ 类型映射来处理这些结构可能会有所帮助,这样它们就可以被 Perl 作为祝福对象进行操作。(这由 h2xs -x 自动处理。)

如果相同的 C 类型在需要不同转换的几种不同上下文中使用,则为该 C 类型定义几个新类型,并为这些新类型创建单独的类型映射条目。在 XSUB 的返回值和参数声明中使用这些类型。

Perl 对象和 C 结构

在处理 C 结构体时,应该为 XS 类型选择 T_PTROBJT_PTRREF。这两种类型都旨在处理指向复杂对象的指针。T_PTRREF 类型允许 Perl 对象取消祝福,而 T_PTROBJ 类型要求对象被祝福。通过使用 T_PTROBJ,可以实现一种类型检查,因为 XSUB 将尝试验证 Perl 对象是否为预期类型。

以下 XS 代码展示了与 ONC+ TIRPC 一起使用的 getnetconfigent() 函数。getnetconfigent() 函数将返回指向 C 结构体的指针,并具有以下所示的 C 原型。该示例将演示 C 指针如何成为 Perl 引用。Perl 将认为此引用是指向已祝福对象的指针,并将尝试调用该对象的析构函数。在 XS 源代码中将提供一个析构函数来释放 getnetconfigent() 使用的内存。XS 中的析构函数可以通过指定名称以 DESTROY 结尾的 XSUB 函数来创建。XS 析构函数可用于释放可能由另一个 XSUB 使用 malloc 分配的内存。

struct netconfig *getnetconfigent(const char *netid);

将为 struct netconfig 创建一个 typedef。Perl 对象将在与 C 类型名称匹配的类中被祝福,并附加标签 Ptr,并且如果它将成为 Perl 包名称,则名称不应包含嵌入的空格。析构函数将放置在与对象类对应的类中,并且将使用 PREFIX 关键字将名称修剪为 DESTROY,因为 Perl 将期望这样做。

typedef struct netconfig Netconfig;

MODULE = RPC  PACKAGE = RPC

Netconfig *
getnetconfigent(netid)
     char *netid

MODULE = RPC  PACKAGE = NetconfigPtr  PREFIX = rpcb_

void
rpcb_DESTROY(netconf)
     Netconfig *netconf
   CODE:
     printf("Now in NetconfigPtr::DESTROY\n");
     free( netconf );

此示例需要以下类型映射条目。有关为扩展添加新类型映射的更多信息,请参阅 perlxstypemap

TYPEMAP
Netconfig *  T_PTROBJ

此示例将与以下 Perl 语句一起使用。

use RPC;
$netconf = getnetconfigent("udp");

当 Perl 销毁 $netconf 引用的对象时,它将把该对象发送到提供的 XSUB DESTROY 函数。Perl 无法确定,也不关心,此对象是 C 结构体还是 Perl 对象。从这个意义上说,由 getnetconfigent() XSUB 创建的对象与由普通 Perl 子例程创建的对象之间没有区别。

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

从 Perl 5.8 开始,定义了一个宏框架,允许将静态数据安全地存储在 XS 模块中,这些模块将从多线程 Perl 中访问。

虽然主要设计用于多线程 Perl,但这些宏的设计使其也能与非线程 Perl 一起使用。

因此,强烈建议所有使用静态数据的 XS 模块使用这些宏。

获取要使用的模板宏集的最简单方法是使用 h2xs 指定 -g (--global) 选项(参见 h2xs)。

下面是一个使用这些宏的示例模块。

    #define PERL_NO_GET_CONTEXT
    #include "EXTERN.h"
    #include "perl.h"
    #include "XSUB.h"

    /* Global Data */

    #define MY_CXT_KEY "BlindMice::_guts" XS_VERSION

    typedef struct {
        int count;
        char name[3][100];
    } my_cxt_t;

    START_MY_CXT

    MODULE = BlindMice           PACKAGE = BlindMice

    BOOT:
    {
        MY_CXT_INIT;
        MY_CXT.count = 0;
        strcpy(MY_CXT.name[0], "None");
        strcpy(MY_CXT.name[1], "None");
        strcpy(MY_CXT.name[2], "None");
    }

    int
    newMouse(char * name)
        PREINIT:
          dMY_CXT;
        CODE:
          if (MY_CXT.count >= 3) {
              warn("Already have 3 blind mice");
              RETVAL = 0;
          }
          else {
              RETVAL = ++ MY_CXT.count;
              strcpy(MY_CXT.name[MY_CXT.count - 1], name);
          }
        OUTPUT:
          RETVAL

    char *
    get_mouse_name(index)
          int index
        PREINIT:
          dMY_CXT;
        CODE:
          if (index > MY_CXT.count)
            croak("There are only 3 blind mice.");
          else
            RETVAL = MY_CXT.name[index - 1];
        OUTPUT:
          RETVAL

    void
    CLONE(...)
	CODE:
	  MY_CXT_CLONE;

MY_CXT 参考

MY_CXT_KEY

此宏用于定义一个唯一的键,用于引用 XS 模块的静态数据。建议的命名方案(如 h2xs 所用)是使用一个字符串,该字符串由模块名称、字符串 "::_guts" 和模块版本号组成。

#define MY_CXT_KEY "MyModule::_guts" XS_VERSION
typedef my_cxt_t

此结构 typedef 必须始终称为 my_cxt_t。其他 CXT* 宏假设存在 my_cxt_t typedef 名称。

声明一个名为 my_cxt_t 的 typedef,它是一个结构,包含所有需要解释器本地化的数据。

typedef struct {
    int some_value;
} my_cxt_t;
START_MY_CXT

始终将 START_MY_CXT 宏直接放在 my_cxt_t 的声明之后。

MY_CXT_INIT

MY_CXT_INIT 宏初始化 my_cxt_t 结构的存储空间。

必须被调用一次,通常在 BOOT: 部分。如果您维护多个解释器,则应在每个解释器实例中调用一次,除了从现有解释器克隆的解释器。(但请参见下面的 "MY_CXT_CLONE"。)

dMY_CXT

在所有访问 MY_CXT 的函数中使用 dMY_CXT 宏(声明)。

MY_CXT

使用 MY_CXT 宏访问 my_cxt_t 结构的成员。例如,如果 my_cxt_t

typedef struct {
    int index;
} my_cxt_t;

那么使用它来访问 index 成员

dMY_CXT;
MY_CXT.index = 2;
aMY_CXT/pMY_CXT

dMY_CXT 的计算可能非常昂贵,为了避免在每个函数中调用它带来的开销,可以使用 aMY_CXT/pMY_CXT 宏将声明传递到其他函数,例如

    void sub1() {
	dMY_CXT;
	MY_CXT.index = 1;
	sub2(aMY_CXT);
    }

    void sub2(pMY_CXT) {
	MY_CXT.index = 2;
    }

类似于 pTHX,当宏是多个参数中的第一个或最后一个时,存在等效形式,其中下划线表示逗号,即 _aMY_CXTaMY_CXT__pMY_CXTpMY_CXT_

MY_CXT_CLONE

默认情况下,当创建一个新的解释器作为现有解释器的副本时(例如通过 threads->create()),两个解释器共享相同的物理 my_cxt_t 结构。调用 MY_CXT_CLONE(通常通过包的 CLONE() 函数),会导致对结构进行逐字节复制,并且任何未来的 dMY_CXT 将导致访问该副本。

MY_CXT_INIT_INTERP(my_perl)
dMY_CXT_INTERP(my_perl)

这些是将显式解释器作为参数的宏版本。

请注意,这些宏仅在同一个源文件中一起使用;也就是说,一个源文件中的 dMY_CTX 将访问与另一个源文件中的 dMY_CTX 不同的结构。

线程感知系统接口

从 Perl 5.8 开始,在 C/C++ 级别的 Perl 知道如何将具有线程感知版本的系统/库接口(例如 getpwent_r())包装到前端宏(例如 getpwent())中,这些宏可以正确处理与 Perl 解释器的多线程交互。这将透明地发生,您唯一需要做的是实例化一个 Perl 解释器。

当编译 Perl 核心源代码(定义了 PERL_CORE)或 Perl 核心扩展(定义了 PERL_EXT)时,始终会发生这种包装。在 Perl 5.28 之前,在 Perl 核心之外编译 XS 代码时,不会进行包装。从该版本开始,您可以

#define PERL_REENTRANT

在您的代码中启用包装。如果您正在使用此类函数,建议您这样做,因为混合使用_r形式(正如为多线程操作编译的 Perl 所做的那样)和无_r形式既没有明确定义(不一致的结果、数据损坏甚至崩溃的可能性更大),也不便携。不幸的是,并非所有系统都具有所有_r形式,但使用此#define可以为您提供 Perl 在每个系统上都意识到的任何保护。

示例

文件RPC.xs:与某些 ONC+ RPC 绑定库函数的接口。

     #define PERL_NO_GET_CONTEXT
     #include "EXTERN.h"
     #include "perl.h"
     #include "XSUB.h"

     /* Note: On glibc 2.13 and earlier, this needs be <rpc/rpc.h> */
     #include <tirpc/rpc.h>

     typedef struct netconfig Netconfig;

     MODULE = RPC  PACKAGE = RPC

     SV *
     rpcb_gettime(host="localhost")
          char *host
	PREINIT:
          time_t  timep;
        CODE:
          ST(0) = sv_newmortal();
          if( rpcb_gettime( host, &timep ) )
               sv_setnv( ST(0), (double)timep );

     Netconfig *
     getnetconfigent(netid="udp")
          char *netid

     MODULE = RPC  PACKAGE = NetconfigPtr  PREFIX = rpcb_

     void
     rpcb_DESTROY(netconf)
          Netconfig *netconf
        CODE:
          printf("NetconfigPtr::DESTROY\n");
          free( netconf );

文件typemap:RPC.xs 的自定义类型映射。(参见 perlxstypemap

TYPEMAP
Netconfig *  T_PTROBJ

文件RPC.pm:RPC 扩展的 Perl 模块。

package RPC;

require Exporter;
require DynaLoader;
@ISA = qw(Exporter DynaLoader);
@EXPORT = qw(rpcb_gettime getnetconfigent);

bootstrap RPC;
1;

文件rpctest.pl:RPC 扩展的 Perl 测试程序。

use RPC;

$netconf = getnetconfigent();
$a = rpcb_gettime();
print "time = $a\n";
print "netconf = $netconf\n";

$netconf = getnetconfigent("tcp");
$a = rpcb_gettime("poplar");
print "time = $a\n";
print "netconf = $netconf\n";

在 Makefile.PL 中添加 -ltirpc 和 -I/usr/include/tirpc。

注意事项

XS 代码可以完全访问系统调用,包括 C 库函数。因此,它有能力干扰 Perl 核心或其他模块设置的某些内容,例如信号处理程序或文件句柄。它可能会弄乱内存,或者做任何其他有害的事情。不要这样做。

某些模块具有事件循环,等待用户输入。两个这样的模块在单个 Perl 应用程序中一起正常工作的可能性极小。

一般来说,perl 解释器将自己视为 Perl 程序的宇宙中心。XS 代码被视为助手,用于完成 perl 不做的事情,或者做得不够快的事情,但始终服从于 perl。XS 代码越接近此模型,发生冲突的可能性就越小。

一个发生冲突的领域是关于 C 本地化。(参见 perllocale。)perl,除了一个例外,除非另有说明,会将程序运行的底层本地化设置为从环境中传递给它的本地化。这与通用 C 语言程序有很大不同,在通用 C 语言程序中,底层本地化是“C”本地化,除非程序更改它。从 v5.20 开始,此底层本地化完全隐藏在纯 Perl 代码中,除非在use locale 的词法范围内,除了 POSIX 模块中的几个函数调用,这些函数调用必须使用它。但是,底层本地化(除了那个例外)会暴露给 XS 代码,影响所有行为依赖于本地化的 C 库例程。您的 XS 代码最好不要假设底层本地化是“C”。例外是 LC_NUMERIC 本地化类别,它之所以是例外,是因为经验表明它可能对 XS 代码造成问题,而我们没有收到关于其他本地化类别出现问题的报告。而这个类别之所以有问题,是因为用作小数点的字符可能会有所不同。许多欧洲语言使用逗号,而英语(因此是 Perl)则期望使用点(U+002E:句号)。许多模块只能处理基数字符为点的情况,因此 perl 尝试使其成为可能。在 Perl v5.20 之前,尝试仅仅是在启动时将LC_NUMERIC 设置为"C" 本地化。任何其他setlocale() 都将更改它;这导致了一些失败。因此,从 v5.22 开始,perl 尝试始终将LC_NUMERIC 设置为"C" 以供 XS 代码使用。

总结一下,以下是您对 XS 代码中本地化的预期和处理方法

不了解本地化的 XS 代码

请记住,即使您认为您的代码不了解本地化,它也可能会调用一个了解本地化的库函数。希望该函数的手册页会指出这种依赖关系,但文档并不完善。

当前区域设置对 XS 代码是可见的,除了可能 LC_NUMERIC(下一段解释)。其他类别没有出现问题报告。Perl 在启动时初始化,以便当前区域设置是用户环境在启动时指示的区域设置。请参阅 "perllocale 中的 ENVIRONMENT"

但是,直到 v5.20 版本,Perl 在启动时初始化,以便 LC_NUMERIC 设置为 "C" 区域设置。但如果任何代码在任何地方更改了它,它将保持更改。这意味着您的模块无法依赖 LC_NUMERIC 是特定的东西,您也不能期望浮点数(包括版本字符串)包含点。如果您不允许非点,那么如果任何人在任何地方更改了区域设置,您的代码可能会崩溃。出于这个原因,v5.22 更改了行为,以便 Perl 尝试将 LC_NUMERIC 保持在 "C" 区域设置中,除了在内部应该使用其他区域设置的操作周围。行为不端的 XS 代码始终能够更改区域设置,但这种情况最常见的实例会被检查并处理。

支持区域设置的 XS 代码

如果需要用户环境中的区域设置,则 XS 代码无需设置区域设置,除了 LC_NUMERIC,因为 perl 已经设置了其他区域设置。XS 代码应避免更改区域设置,因为它会对其他无关代码产生负面影响,并且可能不是线程安全的。为了最大程度地减少问题,宏 "perlapi 中的 STORE_LC_NUMERIC_SET_TO_NEEDED""perlapi 中的 STORE_LC_NUMERIC_FORCE_TO_UNDERLYING""perlapi 中的 RESTORE_LC_NUMERIC" 应该用于影响任何必要的更改。

但是,从 Perl v5.28 开始,在支持此功能的平台上,区域设置是线程安全的。Windows 从 Visual Studio 2005 开始支持此功能。许多其他现代平台支持线程安全的 POSIX 2008 函数。C #define USE_THREAD_SAFE_LOCALE 将仅当此构建使用这些函数时定义。从 Perl 空间,只读变量 ${SAFE_LOCALES} 为 1,如果构建不是线程化的,或者如果定义了 USE_THREAD_SAFE_LOCALE;否则为 0。

在底层,每个线程可以选择使用特定于它的区域设置(这是 Windows 和 POSIX 2008 的功能),或者使用所有线程都可以访问的全局区域设置(这是始终存在的功能)。Windows 和 POSIX 的实现完全不同。在 Windows 上,可以设置运行时,以便标准 setlocale(3) 函数只知道全局区域设置或当前线程的区域设置。在 POSIX 上,setlocale 始终处理全局区域设置,并且创建了其他函数来处理每个线程的区域设置。Perl 使这对于 perl 空间代码透明。它继续使用 POSIX::setlocale(),解释器将其转换为每个线程的函数。

所有其他与区域设置相关的函数都会自动使用每个线程的区域设置(如果已启用),否则使用全局区域设置。因此,如果当前线程正在使用每个线程的区域设置,则对 POSIX 系统上的 setlocale 的调用对当前线程无效。如果 perl 编译为单线程操作,它不会使用每个线程的函数,因此 setlocale 按预期工作。

如果您已加载 POSIX 模块,则可以使用 perlcall 中给出的方法调用 POSIX::setlocale 来安全地更改或查询区域设置(在可以安全执行此操作的系统上),或者您可以使用新的 5.28 函数 "Perl_setlocale" in perlapi,它是系统 setlocale(3) 的直接替代品,并透明地处理单线程和多线程应用程序。

有一些与区域设置相关的库调用仍然不是线程安全的,因为它们在对所有线程全局的缓冲区中返回数据。过去,这些并不重要,因为区域设置根本不是线程安全的。但现在您必须注意它们,以防您的模块在多线程应用程序中被调用。已知的是

asctime()
ctime()
gcvt() [POSIX.1-2001 only (function removed in POSIX.1-2008)]
getdate()
wcrtomb() if its final argument is NULL
wcsrtombs() if its final argument is NULL
wcstombs()
wctomb()

其中一些不应该在 Perl 应用程序中调用,而另一些则已经实现了这些的线程安全版本

asctime_r()
ctime_r()
Perl_langinfo()

如果您使用以下命令编译代码,则从 Perl 5.28 开始,将自动使用 _r 形式

#define PERL_REENTRANT

另请参阅 "Perl_langinfo" in perlapi。您可以使用 perlcall 中给出的方法,获取这些方法的最佳可用区域设置安全版本

POSIX::localeconv()
POSIX::wcstombs()
POSIX::wctomb()

请注意,Localeconv 返回的一些项目可以通过 "Perl_langinfo" in perlapi 获取。

其他不应该在多线程应用程序中使用。

一些模块可能会调用一个了解区域设置的非 Perl 库。只要它不尝试使用系统 setlocale 查询或更改区域设置,这就可以了。但如果这些确实调用了系统 setlocale,则这些调用可能无效。相反,Perl_setlocale 在所有情况下都有效。在多线程 POSIX 2008 系统上,普通 setlocale 无效。它只对全局区域设置进行操作,而每个线程都有自己的区域设置,而不理会全局区域设置。由于将这些非 Perl 库转换为 Perl_setlocale 不切实际,因此 v5.28 中有一个新函数 switch_to_global_locale,它将切换它被调用的线程,以便任何系统 setlocale 调用都能达到预期效果。在返回到 perl 之前,必须调用函数 sync_locale

这个线程可以随意更改区域设置,但不会影响任何其他线程,除非这些线程也被切换到全局区域设置。这意味着多线程应用程序可以有一个线程使用外部库而不会出现问题;但最多只能有一个线程处于这种状态。否则可能会出现不良后果。

在没有多线程区域设置支持的 Perl 中,一些外部库(例如 Gtk)会更改区域设置。这可能会导致 Perl 内核和其他模块出现问题。对于这些库,在将控制权返回给 Perl 之前,从 v5.20.1 开始,从 XS 调用函数 sync_locale() 应该足以避免大多数这些问题。在此之前,您需要一个纯 Perl 语句来执行此操作

POSIX::setlocale(LC_ALL, POSIX::setlocale(LC_ALL));

或使用 perlcall 中给出的方法。

XS 版本

本文档涵盖了 ExtUtils::ParseXS(也称为 xsubpp)3.51 支持的功能。

作者诊断

从 3.49 版本开始,某些警告默认情况下被禁用。在开发过程中,您可以在环境中或 Makefile.PL 中将 $ENV{AUTHOR_WARNINGS} 设置为 true,或者通过代码将 $ExtUtils::ParseXS::AUTHOR_WARNINGS 设置为 true,或者显式地将 author_warnings=>1 传递给 process_file()。目前,这将启用更严格的别名检查,但将来可能会添加更多警告。这种警告只对 XS 文件的作者有用,并且生成的诊断信息不会包含特定于安装的详细信息,因此它们只对 XS 代码本身的维护者有用。

作者

最初由 Dean Roehrich <[email protected]> 编写。

自 1996 年起由 Perl 维护者 <[email protected]> 维护。