使用python回调的SWIG引用计数错误

时间:2014-08-19 20:09:52

标签: callback swig

我在python中包装一个C函数,该函数允许回调到python。我有包装工作,但有一个引用计数问题,我希望有助于理解和修复。 [很少有使用SWIG的语言回调的工作示例我可以找到,所以我很容易出错。]

需要回调的简单C函数func()在cb_test.i中定义。 func()创建一个C数组,并将该数组作为参数传递给回调。包装func()以允许从python调用函数,并且还允许在python中定义回调。执行此操作的简单客户端位于test.py中。该程序的工作方式与此类似[我将示例缩减到最低限度,但它仍然没有'简短']:

  • func()是从python调用的,带有python回调,my_cb()。
  • func()的%pythoncode包装器创建一个%array_class数组py_vals [],稍后用它来优化C和python回调之间的数据编组。
  • SWIG包装器有一个callback_func typedef的类型映射,它将它映射到cb_func(),它在cb_test.i中定义。 python回调存储在py_cb_func全局中,并由cb_func()调用。
  • C func()是一个简单的函数,它使用方形值对局部数组进行种子处理,允许回调修改它们,并打印新值。
  • cb_func()主要是一个在python中执行my_cb()的蹦床。必须将数组参数从C复制到python数据,并在执行回调后再次反向。我们为此目的使用py_vals [],因为它只分配了一次。
  • my_cb()在python中执行。

我的主要问题是%pythoncode包装器中的明显引用计数问题。我发现有必要在py_vals []指向一个临时变量,以避免python在_wrap_func()中的某个地方崩溃。如果cb_test.use_tmp_hack变量设置为False,则python将崩溃。任何人都可以指出为什么python崩溃,以及如何正确地改变代码'。由于问题是python变量似乎没有超出范围,我有点困惑。

我的第二个问题是,有没有更好的方法在C和python回调之间编组数组?我宁愿只是复制它们之间的数据指针,而不是复制数组。但是,C func()创建并拥有C数组,并且无法更改。

非常感谢

test.py

"""Test for SWIG callback reference count problem
"""

import cb_test

# If False, python crashes from a reference count problem
cb_test.use_tmp_hack = True

""" Callback for func.
    Multiplies a small array (seeded to square values) by a constant factor.
    The C func() prints the modified array.
"""
def my_cb(vals, py_cb_data):
    factor = py_cb_data
    for i in range(cb_test.dims):
        vals[i] = vals[i] * factor
    return True;


# func uses my_cb as the callback function, with 4 as the callback data
cb_test.func(my_cb, 4)

cb_test.i

%module cb_test

/* Wrap callback_func param with cb_func, from where the python callback will be executed */
%typemap(in) callback_func {
  py_cb_func = $input;
  $1 = cb_func;
}

#ifdef SOLUTION
/* This block solves the reference counting problem */
/* The PyObject * fields in cb_data_struct require their python refcnts managed. */
%typemap(memberin) PyObject * {
  Py_DecRef($1);
  $1 = $input;
  Py_IncRef($1);
}
#endif

%inline %{
  #include "stdio.h"

  #define dims 6

  /* Define a C function and callback. We're wrapping both in Python. */
  typedef void (*callback_func) (short Vals[], void *cb_args);

  /* Func seeds a small array with square values, which the callback modifies.
     The modified array is printed to stdout */
  void func(callback_func cb, void *cb_args)
  {
    int i;
    short vals[dims];

    for (i = 0; i < dims; i++)
      vals[i] = i * i;
    cb(vals, cb_args);
    for (i = 0; i < dims; i++)  printf("%d ", vals[i]);  printf("\n");
  }

  /* The cb_data struct for callback_func */
  typedef struct cb_data_struct {
    PyObject *py_vals;
    PyObject *py_cb_data;
  } cb_data_struct;
%}

%{
  static PyObject *py_cb_func;
  static void cb_func(short Vals[], cb_data_struct *cb_data)
  {
    PyObject *py_cb_data;
    short *py_vals;
    unsigned int i;

    /* py_vals must be a ushortArray object */
    SWIG_ConvertPtr(cb_data->py_vals, &py_vals, SWIGTYPE_p_ushortArray, 0);

    /* Copy the C input array to the py_vals Python wrapping object */
    for (i = 0; i < dims; i++)
      py_vals[i] = Vals[i];

    /* Pass python callback data back to python unmodified */
    py_cb_data = cb_data->py_cb_data;

    /* The Python callback will modify the array */
    PyObject_CallFunctionObjArgs(py_cb_func, cb_data->py_vals, py_cb_data, NULL);

    /* Copy the modified py_vals back to the C array */
    for (i = 0; i < dims; i++)
      Vals[i] = py_vals[i];
  }
%}


/* Define a C array type for use in python code */
%include carrays.i
%array_class(unsigned short, ushortArray);

/* Marshal the python objects enabling the C callback_func to execute the Python callback function */
%pythoncode %{
use_tmp_hack = True

def func(cb_func, py_cb_data):
  cb_data = cb_data_struct()

  # If use_tmp_hack is set to False by the client, python crashes; the py_vals
  # array disappears in the setters for py_vals.
  # Using tmp_vals to bump the reference count seems to work around that.
  if use_tmp_hack:
    tmp_vals = ushortArray(dims)
    cb_data.py_vals = tmp_vals
  else:
    cb_data.py_vals = ushortArray(dims)

  cb_data.py_cb_data = py_cb_data
  _cb_test.func(cb_func, cb_data)
%}

1 个答案:

答案 0 :(得分:0)

崩溃的问题在于cb_data_struct的PyObject *成员的setter。我假设Python解释器会以某种方式管理引用计数,但似乎有必要在SWIG中执行此操作。我相信我已经解决了使用这个在成员的setter中使用的typemap。它递减先前值的计数并递增新值。

%typemap(memberin) PyObject * {
  Py_DecRef($1);
  $1 = $input;
  Py_IncRef($1);
}

我已经将这个块添加到问题中的代码中,显然很有希望。

在看了很多之后,我认为在C和Python回调之间有一种更有效的方法来编组数组。

[此解决方案确实带有健康警告,因为我没有SWIG经验。不过,我已经验证了当计数减少到零时,对象会被删除,正如预期的那样。]