python - 嵌入 CPython:如何构造 Python 可调用对象来包装 C 回调指针?
问题描述
假设我将 CPython 解释器嵌入到一个用 C 编写的更大程序中。程序的 C 组件有时需要调用用 Python 编写的函数,将回调函数作为参数提供给它们。
使用 CPython扩展和嵌入API,我如何构造一个包装 C 函数指针的 Python“可调用”对象,以便我可以将该对象传递给 Python 代码并让 Python 代码成功回调到 C 代码中?
注意:这是用户dhanasubbu最初发布的问题的修订版,我回答了该问题,但随后被删除。我认为这实际上是一个很好的问题,所以我将我写的内容转换为我自己对这个问题的陈述的自我回答。欢迎其他答案。
解决方案
要在 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
函数烦恼;只要确保你总是->cfun
在从 C 创建实例后初始化,否则Callback_call
会崩溃。)
现在,假设您需要调用的实际函数是 named real_callback
,而您想要将它传递给的 Python 函数是 named 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 异常和/或从外部上下文接收“关闭”信息,留作练习。
推荐阅读
- java - 未使用 Struts2 日期转换器
- c# - TryCatchFinally 块的意外行为?
- android-studio - Android Studio 3.0.1 模拟器横向模式不起作用
- android - 生成 Pdf 并保存在 Android 中的特定文件夹中
- javascript - 滑入和滑出 div
- server - 谷歌云服务器无法连接到 Anaconda 端口
- javascript - 无法在数据库中插入多个图像
- sql - 查询以获取具有特定产品列表的订单
- javascript - HTML5:将一个画布复制到另一个画布非常慢
- html - 具有 d-flex 和另一种边框颜色的 Bootstrap 4 表