我正在编写一个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);
}
那么如何安全地清理XS代码中的资源呢?如何注册一些在抛出异常时运行的析构函数,或者当我从 XS 代码返回到 Perl 代码时?
到目前为止,我的想法和发现:
我可以创建一个在析构函数中运行必要清理的类,然后创建一个包含此类实例的凡人 SV。在未来的某个时候,Perl 将释放该 SV 并运行我的析构函数。然而,这似乎相当倒退,必须有更好的方法。
XSAWYERX的XS Fun小册子似乎详细讨论了DESTROY方法,但没有处理源自XS代码的异常。
LEONT 的
Scope::OnExit
模块具有使用SAVEDESTRUCTOR()
和SAVEDESTRUCTOR_X()
宏的 XS 代码。这些似乎没有记录在案。Perl API 将
save_destructor()
和save_destructor_x()
函数列为公共函数,但未记录。Perl 的
scope.h
标头(包含在perl.h
中(声明了宏SAVEDESTRUCTOR(f,p)
和SAVEDESTRUCTOR_X(f,p)
宏,没有任何进一步的解释。从上下文和Scope::OnExit
代码来看,f
是一个函数指针,p
是一个将传递给f
的 void 指针。_X 版本适用于使用pTHX_
宏参数声明的函数。
我在这方面走在正确的轨道上吗?我应该适当地使用这些宏吗?它们是在哪个 Perl 版本中引入的?是否有关于其使用的进一步指导?析构函数究竟何时触发?大概是在与FREETMPS
或LEAVE
宏相关的点上?
经过进一步的研究,事实证明SAVEDESTRUCTOR
实际上是有记录的——用perlguts而不是perlapi。那里记录了确切的语义。
因此,我假设SAVEDESTRUCTOR
应该用作清理的"最终"块,并且足够安全和稳定。
摘自 本地化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 或更早版本。