使用C语言嵌入Python

6
我希望在C应用程序中使用一个基于事件的Python库。我使用官方的C-API来嵌入Python:http://docs.python.org/2/c-api/index.html#c-api-index 调用Python方法并收集返回值没有问题。然而,我不知道如何做到以下几点:
一些Python库函数将Python函数指针作为参数传递。
在从C调用这些方法时,是否可以传递C函数指针,以便Python函数使用C函数作为回调?
如果不行,如何让Python使用C回调函数?

请查看https://dev59.com/QGw15IYBdhLWcg3wbLHU(需要合并;没有mod点)。 - ecatmur
1
@ecatmur 这个链接的答案是错误的。methd必须是静态分配的,因为PyCFunction保留了对提供的PyMethodDef的指针,并在稍后使用它来检索C函数和标志。答案中的代码自动分配了PyMethodDef,这将导致调用生成的Python函数时崩溃。 - user4815162342
@user4815162342:在链接的答案中,我认为Manux只是展示了整个过程的概述。在我看来,Manux使用函数名作为模块名而不是NULL有点傻。在实际代码中,我假设PyMethodDef是在堆上分配的。 - Eryk Sun
也许回答应该澄清一下,即需要在堆上分配PyMethodDef。这引出了后续问题,即动态分配的PyMethodDef何时以及如何释放。我的答案,在一些初始投资的基础上,可以很好地解决这个问题。 - user4815162342
嗨,我在这篇文章下提供了一个不错的例子 https://stackoverflow.com/a/46441794/5842403 - Joniale
1个回答

10

这比人们预想的要困难,但是它可以完成。

如果您有一个希望作为回调提供的单个C函数,您可以使用PyCFunction_New将其转换为Python可调用函数:

#include <python.h>

static PyObject *my_callback(PyObject *ignore, PyObject *args)
{
  /* ... */
}

static struct PyMethodDef callback_descr = {
  "function_name",
  (PyCFunction) my_callback,
  METH_VARARGS,                 /* or METH_O, METH_NOARGS, etc. */
  NULL
};

static PyObject *py_callback;

...
py_callback = PyCFunction_New(&callback_descr, NULL);

如果您想在运行时选择不同的回调函数,例如提供一个通用的c_to_python函数将C回调函数转换为Python回调函数,则此方法将无法起作用。 在这种情况下,您需要实现一个带有自己的tp_call的扩展类型。

typedef struct {
  PyObject_HEAD
  static PyObject (*callback)(PyObject *, PyObject *);
} CallbackObject;

static PyObject *
callback_call(CallbackObject *self, PyObject *args, PyObject *kwds)
{
  return self->callback(args, kwds);
}

static PyTypeObject CallbackType = {
    PyObject_HEAD_INIT(NULL)
    0,                          /*ob_size*/
    "Callback",                 /*tp_name*/
    sizeof(CallbackObject),     /*tp_basicsize*/
    0,                          /*tp_itemsize*/
    0,                          /*tp_dealloc*/
    0,                          /*tp_print*/
    0,                          /*tp_getattr*/
    0,                          /*tp_setattr*/
    0,                          /*tp_compare*/
    0,                          /*tp_repr*/
    0,                          /*tp_as_number*/
    0,                          /*tp_as_sequence*/
    0,                          /*tp_as_mapping*/
    0,                          /*tp_hash */
    (ternaryfunc) callback_call, /*tp_call*/
    0,                          /*tp_str*/
    0,                          /*tp_getattro*/
    0,                          /*tp_setattro*/
    0,                          /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
};

PyObject *
c_to_python(PyObject (*callback)(PyObject *, PyObject *))
{
  CallbackObject *pycallback = PyObject_New(CallbackObject, &CallbackType);
  if (pycallback)
    pycallback->callback = callback;
  return pycallback;
}

这段代码可以轻松地进行扩展,以便还能够接受一个user_data指针;只需将用户数据存储在CallbackObject结构体中即可。


快速查看一下,似乎boost::python就是这样包装C函数的。 - neodelphi
@neodelphi 这很有道理,但问题标记为C,所以boost::python并不适用。顺便问一下,boost::python是否得到积极维护?当我查看文档时,它似乎相当陈旧。 - user4815162342
你说得对。我之所以找到这篇文章,是因为我正在尝试实现一个类似于boost::python的库,因为它已经很多年没有更新了。我没有找到其他的解决方案,而且我看到boost::python是这样做的,这似乎是正确的方法。 - neodelphi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接