使用Python/C API传递C指针

12 投票
2 回答
12352 浏览
提问于 2025-04-17 08:00

我刚接触Python/C的接口……我想在我的C程序中增加一些新功能,具体来说,就是把Python嵌入到程序里,同时扩展功能,让嵌入的解释器能够执行一个脚本,这个脚本会和我在C程序中写的扩展Python模块进行互动。我的C程序没有全局变量,我希望能保持这种状态;但为了让Python能调用C的功能,似乎扩展的C函数至少需要访问全局变量,以便获取程序的状态。我该怎么解决这个问题呢?

比如说,这是我计划嵌入的方式,其中PYINTERFACE_Initialize是从主函数调用的:

void PYINTERFACE_Initialize(State *ptr, FILE *scriptFile, const char* scriptFileName)
{
    Py_Initialize();
    PyObject *module = Py_InitModule("CInterface", CInterfaceMethods);
    if (PyRun_SimpleFileEx(scriptFile, scriptFileName, 1) != 0)
    {
        printf("PYINTERFACE script execution failed!\n");
    }
    **//ADD State *ptr TO module**      
}

这是扩展的函数:

static PyObject*
CInterface_GetStateInfo(PyObject *self, PyObject *args)
{
    const char *printStr;
    double stateInfo;
    State *ptr;

    if(!PyArg_ParseTuple(args, "s", &printStr))
    {
        return NULL;
    }
    printf("Received %s\n", printStr);

    **//RETRIEVE State *ptr FROM self**      

    stateInfo = ptr->info;
    return Py_BuildValue("d", currentTime);
}

这样传递State *ptr是不是最简单的方法?我确实不想把内部状态暴露给Python。我考虑过使用胶囊,但胶囊似乎并不是为了支持这种行为而设计的。

提前谢谢你们!

2 个回答

6

假设你想要一些向后兼容性,你可以使用 PyCObjects:http://docs.python.org/c-api/cobject.html

如果你只想使用 Python 3,可以选择 PyCapsule:http://docs.python.org/c-api/capsule.html

简单来说,PyCObject 的功能是把你的不透明指针转换成一个 PyObject,这样你就可以在 Python 中随意使用。当你回到 C 语言时,可以把它解开并使用。

PyCapsule 和 PyCObject 很相似,不过它增加了一些功能,主要是它允许你在一个 Capsule 中存储多个指针,基本上就像一个字典。

在你具体的情况下,当你添加指针时,你只需要这样做(错误检查和销毁代码省略):

PyObject *pystate = PyCObject_FromVoidPtr(State, NULL);
PyObject *dict = PyModule_GetDict(module);
PyDict_SetItemString(dict, "CStateObject", pystate);

# To retrieve it from self (assuming that it is an object)
PyObject *pystate = PyObject_GetAttrString(self, "CStateObject");
State *state = (State *)PyCObject_AsVoidPtr(pystate);
12

胶囊基本上是一些在Python中看不见的空指针,你可以把它们传来传去,或者和模块关联起来。它们是解决你问题的“方法”。

这里有个例子,使用一个实例x,这个实例不需要是静态的。首先,把指针附加到你的模块上,像这样(省略了错误检查的部分)……

// wrap the methods to be exposed to python in a module
// i.e. this is a list of method descriptions for the module
static PyMethodDef InitializeTurkeyMethods[] = {

    // this block describes one method.. turkey.do_something()
    {"do_something", 
     turkey_do_something, // fn pointer to wrap (defined below)
     METH_VARARGS, 
     "do something .. return an int."},

    {NULL, NULL, 0, NULL} // sentinel.
};


int init(X * x) { 

    // initialize embedded python scripting .. 
    // (this method a no-op on second or later calls).
    Py_Initialize();

    // initialize the turkey python module 
    PyObject * module = Py_InitModule("turkey", InitializeTurkeyMethods);

    // Create a capsule containing the x pointer 
    PyObject * c_api_object = PyCapsule_New((void *)x, "turkey._X_C_API", NULL);

    // and add it to the module
    PyModule_AddObject(module, "_X_C_API", c_api_object);
}

然后在你想要让Python调用的函数中,为了拿回那个X指针,你需要这样做(这段代码实际上要在你开始引用它之前写):

static PyObject* turkey_do_something(PyObject *self, PyObject *args) {    

    if(!PyArg_ParseTuple(args, ":turkey_do_something"))
        return NULL;

    // get the x pointer back from the capsule
    X * x = (X*)PyCapsule_Import("turkey._X_C_API", 0);    

    // call some fn on x 
    return Py_BuildValue("i", x->some_fn_that_returns_an_int());
}

这里的“turkey._X_C_API”只是一个用于类型检查的名字——在这里放一个对你的应用有意义的名字。Turkey是我刚刚随便起的一个示例模块名。

现在假设你在调用Py_InitModule()时,已经按照某种方式导出了turkey_do_something函数,你可以在Python脚本中这样调用它:

import turkey

print turkey.do_something()

查看这个链接:http://docs.python.org/2/c-api/arg.html,了解如何格式化元组,还有这个…… http://docs.python.org/3.1/c-api/capsule.html,了解胶囊的文档。

撰写回答