10

我正在编写一个 XS 模块。我分配一些资源(例如malloc()or SvREFCNT_inc())然后做一些涉及 Perl API 的操作,然后释放资源。这在普通 C 中很好,因为 C 没有异常,但使用 Perl API 的代码可能会croak(),从而防止正常清理和泄漏资源。因此,除了相当简单的情况外,似乎不可能编写正确的 XS 代码。

当我croak()自己时,我可以清理到目前为止分配的任何资源,但我可能会croak()直接调用函数,这会避开我编写的任何清理代码。

伪代码来说明我的担忧:

static void some_other_function(pTHX_ Data* d) {
  ...
  if (perhaps) croak("Could not frobnicate the data");
}

MODULE = Example  PACKAGE = Example

void
xs(UV n)
  CODE:
  {
    /* Allocate resources needed for this function */
    Data* object_graph;
    Newx(object_graph, 1, Data);
    Data_init(object_graph, n);

    /* Call functions which use the Perl API */
    some_other_function(aTHX_ object_graph);

    /* Clean up before returning.
     * Not run if above code croak()s!
     * Can this be put into the XS equivalent of a  "try...finally" block?
     */
    Data_destroy(object_graph);
    Safefree(object_graph);
  }

那么如何安全地清理 XS 代码中的资源呢?如何注册一些在抛出异常或从 XS 代码返回到 Perl 代码时运行的析构函数?

到目前为止我的想法和发现:

  • 我可以创建一个在析构函数中运行必要清理的类,然后创建一个包含此类实例的凡人 SV。在未来的某个时候,Perl 将释放那个 SV 并运行我的析构函数。然而,这似乎相当倒退,必须有更好的方法。

  • XSAWYERX 的XS Fun小册子似乎详细讨论了 DESTROY 方法,但没有讨论源自XS 代码的异常的处理。

  • LEONT 的Scope::OnExit模块具有XS 代码使用SAVEDESTRUCTOR()SAVEDESTRUCTOR_X()宏。这些似乎没有记录。

  • Perl API将其列为公共但未记录的函数save_destructor()save_destructor_x()

  • Perl 的scope.h头文件(由 包含perl.h)声明SAVEDESTRUCTOR(f,p)SAVEDESTRUCTOR_X(f,p)宏,没有任何进一步的解释。从上下文和Scope::OnExit代码来看,f是一个函数指针和p一个将传递给f. pTHX__X 版本适用于使用宏参数声明的函数。

我在正确的轨道上吗?我应该酌情使用这些宏吗?它们是在哪个 Perl 版本中引入的?关于它们的使用是否有任何进一步的指导?究竟什么时候触发析构函数?大概在与FREETMPSorLEAVE宏相关的地方?

4

1 回答 1

5

经过进一步研究,事实证明这SAVEDESTRUCTOR实际上是有记录的——在 perlguts 而不是 perlapi 中。那里记录了确切的语义。

因此,我假设它SAVEDESTRUCTOR应该用作清理的“最终”块,并且足够安全和​​稳定。

摘自Localizing changes in perlguts,其中讨论了{ local $foo; ... }块的等效项:

有一种方法可以通过 Perl API 从 C 中实现类似的任务:创建一个伪块,并安排在其末尾自动撤消一些更改,无论是显式的还是通过非本地退出(通过 die( ))。类似的构造由一对ENTER/LEAVE宏创建(请参阅在 perlcall 中返回标量)。这样的构造可以专门为一些重要的本地化任务创建,或者可以使用现有的(如封闭 Perl 子例程/块的边界,或用于释放 TMP 的现有对)。(在第二种情况下,额外本地化的开销几乎可以忽略不计。)请注意,任何 XSUB 都自动包含在ENTER/LEAVE对中。

在这样的伪块内,可以使用以下服务:

  • […]

  • SAVEDESTRUCTOR(DESTRUCTORFUNC_NOCONTEXT_t f, void *p)

    伪块f结束时,使用唯一的参数调用函数p

  • SAVEDESTRUCTOR_X(DESTRUCTORFUNC_t f, void *p)

    伪块结束时,使用隐式上下文参数(如果有)调用函数f,并且p.

该部分还列出了几个专门的析构函数,例如在某些情况下SAVEFREESV(SV *sv)可能SAVEMORTALIZESV(SV *sv)比过早的更正确。sv_2mortal()

这些宏基本上一直可用,至少 Perl 5.6 或更早版本。

于 2017-08-21T09:51:03.693 回答