python - 在 C 中编写 python 扩展时,如何将 python 函数传递给 C 函数?
问题描述
首先,为令人困惑的标题道歉。
我想要实现的是以下内容:假设我有一些函数foo
,它接受一个函数和一个整数作为输入。例如
int foo(int(*func)(), int i) {
int n = func() + i;
return n;
}
现在,我想将此函数包装在 python 扩展模块中。所以我开始写我的界面:
#include <Python.h>
extern "C" {
static PyObject* foo(PyObject* self, PyObject* args);
}
static PyMethodDef myMethods[] = {
{"foo", foo, METH_VARARGS, "Runs foo"},
{NULL, NULL, 0, NULL}
}
// Define the module
static struct PyModuleDef myModule = {
PyModuleDef_HEAD_INIT,
"myModule",
"A Module",
-1,
myMethods
};
// Initialize the module
PyMODINIT_FUNC PyInit_BSPy(void) {
return PyModule_Create(&myModule);
}
//Include the function
static PyObject* foo(PyObject* self, PyObject* args){
// Declare variable/function pointer
int(*bar)(void);
unsigned int n;
// Parse the input tuple
if (!PyArg_ParseTuple(args, ..., &bar, &n)) {
return NULL;
}
}
现在,当需要解析输入元组时,我感到很困惑,因为我不确定如何解析它。这个想法很简单:我需要能够foo(bar(), n)
在 python 中调用。但我可以使用一些帮助来实现这一点。
解决方案
首先,当您有一个用 C 实现的 Python“扩展方法”,并且该函数接收 Python 可调用对象作为参数时,以下是您接收参数的方式以及调用可调用对象的方式:
/* this code uses only C features */
static PyObject *
foo(PyObject *self, PyObject *args)
{
PyObject *cb;
// Receive a single argument which can be any Python object
// note: the object's reference count is NOT increased (but it's pinned by
// the argument tuple).
if (!PyArg_ParseTuple(args, "O", &cb)) {
return 0;
}
// determine whether the object is in fact callable
if (!PyCallable_Check(cb)) {
PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
return 0;
}
// call it (no arguments supplied)
// there are a whole bunch of other PyObject_Call* functions for when you want
// to supply arguments
PyObject *rv = PyObject_CallObject(cb, 0);
// if calling it returned 0, must return 0 to propagate the exception
if (!rv) return 0;
// otherwise, discard the object returned and return None
Py_CLEAR(rv);
Py_RETURN_NONE;
}
使用这样的逻辑进行包装的问题bsp_init
在于指向 Python 可调用对象的指针是数据指针。如果您将该指针直接传递给bsp_init
,bsp_init
将尝试将数据作为机器代码调用并且它会崩溃。如果bsp_init
通过指向它调用的函数的数据指针传递,您可以使用“胶水”过程解决此问题:
/* this code also uses only C features */
struct bsp_init_glue_args {
PyObject *cb;
PyObject *rv;
};
static void
bsp_init_glue(void *data)
{
struct bsp_init_glue_args *args = data;
args->rv = PyObject_CallObject(args->cb, 0);
}
static PyObject *
foo(PyObject *self, PyObject *args)
{
bsp_init_glue_args ba;
if (!PyArg_ParseTuple(args, "O", &ba.cb)) {
return 0;
}
if (!PyCallable_Check(ba.cb)) {
PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
return 0;
}
bsp_init(bsp_init_glue, (void *)&ba, ...);
if (ba->rv == 0) return 0;
Py_CLEAR(ba->rv);
Py_RETURN_NONE;
}
不幸的是,bsp_init
没有这个签名,所以你不能这样做。但是替代接口BSPLib::Classic::Init
需要 a std::function<void()>
,它是围绕上述模式的面向对象的包装器,因此您可以这样做:
/* this code requires C++11 */
static PyObject *
foo(PyObject *self, PyObject *args)
{
PyObject *cb;
PyObject *rv = 0;
if (!PyArg_ParseTuple(args, "O", &cb)) {
return 0;
}
if (!PyCallable_Check(cb)) {
PyErr_SetString(PyExc_TypeError, "foo: a callable is required");
return 0;
}
std::function<void()> closure = [&]() {
rv = PyObject_CallObject(cb, 0);
};
BSPLib::Classic::Init(closure, ...);
if (rv == 0) return 0;
Py_CLEAR(rv);
Py_RETURN_NONE;
}
这里的魔力在于[&]() { ... }
符号,它是用于定义和创建“捕获”变量的本地类实例的语法糖,cb
因此rv
花括号内的代码将被编译为单独的函数,可以进行通信与foo
适当的。这是一个称为“lambdas”的 C++11 功能,这是一个行话术语,可以追溯到理论 CS 的早期,并被 Lisp 永垂不朽。 这是一个教程,但我不确定它有多好,因为我已经了解了这个概念。
在纯 C 中不可能做到这一点,但也不可能BSPLib::Classic::Init
从纯 C 调用(因为您根本无法std::function
在纯 C 中定义对象......好吧,如果没有对 C++ 标准库进行逆向工程和ABI,无论如何)所以没关系。