I have written a Python package almost completely in C++. The reason for doing so is because I want to manually wrap an existing C++ library, but that is irrelevant here.
This Python package consists of a number of different extension modules, all of which I compile with distutils in a 'setup.py' script. These extension modules can be interrelated, in which case I link them by passing the shared library to the Extension constructor. To be clear, suppose I have two Python C++ modules, A and B, where B uses functions defined in A. These normally compile into A.so and B.so. Since B uses functions defined in A, I compile the A module as usual then I pass ':A.so' as a library to the libraries keyword in the Extension constructor for the B module. (The ':' lets g++ deal with the fact that the library does not start with the usual 'lib' prefix.) This works fine for linking functions and classes.
My problem is as follows: I have defined some global C++ variables in A. While doing what I have described allows B to access functions in A, it actually seems to create a COPY of any global data defined in A. This is a real problem for me.
It seems to me that the issue is essentially similar to having global variables across shared libraries, as discussed here and elsewhere. That solution, and others I have found online, do not seem to solve the problem.
Any help would be very appreciated!
Edit: Forgot to mention that, yes, my global variables are declared as extern.
I guess the pythonic way is to let different extensions interact via a C API. Although I am not very experienced with c++ I guess it is not so much different from the solution in C. I would do it the following way:
Writing C APIs for python C extensions is a bit involved. (Read python docs on Python C API, if your are not familiar with it.) The minimal example of my proposed solution would look like this:
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'])],
)
and finally you will be able to read and modify x
from both modules, either from C level or python. In python this will look like:
>>> import A, B
>>> A.set_x(1)
>>> B.get_x()
1
>>> B.set_x(2)
>>> A.get_x()
2
for access from C level use macros PyA_GET_X()
and PyA_SET_X(x)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With