如何创建一个在Python C/C++扩展间共享的全局C/C++变量?

5
我已经用C++编写了一个Python包。这样做的原因是我想手动封装一个现有的C++库,但这在这里不相关。
这个Python包由许多不同的扩展模块组成,我都在“setup.py”脚本中使用distutils进行编译。这些扩展模块可以相互关联,如果是这种情况,我会通过将共享库传递给Extension构造函数来链接它们。为了清楚起见,假设我有两个Python C++模块A和B,其中B使用在A中定义的函数。它们通常编译成A.so和B.so。由于B使用在A中定义的函数,我像往常一样编译A模块,然后在B模块的Extension构造函数中将“:A.so”作为库传递给libraries关键字。(“:”让g++处理库没有以通常的“lib”前缀开头的事实。)这对于链接函数和类很好用。
我的问题如下:我在A中定义了一些全局的C++变量。虽然我所描述的操作允许B访问A中的函数,但它实际上似乎创建了A中任何全局数据的副本。这对我来说是一个真正的问题。
在我看来,这个问题本质上类似于跨共享库具有全局变量,如这里和其他地方讨论的那样。我在网上找到的解决方案,包括那个,似乎都不能解决问题。
非常感谢任何帮助!
编辑:忘记提到我的全局变量已声明为extern。

mmap? - David Ranieri
也许... mmap 真的必要吗?我认为我只有一个进程,但我不完全知道 Python 在底层做了什么。 - user2333829
"mmap 是否真的必要?",我不知道,这只是一个选项(Python 不是我的强项),另外在 Debian 或 Red Hat 及其衍生版本中,您可以利用 /dev/shm。 - David Ranieri
还有其他人能帮忙吗?虽然我很感谢到目前为止的建议,但我认为只要使用正确的编译器标志,我应该能够完成这个任务。我真的卡住了... - user2333829
2个回答

2
我认为采用Python的方式是通过 C API 让不同的扩展程序互相交互。虽然我对 c++ 不太熟悉,但我想它与C中的解决方案并没有太大差异。我会按以下方式完成:
  • 在模块A中定义全局变量
  • 为模块A定义C API,其中包含指向全局变量的指针(和一个宏以方便使用)。
  • 在模块B中加载C API,并通过指针访问全局变量。

编写 Python C 扩展程序的C API 有点复杂。(如果您不熟悉它,请阅读 Python 文档的 Python C API 部分。) 我提出的解决方案的最小示例如下所示:

A.h

/* Header file for A module */

#ifndef A_MODULE_H
#define A_MODULE_H
#ifdef __cplusplus
extern "C" {
#endif

#define PyA_GET_X() (*x_ptr)
#define PyA_SET_X(x) (*x_ptr = x)

#ifdef A_MODULE
/* do nothing for this minimal example */
#else
static void **PyA_API;
#define x_ptr ((long *)PyA_API[0])

static int import_A(void)
{
    PyA_API = (void **)PyCapsule_Import("A._C_API", 0);
    return (PyA_API != NULL) ? 0 : -1;
}
#endif /* !defined(A_MODULE) */
#ifdef __cplusplus
}
#endif
#endif /* !defined(A_MODULE_H) */

A.c

#include <Python.h>
#define A_MODULE
#include "A.h"

long x = 0; /* here is the global variable */

static PyObject* 
set_x(PyObject *self, PyObject *args){
    if (!PyArg_ParseTuple(args, "l", &x)) return NULL;
    Py_RETURN_NONE;
}

static PyObject* 
get_x(PyObject *self, PyObject *args){
    return PyInt_FromLong(x);
}

static PyMethodDef methods[] = {
    {"set_x", (PyCFunction)set_x, METH_VARARGS, ""},
    {"get_x", (PyCFunction)get_x, METH_NOARGS, ""},
    {NULL, NULL, 0, NULL}
    };

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC initA(void){
    PyObject *m = Py_InitModule3("A", methods, "");
    static void *PyA_API[1];
    PyA_API[0] = (void *)&x;

    PyObject *c_api_object = PyCapsule_New((void *)PyA_API, "A._C_API", NULL);
    if (c_api_object != NULL) PyModule_AddObject(m, "_C_API", c_api_object);
}

B.c

#include <Python.h>
#define B_MODULE
#include "A.h"

static PyObject* 
set_x(PyObject *self, PyObject *args){
    long y;
    if (!PyArg_ParseTuple(args, "l", &y)) return NULL;
    PyA_SET_X(y);
    Py_RETURN_NONE;
}

static PyObject* 
get_x(PyObject *self, PyObject *args){
    return PyInt_FromLong(PyA_GET_X());
}

static PyMethodDef methods[] = {
    {"set_x", (PyCFunction)set_x, METH_VARARGS, ""},
    {"get_x", (PyCFunction)get_x, METH_NOARGS, ""},
    {NULL, NULL, 0, NULL}
    };

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC initB(void){
    import_A();
    Py_InitModule3("B", methods, "");

}

setup.py

from numpy.distutils.core import setup, Extension

setup(
    name="AB",
    ext_modules = [Extension('A', ['A.c']), Extension('B', ['B.c'])],
    )

最终你将能够在C级别和Python中读取和修改x。在Python中,代码如下:

>>> import A, B
>>> A.set_x(1)
>>> B.get_x()
    1
>>> B.set_x(2)
>>> A.get_x()
    2

要从C级别访问,请使用宏PyA_GET_X()PyA_SET_X(x)


0
如果你可以从Python代码调用C++函数,只需创建一个函数来访问全局变量即可。

这是有可能的,但我想在Python扩展模块的C++代码中访问C++全局变量。 - user2333829

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