我正在写一个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
模块功能XS code使用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
的void指针。 _X版本用于使用pTHX_
宏参数声明的函数。
我是否在正确的轨道上?我应该酌情使用这些宏吗?他们介绍了Perl版本?是否有关于其使用的进一步指导?什么时候触发了析构函数?大概是在与FREETMPS
或LEAVE
宏相关的点上?
答案 0 :(得分:5)
经过进一步的研究,事实证明SAVEDESTRUCTOR
实际上已记录在perlguts而不是perlapi。那里记录了确切的语义。
因此,我认为SAVEDESTRUCTOR
应该被用作"最后"阻止清理,并且足够安全和稳定。
摘自Localizing changes in perlguts,其中讨论了{ local $foo; ... }
块的等价物:
有一种方法可以通过Perl API从C实现类似的任务:创建一个伪块,并安排一些更改在结束时自动撤消,无论是显式的,还是通过非本地出口(通过die())。 block -like构造由一对
ENTER
/LEAVE
宏创建(请参阅Returning a Scalar in 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或更早版本。