Finally Figured Out boost::python
After lots of experimentation and two previous failed attempts, Glom now uses boost::python, with very little use of the nasty Python C API remaining. This should make it easier to add Python API to easily access and set field/record/table/database details from the Python that’s used in calculated fields or button scripts.
Useful Things I Now Know about boost::python
boost::python is no fun to get started with, but it’s now far easier for me to use than the Python C API.
I’ve mentioned the awful boost::python documentation before. Here are some essential things that I figured out, which are not really documented. This is thanks to helpful people on the boost::python mailing list. Corrections welcome – there’s so much here that some of it must be wrong.
None of this is very obvious or pleasant. If anyone had their first real C++ experience with boost::python then I’d forgive them for being put off C++ for good. I love C++ so that would be unfortunate.
“Converting” between C and C++ object types
C++ to C: To get the underlying PyObject* from a boost::python::object (awful docs), when you need to use a C function:
PyObject* cobject = theobject.ptr();
To test for a boost::python::object with a null underlying PyObject*, do:
if(cppobject.ptr())
Do not do if(cppobject). That tests if the python object is actually a boolean that is PyTrue.
C to C++: To get a boost::python::object for a PyObject*, when you received one from a C function, but you then need to use the result in a C++ function, or just want the improved memory management:
- If the C function gave you a reference that you should later unreference:
boost::python::object cppobject( (boost::python::handle<>(cobject)) );
(You need those extra brackets, for “interesting” compiler reasons.)
- Or, if you need to take a reference.
boost::python::object cppobject(boost::python::borrowed(cobject));
(Yes. that’s horrible too. I see no reason to expose boost::python::handle in the API.)
- However, you’ll need to use allow_null too to avoid exceptions if the PyObject* might be null. Well, any pointer could be null, so say hello to:
boost::python::object cppobject(Â (boost::python::handle<>(boost::python::allow_null(cobject))) );
or
boost::python::object(boost::python::borrowed(boost::python::allow_null(cobject)));
(Shoot me now. No, reducing it to b::p::whatever is not a significant improvement.)
I understand that a null PyObject* may sometimes be an exceptional unexpected event, but forcing the use of a try/catch by default just for a null pointer check is annoying. Explicit functions such as wrap() and wrap_not_null() would be so much easier.
See the equivalent for gtkmm (plus calling reference() when necessary with non-widgets).
Using boost::python with your own wrapped C++ classes.
Others
- To get a C++ value out of a boost::python::object do, for instance:
boost::python::extract<std::string> extractor(cppobject);
if(extractor.check()) mystring = extractor;
You can do
mystring = boost::python::extract<std::string>(cppobject)
without the check() but that will throw an exception if the underlying type is not really what you expect.
- boost::python likes to throw exceptions. I think it only ever throws boost::python::error_already_set, though the (Python) error is often not already set when it’s thrown. When the error is set, you’ll need to use Python C API to discover what it is.
- To provide [] syntax in python for your wrapped class, you’ll need to know how the C API works. Add this voodoo to your boot::python::class declaration:
.def("__getitem__", &MyClass::getitem)
.def("__len__", &MyClass::len)
Those methods can then have signatures like this:
boost::python::object getitem(const boost::python::object& cppitem);
long len() const;
- To use Python date or time values, you will need to use C Python functions. For instance:
PyObject* cobject = cppobject.ptr();
int day = PyDateTime_GET_DAY(cobject);
int month = PyDateTime_GET_MONTH(cobject);
int year = PyDateTime_GET_YEAR(cobject) );
Boost has no .pc files
boost is a complete pain as a dependency. I understand that they don’t want to freeze API or ABI, because it’s a place for gradually improving API, though I think they should just have regular stable/devel phases with parallel installs. But I can’t forgive how difficult it is to get the header and linker options to use boost libraries. There are some m4 macros out there but they are hacky and fragile, and don’t actually work for boost::python. It shouldn’t be hard to provide pkg-config .pc files, so you wouldn’t need to do any compilation or linker checks in configure at all. I hacked some m4 code together based on some existing stuff, but I couldn’t recommend it.
So distro packagers won’t enjoy this new dependency. Sorry.