Как я могу иметь глобальную переменную C/C++, которая используется расширениями Python C/C++?

Я почти полностью написал пакет Python на C++. Причина этого в том, что я хочу вручную обернуть существующую библиотеку C++, но здесь это не имеет значения.

Этот пакет Python состоит из нескольких различных модулей расширения, каждый из которых я скомпилировал с помощью distutils в скрипте setup.py. Эти модули расширения могут быть взаимосвязаны, и в этом случае я связываю их, передавая разделяемую библиотеку конструктору расширения. Для ясности предположим, что у меня есть два модуля Python C++, A и B, где B использует функции, определенные в A. Обычно они компилируются в A.so и B.so. Поскольку B использует функции, определенные в A, я компилирую модуль A как обычно, а затем передаю ':A.so' как библиотеку ключевому слову library в конструкторе расширений для модуля B. (':' позволяет g++ учитывать тот факт, что библиотека не начинается с обычного префикса 'lib'.) Это прекрасно работает для связывания функций и классов.

Моя проблема заключается в следующем: я определил некоторые глобальные переменные С++ в A. Выполнение того, что я описал, позволяет B получить доступ к функциям в A, на самом деле кажется, что создается КОПИЯ любых глобальных данных, определенных в A. Это реальная проблема для меня.

Мне кажется, что проблема по существу похожа на наличие глобальных переменных в общих библиотеках, как обсуждалось здесь и в других местах. Это решение и другие, которые я нашел в Интернете, похоже, не решают проблему.

Любая помощь будет очень признательна!

Изменить: забыл упомянуть, что да, мои глобальные переменные объявлены как extern.


person user2333829    schedule 08.06.2013    source источник
comment
mmap?   -  person David Ranieri    schedule 09.06.2013
comment
Может быть... Действительно ли mmap необходим? Я думаю, что у меня есть только один процесс, но я не совсем понимаю, что делает Python.   -  person user2333829    schedule 09.06.2013
comment
Is mmap truly necessary?, я не знаю, это вариант (Python не моя сильная сторона), также с Debian или Red Hat и их производными вы можете использовать /dev/shm   -  person David Ranieri    schedule 09.06.2013
comment
Кто-нибудь еще может помочь? Хотя я ценю предложения до сих пор, я думаю, что смогу сделать это только с правильными флагами компилятора. Я действительно застрял...   -  person user2333829    schedule 11.06.2013


Ответы (2)


Я предполагаю, что питонический способ - позволить различным расширениям взаимодействовать через C API. Хотя я не очень разбираюсь в С++, я думаю, что это не так уж сильно отличается от решения в C. Я бы сделал это следующим образом:

  • определить глобальную переменную в модуле A
  • определите C API для модуля A, который содержит указатель на глобальную переменную (и макрос для удобства, если хотите).
  • загрузите C API в модуль B и получите доступ к глобальной переменной через указатель.

Написание C API для расширений Python C немного сложнее. (Прочтите документацию по 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'])],
    )

и, наконец, вы сможете читать и изменять x из обоих модулей, либо из уровня C, либо из 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).

person dastrobu    schedule 14.08.2013

Если вы можете вызывать из кода Python функции C++, просто создайте функцию, которая будет обращаться к глобальной переменной.

person eyalm    schedule 08.06.2013
comment
Это возможно, но я хочу получить доступ к глобальным переменным C++ в коде C++ для модулей расширения Python. - person user2333829; 09.06.2013