假设我将CPython解释器嵌入到用C编写的更大程序中。该程序的C组件有时需要调用用Python编写的函数,并向它们提供回调函数作为参数。
使用CPython extending and embedding API,如何构造包装了C指向函数的Python“可调用”对象,以便可以将该对象传递给Python代码并成功调用Python代码回到C代码中?
注意:这是用户originally posted对问题dhanasubbu的修订版,我回答了该问题,但随后将其删除。我认为这实际上是一个好问题,因此我已将自己写的内容转换为对问题的自我回答。欢迎提供其他答案。
答案 0 :(得分:3)
要定义一种扩展类型,在Python使用它的意义上,它是“可调用的”
术语,则填充类型对象的tp_call
插槽,这与__call__
特殊方法的C等效项。该插槽中的函数将是一个胶水例程,该例程调用实际的C回调。这是最简单的情况的代码,当C回调不接受任何参数且不返回任何内容时。
typedef struct {
PyObject_HEAD
/* Type-specific fields go here. */
void (*cfun)(void); /* or whatever parameters it actually takes */
} CallbackObj;
static PyObject *Callback_call(PyObject *self, PyObject *args, PyObject *kw)
{
/* check that no arguments were passed */
const char no_kwargs[] = { 0 };
if (!PyArg_ParseTupleAndKeywords(args, kw, "", no_kwargs))
return 0;
CallbackObj *cself = (CallbackObj *)self;
cself->cfun();
Py_RETURN_NONE;
}
static PyTypeObject CallbackType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "mymodule.Callback",
.tp_doc = "Callback function passed to foo, bar, and baz.",
.tp_basicsize = sizeof(CallbackObj),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
.tp_call = Callback_call,
};
照常使用PyType_Ready
实例化类型对象。但是,请勿将其放在Python可见的任何模块中,因为Python代码无法正确创建此类型的实例。 (因此,我没有为tp_init
函数烦恼;只需确保在从C创建实例后始终初始化->cfun
,否则Callback_call
会崩溃。)
现在,假设您需要调用的实际函数被命名为real_callback
,而您想要传递给它的Python函数被命名为function_to_call
。首先,通过以下方式创建一个回调对象
像往常一样调用类型对象,并初始化其->cfun
字段:
PyObject *args = PyTuple_New(0);
CallbackObj *cb = (CallbackObj *)PyObject_CallObject(
(PyObject *)CallbackType, args);
Py_DECREF(args);
cb->cfun = real_callback;
然后将cb
放入参数元组,并调用Python函数
像往常一样反对。
args = Py_BuildValue("(O)", cb);
PyObject *ret = PyObject_CallObject(function_to_call, args);
Py_DECREF(args);
Py_DECREF(cb);
// do stuff with ret, here, perhaps
Py_DECREF(ret);
将其扩展到更复杂的情况,其中C回调需要接受参数和/或返回值和/或引发关于错误的Python异常和/或从外部上下文接收“关闭”信息,这是一个练习。
答案 1 :(得分:2)
我很想使用标准库ctypes module,因为它已经包含了C函数指针的适当包装器,并且可以自动处理从Python类型到C类型的转换,以获得各种参数。
我已经在Cython中编写了一个工作示例,因为它是混合Python和C的简便方法,但是它应该显示如何使用这些对象:
cdef extern from *:
"""
int f(int x) {
return x*2;
}
"""
int f(int f)
我定义了一个示例函数f
(在文档字符串中,Cython直接将其合并到编译文件中)。
import ctypes
from libc.stdint cimport intptr_t
def make_f_wrapper():
func_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
cdef intptr_t f_ptr = <intptr_t>&f
return func_type(f_ptr)
这是创建指向f
的ctypes指针的Cython(非常接近Python语法)版本。我定义函数的参数,将f
指针转换为适当大小的整数,然后使用该整数初始化包装对象。
cdef extern from *:
"""
PyObject* make_f_wrapper_c_impl() {
PyObject *ctypes_module = NULL, *CFUNCTYPE = NULL, *c_int = NULL,
*func_type = NULL, *ptr_value = NULL, *result = NULL;
ctypes_module = PyImport_ImportModule("ctypes");
if (ctypes_module == NULL) goto cleanup;
CFUNCTYPE = PyObject_GetAttrString(ctypes_module,"CFUNCTYPE");
if (CFUNCTYPE == NULL) goto cleanup;
c_int = PyObject_GetAttrString(ctypes_module,"c_int");
if (c_int == NULL) goto cleanup;
func_type = PyObject_CallFunctionObjArgs(CFUNCTYPE,c_int,c_int,NULL);
if (func_type == NULL) goto cleanup;
ptr_value = PyLong_FromVoidPtr(&f);
if (ptr_value == NULL) goto cleanup;
result = PyObject_CallFunctionObjArgs(func_type,ptr_value,NULL);
cleanup:
Py_XDECREF(ptr_value);
Py_XDECREF(func_type);
Py_XDECREF(c_int);
Py_XDECREF(CFUNCTYPE);
Py_XDECREF(ctypes_module);
return result;
}
"""
object make_f_wrapper_c_impl()
def make_f_wrapper_c():
return make_f_wrapper_c_impl()
上面的代码是上面Pythony代码的C转换-确实做同样的事情,但是由于使用了C API而更加复杂。它仅通过其Python接口使用ctypes模块。再次通过文档字符串将C代码嵌入到Cython文件中;但是,可以在直接编写的C API模块中使用类似的代码。
(所有这些Cython片段结合在一起,形成了一个长期有效的示例)