我应该如何释放在类型映射中为argout结构数组分配的内存?

时间:2017-06-28 16:36:52

标签: python swig

我正在使用SWIG为Python包装一个C库。其中一种C方法需要 指向结构的指针缓冲区以及要填充和填充的元素数 尖头结构。对于我想提供的Python API 元素数量和返回值是填充结构的元组。

  C     : int fill_widgets(widget_t *buffer, int num_widgets);
  Python: fill_widgets(num_widgets) -> (widget, widget,...)

我已经编写了可以按照我想要的方式运行的字体图 - 字体图分配 一个基于Python输入参数的缓冲区,将缓冲区传递给C方法, 将其转换为Python元组,然后返回小部件的元组。但我无法弄清楚 if / when / how我需要释放在我的typemap中分配的内存。

我最初包含了一个freearg typemap来在包装器时释放缓冲区 函数退出,但我相信返回Python的结构仍在使用 物理内存(即内存未被复制,我只是得到一个代理指针 那是使用相同的缓冲区)。我也尝试过设置SWIG_POINTER_OWN标志 在创建代理对象时(通过SWIG_NewPointerObj),但是因为我正在创建 一个指向缓冲区中每个元素的代理指针,释放它们是没有意义的 所有。在这两种情况下,Python最终都会在稍后结束 free()call。

因此,在创建代理时,不要在typemap或SWIG_POINTER_OWN中使用freearg, 当Python元组结构超出范围时,如何释放内存?

这是一个简单的SWIG界面,演示了我的内容:

%module "test"

%typemap (in, numinputs=1) (BUF, NUM){
    $2 = PyInt_AsLong($input);
    $1 = ($1_type)calloc($2, sizeof($*1_type));
}

%typemap (argout) (BUF, NUM){
    PyObject *tpl = PyTuple_New($2);
    for ($2_ltype i=0; i<$2; i++)
    {
        PyTuple_SET_ITEM(tpl, i, SWIG_NewPointerObj(&$1[i], $1_descriptor, 0));
    }
    $result = SWIG_Python_AppendOutput($result, tpl);
}

%typemap (freearg) (BUF, NUM){
    //free($1);
}

%apply (BUF, NUM) {(widget_t *buf, int num_widgets)};

%inline {
typedef struct {int a; int b;} widget_t;

int fill_widgets(widget_t *buf, int num_widgets)
{
    for(int i=0; i<num_widgets; i++)
    {
        buf[i].a = i;
        buf[i].b = 2*i;
    }
    return num_widgets;
}
}

构建/运行的一个例子:

$ swig -python test.i
$ gcc -I/path/to/python2.7 -shared -lpython2.7 test_wrap.c  -o _test.so
$ python
>>> import test
>>> _,widgets = test.fill_widgets(4)
>>> for w in widgets: print w.a, w.b
... 
0 0
1 2
2 4
3 6
>>> 

C:

中fill_widgets的示例用法
int main()
{
    widget_t widgets[10];  // or widget_t *widgets = calloc(10, sizeof(widget_t))
    fill_widgets(widgets, 10);
}

1 个答案:

答案 0 :(得分:1)

使这个有趣的是你有1个缓冲区,但创建了N个Python代理对象,所有这些对象都存在于这个缓冲区中。

假设您不愿意从该缓冲区中复制对象,那么您将获得1:1的Python代理对象映射分配,然后处理原始缓冲区,我们基本上已经找到了一个解决方案。这里的目的是确保每个Python对象也包含对拥有内存的对象的引用。这样做我们可以保持引用计数高,只有在确定没有人可能仍然指向它时才释放内存。

最简单的解决方案是为缓冲区中的第一个对象设置SWIG_POINTER_OWN(即指针实际引用从calloc返回的内存的那个对象),然后让其他所有代理对象不拥有内存,但保留对该内存的引用。

要实现此功能,我们会对您的argout typemap进行两处更改。首先,我们仅为元组的第一个元素设置SWIG_POINTER_OWN。其次,除了第一个项目之外,我们只为PyObject_SetAttrString调用以保持参考。所以最终看起来像这样:

%typemap (argout) (BUF, NUM){
    PyObject *tpl = PyTuple_New($2);
    for ($2_ltype i=0; i<$2; i++)
    {
        PyObject *item = SWIG_NewPointerObj(&$1[i], $1_descriptor, 0==i?SWIG_POINTER_OWN:0);
        if (i) {
            PyObject_SetAttrString(item,"_buffer",PyTuple_GET_ITEM(tpl, 0));
        } 
        PyTuple_SET_ITEM(tpl, i, item);
    }
    $result = SWIG_Python_AppendOutput($result, tpl);
}

我们可以交互式检查引用计数是否符合预期:

In [1]: import test

In [2]: import sys

In [3]: a,b=test.fill_widgets(20)

In [4]: sys.getrefcount(b[0])
Out[4]: 21

In [5]: sys.getrefcount(b[1])
Out[5]: 2

In [6]: b[1]._buffer
Out[6]: <test.widget_t; proxy of <Swig Object of type 'widget_t *' at 0xb2118d10> >

In [7]: b[1]._buffer == b[0]
Out[7]: True

In [8]: x,y,z = b[0:3]

In [9]: del a

In [10]: del b

In [11]: sys.getrefcount(x)
Out[11]: 4

In [12]: sys.getrefcount(y)
Out[12]: 2

In [13]: sys.getrefcount(z)
Out[13]: 2

In [14]: del x

In [15]: sys.getrefcount(y._buffer)
Out[15]: 3
相关问题