我在python中包装一个C函数,该函数允许回调到python。我有包装工作,但有一个引用计数问题,我希望有助于理解和修复。 [很少有使用SWIG的语言回调的工作示例我可以找到,所以我很容易出错。]
需要回调的简单C函数func()在cb_test.i中定义。 func()创建一个C数组,并将该数组作为参数传递给回调。包装func()以允许从python调用函数,并且还允许在python中定义回调。执行此操作的简单客户端位于test.py中。该程序的工作方式与此类似[我将示例缩减到最低限度,但它仍然没有'简短']:
我的主要问题是%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)
%}
答案 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经验。不过,我已经验证了当计数减少到零时,对象会被删除,正如预期的那样。]