SWIG 中的垃圾收集和自定义 getter
Garbage collection and custom getter in SWIG
我不是作者,但我使用的 public 软件包似乎正在泄漏内存 (Github issue)。我想弄清楚如何修补它以使其正常工作。
为了缩小问题范围,有一个结构,称之为 xxx_t
。首先 %extend
用于使结构的成员在 Python:
中可用
%extend xxx_t {
char *surface;
}
还有一个习俗getter。它在这里究竟做了什么并不重要,除了它使用 new
来创建一个 char*
。
%{
char* xxx_t_surface_get(xxx *n) {
char *s = new char [n->length + 1];
memcpy (s, n->surface, n->length);
s[n->length] = '[=12=]';
return s;
}
%}
目前代码中有这一行来处理垃圾回收:
%newobject surface;
这似乎没有按预期工作。 %newobject xxx_t::surface;
也不行。如果我用 %newobject xxx_t_surface_get;
替换它,那将不起作用,因为 getter 函数被转义(在 %{ ... %}
内)。
告诉 SWIG 关于 char*
以便释放它的正确方法是什么?
在开始之前值得指出一件事:因为你 return char*
它最终使用 SWIG 的普通字符串类型映射来生成 Python 字符串。
话虽如此,让我们了解一下当前生成的代码是什么样的。我们可以使用以下 SWIG 接口定义开始我们的调查以进行试验:
%module test
%inline %{
struct foobar {
};
%}
%extend foobar {
char *surface;
}
如果我们通过 SWIG 运行 像这样的东西,我们将看到一个生成的函数,它包装了你的 _surface_get
代码,像这样:
SWIGINTERN PyObject *_wrap_foobar_surface_get(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
foobar *arg1 = (foobar *) 0 ;
void *argp1 = 0 ;
int res1 = 0 ;
PyObject * obj0 = 0 ;
char *result = 0 ;
if (!PyArg_ParseTuple(args,(char *)"O:foobar_surface_get",&obj0)) SWIG_fail;
res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_foobar, 0 | 0 );
if (!SWIG_IsOK(res1)) {
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "foobar_surface_get" "', argument " "1"" of type '" "foobar *""'");
}
arg1 = reinterpret_cast< foobar * >(argp1);
result = (char *)foobar_surface_get(arg1);
resultobj = SWIG_FromCharPtr((const char *)result);
/* result is never used again from here onwards */
return resultobj;
fail:
return NULL;
}
但是这里要注意的是,当这个包装器 return 时,调用您的 getter 的结果会丢失。也就是说,它甚至与 returned.
的 Python 字符串对象的生命周期无关
因此我们可以通过多种方式解决此问题:
- 一个选项是确保生成的包装器在
SWIG_FromCharPtr
发生后根据调用 getter 的结果调用 delete[]
。这正是 %newobject
在这种情况下所做的。 (见下文)。
- 另一种选择是在调用之间保留分配的缓冲区,可能在某些线程本地存储中并跟踪大小以最小化分配
- 或者,我们可以使用某种基于 RAII 的对象来拥有临时缓冲区并确保它被删除。 (如果我们愿意,我们可以用
operator void*
做一些巧妙的事情)。
如果我们更改界面以添加 %newobject
,如下所示:
%module test
%inline %{
struct foobar {
};
%}
%newobject surface;
%extend foobar {
char *surface;
}
然后我们看到我们生成的代码现在看起来像这样:
// ....
result = (char *)foobar_surface_get(arg1);
resultobj = SWIG_FromCharPtr((const char *)result);
delete[] result;
我们也可以在 github 的真实代码中看到这一点,所以这不是您要查找的错误。
通常对于 C++,我倾向于使用 RAII 选项。碰巧从 SWIG 和 C++ 的角度来看,有一种巧妙的方法可以做到这一点:std::string
。因此,我们可以通过执行以下操作以简单干净的方式修复泄漏:
%include <std_string.i> /* If you don't already have this... */
%extend xxx_t {
std::string surface;
}
%{
std::string xxx_t_surface_get(xxx *n) {
return std::string(n->surface, n->length);
}
%}
(你也需要改变 setter 来匹配,除非你把它设为 const 所以没有 setter)
尽管如此,它仍在为同一输出进行两组分配。首先,std::string
对象进行一次分配,然后对 Python 字符串对象进行一次分配。这就是缓冲区已经存在于 C++ 中的所有内容。因此,虽然此更改足以解决泄漏问题并且是正确的,但您还可以更进一步,编写一个减少复制的版本:
%extend xxx_t {
PyObject *surface;
}
%{
PyObject *xxx_t_surface_get(xxx *n) {
return SWIG_FromCharPtrAndSize(n->surface, n->length);
}
%}
我不是作者,但我使用的 public 软件包似乎正在泄漏内存 (Github issue)。我想弄清楚如何修补它以使其正常工作。
为了缩小问题范围,有一个结构,称之为 xxx_t
。首先 %extend
用于使结构的成员在 Python:
%extend xxx_t {
char *surface;
}
还有一个习俗getter。它在这里究竟做了什么并不重要,除了它使用 new
来创建一个 char*
。
%{
char* xxx_t_surface_get(xxx *n) {
char *s = new char [n->length + 1];
memcpy (s, n->surface, n->length);
s[n->length] = '[=12=]';
return s;
}
%}
目前代码中有这一行来处理垃圾回收:
%newobject surface;
这似乎没有按预期工作。 %newobject xxx_t::surface;
也不行。如果我用 %newobject xxx_t_surface_get;
替换它,那将不起作用,因为 getter 函数被转义(在 %{ ... %}
内)。
告诉 SWIG 关于 char*
以便释放它的正确方法是什么?
在开始之前值得指出一件事:因为你 return char*
它最终使用 SWIG 的普通字符串类型映射来生成 Python 字符串。
话虽如此,让我们了解一下当前生成的代码是什么样的。我们可以使用以下 SWIG 接口定义开始我们的调查以进行试验:
%module test
%inline %{
struct foobar {
};
%}
%extend foobar {
char *surface;
}
如果我们通过 SWIG 运行 像这样的东西,我们将看到一个生成的函数,它包装了你的 _surface_get
代码,像这样:
SWIGINTERN PyObject *_wrap_foobar_surface_get(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
foobar *arg1 = (foobar *) 0 ;
void *argp1 = 0 ;
int res1 = 0 ;
PyObject * obj0 = 0 ;
char *result = 0 ;
if (!PyArg_ParseTuple(args,(char *)"O:foobar_surface_get",&obj0)) SWIG_fail;
res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_foobar, 0 | 0 );
if (!SWIG_IsOK(res1)) {
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "foobar_surface_get" "', argument " "1"" of type '" "foobar *""'");
}
arg1 = reinterpret_cast< foobar * >(argp1);
result = (char *)foobar_surface_get(arg1);
resultobj = SWIG_FromCharPtr((const char *)result);
/* result is never used again from here onwards */
return resultobj;
fail:
return NULL;
}
但是这里要注意的是,当这个包装器 return 时,调用您的 getter 的结果会丢失。也就是说,它甚至与 returned.
的 Python 字符串对象的生命周期无关因此我们可以通过多种方式解决此问题:
- 一个选项是确保生成的包装器在
SWIG_FromCharPtr
发生后根据调用 getter 的结果调用delete[]
。这正是%newobject
在这种情况下所做的。 (见下文)。 - 另一种选择是在调用之间保留分配的缓冲区,可能在某些线程本地存储中并跟踪大小以最小化分配
- 或者,我们可以使用某种基于 RAII 的对象来拥有临时缓冲区并确保它被删除。 (如果我们愿意,我们可以用
operator void*
做一些巧妙的事情)。
如果我们更改界面以添加 %newobject
,如下所示:
%module test
%inline %{
struct foobar {
};
%}
%newobject surface;
%extend foobar {
char *surface;
}
然后我们看到我们生成的代码现在看起来像这样:
// ....
result = (char *)foobar_surface_get(arg1);
resultobj = SWIG_FromCharPtr((const char *)result);
delete[] result;
我们也可以在 github 的真实代码中看到这一点,所以这不是您要查找的错误。
通常对于 C++,我倾向于使用 RAII 选项。碰巧从 SWIG 和 C++ 的角度来看,有一种巧妙的方法可以做到这一点:std::string
。因此,我们可以通过执行以下操作以简单干净的方式修复泄漏:
%include <std_string.i> /* If you don't already have this... */
%extend xxx_t {
std::string surface;
}
%{
std::string xxx_t_surface_get(xxx *n) {
return std::string(n->surface, n->length);
}
%}
(你也需要改变 setter 来匹配,除非你把它设为 const 所以没有 setter)
尽管如此,它仍在为同一输出进行两组分配。首先,std::string
对象进行一次分配,然后对 Python 字符串对象进行一次分配。这就是缓冲区已经存在于 C++ 中的所有内容。因此,虽然此更改足以解决泄漏问题并且是正确的,但您还可以更进一步,编写一个减少复制的版本:
%extend xxx_t {
PyObject *surface;
}
%{
PyObject *xxx_t_surface_get(xxx *n) {
return SWIG_FromCharPtrAndSize(n->surface, n->length);
}
%}