c++ - 我怎样才能拥有在 Python C/C++ 扩展之间共享的全局 C/C++ 变量?

标签 c++ python c shared-libraries python-c-extension

我几乎完全用 C++ 编写了一个 Python 包。这样做的原因是因为我想手动包装一个现有的 C++ 库,但这与这里无关。

这个 Python 包由许多不同的扩展模块组成,所有这些模块都是我在“setup.py”脚本中使用 distutils 编译的。这些扩展模块可以相互关联,在这种情况下,我通过将共享库传递给扩展构造函数来链接它们。明确地说,假设我有两个 Python C++ 模块,A 和 B,其中 B 使用 A 中定义的函数。这些通常编译成 A.so 和 B.so。由于 B 使用 A 中定义的函数,我照常编译 A 模块,然后将“:A.so”作为库传递给 B 模块的扩展构造函数中的 libraries 关键字。 (':' 让 g++ 处理库不以通常的 'lib' 前缀开头的事实。)这适用于链接函数和类。

我的问题如下:我在 A 中定义了一些全局 C++ 变量。虽然执行我所描述的操作允许 B 访问 A 中的函数,但它实际上似乎创建了 A 中定义的任何全局数据的 COPY。这是对我来说是一个真正的问题。

在我看来,这个问题本质上类似于在共享库中使用全局变量,如讨论的那样here和其他地方。该解决方案以及我在网上找到的其他解决方案似乎无法解决问题。

如有任何帮助,我们将不胜感激!

编辑:忘了说,是的,我的全局变量声明为 extern。

最佳答案

我想 pythonic 方式是让不同的扩展通过 C API 进行交互。虽然我对 C++ 不是很有经验,但我想它与 C 中的解决方案没有太大区别。我会按以下方式进行:

  • 定义模块A中的全局变量
  • 为模块 A 定义一个 C API,其中包含一个指向全局变量的指针(如果您愿意,还可以包含一个方便的宏)。
  • 加载模块B中的C API并通过指针访问全局变量。

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

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'])],
    )

最后,您将能够从两个模块中读取和修改 x,无论是从 C 级别还是从 python。在 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)

关于c++ - 我怎样才能拥有在 Python C/C++ 扩展之间共享的全局 C/C++ 变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17003492/

相关文章:

c - GDB - 不显示调用命令的结果

c++ - 在 Clion 中包含 mpi.h

c++ - 超出 DirectX11 视频内存

python - 我如何改进欧拉问题 7 的代码

c++ - 函数和非捕获 lambda 之间的区别

python - 左加入但保持一个观察

python - pip3 install Flask-MongoEngine 安装失败

C 程序没有正确读取参数

c++ - C++ 中的 11db 错误; Xcode。

Github 项目上的 C++ 编译错误