安全地释放XS代码中的资源(在范围退出时运行析构函数)

amo*_*mon 9 c perl destructor perl-xs xs

我正在写一个XS模块.我分配了一些资源(例如malloc()或者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);
  }
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • XSAWYERX的XS乐趣小册子似乎讨论DESTROY连篇累牍的方法,但并非源于异常的处理 XS码.

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

  • Perl的API列表save_destructor()save_destructor_x()功能公开,但没有证件.

  • 声明和宏,Perl的scope.h标题(包含perl.h),没有任何进一步的解释.从上下文和代码判断,是一个函数指针和一个将被传递给的void指针._X版本用于使用宏参数声明的函数.SAVEDESTRUCTOR(f,p)SAVEDESTRUCTOR_X(f,p)Scope::OnExitfpfpTHX_

我是否在正确的轨道上?我应该酌情使用这些宏吗?他们介绍了Perl版本?是否有关于其使用的进一步指导?什么时候触发了析构函数?据推测,在涉及到一个点FREETMPSLEAVE宏?

amo*_*mon 5

经过进一步的研究,事实证明,SAVEDESTRUCTOR事实证明 - 在perlguts而不是perlapi.那里记录了确切的语义.

因此,我认为它SAVEDESTRUCTOR应该被用作清理的"最终"块,并且足够安全和​​稳定.

摘自perlguts中的本地化更改,讨论了{ local $foo; ... }块的等价物:

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

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

  • [...]

  • 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或更早版本.