I have some C++ code that defines two classes, A and B. B takes an instance of A during construction. I have wrapped A with Boost.Python so that Python can create instances of A, as well as subclasses. I want to do the same with B.
class A {
public:
A(long n, long x, long y) : _n(n), _x(x), _y(y) {};
long get_n() { return _n; }
long get_x() { return _x; }
long get_y() { return _y; }
private:
long _n, _x, _y;
};
class B {
public:
B(A a) : _a(a) {};
doSomething() { ... };
private:
A _a;
};
While wrapping B, I needed to work out how to pass an instance of A to B's constructor. I did some digging and the solution I found was to write a "converter" class:
struct A_from_python_A {
static void * convertible(PyObject* obj_ptr) {
// assume it is, for now...
return obj_ptr;
}
// Convert obj_ptr into an A instance
static void construct(PyObject* obj_ptr,
boost::python::converter::rvalue_from_python_stage1_data* data) {
// extract 'n':
PyObject * n_ptr = PyObject_CallMethod(obj_ptr, (char*)"get_n", (char*)"()");
long n_val = 0;
if (n_ptr == NULL) {
cout << "... an exception occurred (get_n) ..." << endl;
} else {
n_val = PyInt_AsLong(n_ptr);
Py_DECREF(n_ptr);
}
// [snip] - also do the same for x, y
// Grab pointer to memory into which to construct the new A
void* storage = (
(boost::python::converter::rvalue_from_python_storage<A>*)
data)->storage.bytes;
// in-place construct the new A using the data
// extracted from the python object
new (storage) A(n_val, x_val, y_val);
// Stash the memory chunk pointer for later use by boost.python
data->convertible = storage;
}
// register converter functions
A_from_python_A() {
boost::python::converter::registry::push_back(
&convertible,
&construct,
boost::python::type_id<A>());
}
};
Then I register this with:
BOOST_PYTHON_MODULE(interpolation_ext)
{
// register the from-python converter for A
A_from_python_A();
class_<A>("A", init<long, long, long>())
;
class_<B>("B", init<object>())
;
}
Convertible and construct are methods that answer the "is this convertible?" and "how to convert?" questions respectively. I have observed that the construct() method is non-trivial - it has to reach into A's PyObject*, extract all relevant fields, then rebuild a C++ instance that it then passes to B's constructor. Because A contains some private fields, it has to do this via public access mechanisms (whereas with a pure Python object it wouldn't have to, right?). This seems to work.
However, is the field extraction in the 'construct' function really necessary? It seems laborious. If A is a compound object, it could get very complicated, and possibly require one converter to invoke another. I perhaps understand the requirement if A is a Python class, but if the A instance originated from the C++ side, is there a way to determine that this is the case, and then simply get a handle (e.g. pointer) to this 'native' object, as a shortcut?
Here's the associated python code:
from my_ext import A, B
a = A(1,2,3)
b = B(a)
b.doSomething()