需要了解的XS实用程序

在之前的教程中,我们学习了如何编写自己的XS函数,如何处理多个参数,以及如何返回不同的值,包括undef

在本教程中,我将介绍一些在XS编程中常见情况下的实用程序。你已经看到的是SvOK,它可以告诉你一个标量是否已定义。以下是我将要讨论的新内容:

  • 在启动时调度XS代码
  • 处理绑定的变量
  • Unicode工具

在编写XS代码时,你经常会想要了解并知道如何处理以下内容。

模块代码

与之前一样,我们将定义模块代码来加载我们的XS。这是所有需要的内容

package XS::Tutorial::Three;
require XSLoader;

XSLoader::load();
1;

应该保存为lib/XS/Tutorial/Three.pm

XS代码

XS文件的顶部将类似于上一章

#define PERL_NO_GET_CONTEXT // we'll define thread context if necessary (faster)
#include "EXTERN.h"         // globals/constant import locations
#include "perl.h"           // Perl symbols, structures and constants definition
#include "XSUB.h"           // xsubpp functions and macros

MODULE = XS::Tutorial::Three  PACKAGE = XS::Tutorial::Three
PROTOTYPES: ENABLE

请记住,在PROTOTYPES行之后追加任何XS代码。应该保存为lib/XS/Tutorial/Three.xs

在启动时调度XS代码

有时你需要运行一些代码,这样你的XS函数才能工作。例如,libpostal有启动例程,它会填充在库可以使用之前必须调用的数据结构。

你可以用“懒惰”的方式编写这段代码,即在XS函数内部检查初始化代码是否已经运行,如果没有运行,则在执行函数代码的其余部分之前运行它。

但是XS提供了一个使用BOOT关键字来完成此操作的方法。关键字下面的任何C代码都会在启动过程中执行。

BOOT:
printf("We're starting up!\n");

启动部分在关键字之后的第一行空行处结束。

处理绑定的变量

绑定的变量是特殊变量,当与它们交互时将执行自定义代码。但是你永远不会使用它们,为什么要担心呢?问题是如果你正在编写将被他人使用的代码,你无法确定调用者是否不会将一个绑定的变量传递给你的XS函数。而且与常规Perl不同,XS不会自动执行绑定的代码。

尽管如此,XS提供了一些用于处理绑定的变量的函数。你在很多XS代码中都会看到的是SvGETMAGIC。想象一下,如果你的函数接收到了一个绑定的变量;在XS中,它的值将是未定义的,直到你调用mg_get(“魔法获取”)来获取它,这将调用FETCH

不幸的是,mg_get只能用于绑定的标量,所以你不想在常规标量上调用它。这就是SvGETMAGIC的作用:如果标量是绑定的,它将调用mg_get,如果不是,则不会发生任何事情。

以下是它的用法:

SV*
get_tied_value(SV *foo)

PPCODE:
  /* call FETCH() if it's a tied variable to populate the sv */
  SvGETMAGIC(foo);
  PUSHs(sv_2mortal(foo));

此代码声明了一个名为get_tied_value的XS函数,它接受一个标量变量,并对它调用SvGETMAGIC,通过将其推入堆栈来返回值。

魔法?

你可能想知道为什么处理绑定的变量的函数被命名为“魔法”或“mg”。原因是每个变量的绑定行为都是通过指向一个魔法虚拟表的指针实现的,这是一个包含指向绑定行为的函数指针的结构。

通常,Perl C API会提供mg(“魔法”)和nomg(“非魔法”)函数的变体,这样你可以决定是否想要触发绑定行为。

UTF-8工具

Perl拥有大量的工具用于管理UTF-8编码的文本,但使用XS时,你是在C语言中工作,而C语言不支持UTF-8。开始考虑基本类型如char和C代码中的常见假设,你会发现如果处理不当,多字节字符可能会造成破坏。

幸运的是,Perl C API提供了函数来管理UTF-8数据,这可以帮助你。以下是一些示例。

Perl标量有一个UTF-8标志,当标量包含解码的UTF-8数据时,此标志被打开。我们可以使用SvUTF8来检测它。

SV*
is_utf8(SV *foo)
PPCODE:
  /* if the UTF-8 flag is set return 1 "true" */
  if (SvUTF8(foo)) {
    PUSHs(sv_2mortal(newSViv(1)));
  }
  /* else return undef "false" */
  else {
    PUSHs(sv_newmortal());
  }

这声明了一个名为is_utf8的XS函数,它接受一个标量,如果UTF-8标志被设置,则返回true,否则返回false。

想象一下,你有一些只适用于ASCII文本的C代码,即单字节字符。你可以使用SvUTF8检测到UTF-8标志打开的传入标量,但面对那些标志关闭的标量你该怎么办呢?

你可以立即croak,抛出异常。或者你可以尝试将标量降级为非UTF-8,因为字符串可能被标记为UTF-8,但只包含ASCII兼容字符(十进制值0-127)。

SV*
is_downgradeable(SV *foo)
PPCODE:
  /* if the UTF-8 flag is set and the scalar is not downgrade-able return
     undef */
  if (SvUTF8(foo) && !sv_utf8_downgrade(foo, TRUE)) {
    PUSHs(sv_newmortal());
  }
  /* else return 1 */
  else {
    PUSHs(sv_2mortal(newSViv(1)));
  }

此函数返回false,如果标量包含无法降级为ASCII的数据,否则返回true。它是通过使用sv_utf8_downgrade函数实现的,该函数接受标量和布尔值,表示是否可以失败。因为第二个参数是TRUE,所以如果标量不可降级,函数将简单地返回false(否则它将croak)。

参考资料


封面图片© Steve Buissinne

标签

David Farrell

David是一位职业程序员,他经常推文博客关于代码和编程艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请在GitHub上打开一个issue或pull request来帮助我们。