In two previous posts we showed how to call C functions from other modules and Python methods from within a Boost.Python module. Here we’ll show that the techniques are easily combined and demonstrate the atexit module which we’ll use to prevent Python interpreter crashes upon exit.
For this post we’ll use the CppDuck.cpp and PyDuck.py from previous posts, changing only the main Bird module.
Most of the code can remain the same, however we’ll need to now change setQuack() to identify whether the passed object is a callable Python method (using PyCallable_Check) or a capsule.
void setQuack(object obj) { if (PyCallable_Check(obj.ptr())) { quacker = obj; } else { quacker = reinterpret_cast<quacker_t*>(PyCapsule_GetPointer(obj.ptr(), "quacker")); } if (PyErr_Occurred()) throw_error_already_set(); }
The remainder of Bird.cpp can be as in Calling Python from C++. Usage is easy too:
>>> import Bird >>> import CppDuck >>> import PyDuck >>> >>> Bird.callQuack() No noise came out. >>> >>> Bird.setQuack(CppDuck.getQuack()) >>> Bird.callQuack() C++ Quack >>> >>> Bird.setQuack(PyDuck.quack) >>> Bird.callQuack() Python Quack
On some versions of Python this may cause the program to crash at exit since Python interpreter may have already shut down before the quacker Boost.Function object is destroyed. We can fix this by defining a simple finalize function:
void finalize() { quacker.clear(); }
And adding to the module:
BOOST_PYTHON_MODULE(Bird) { // ... def("_finalize", &finalize); // The following code executes: // import atexit // atexit.register(_finalize) object atexit = object(handle<>(PyImport_ImportModule("atexit"))); object finalize = scope().attr("_finalize"); atexit.attr("register")(finalize); }
This code ensures that the quacker object is cleared before Python is unloaded, preventing any potential crashes.
The full Bird.cpp program then looks like:
#include <boost/python.hpp> #include <boost/function.hpp> #include <iostream> using namespace boost::python; typedef void (quacker_t)(void); boost::function<quacker_t> quacker; void default_quacker() { std::cout << "No noise came out." << std::endl; } void setQuack(object obj) { if (PyCallable_Check(obj.ptr())) { quacker = obj; } else { quacker = reinterpret_cast<quacker_t*>(PyCapsule_GetPointer(obj.ptr(), "quacker")); } if (PyErr_Occurred()) throw_error_already_set(); } void callQuack() { quacker(); } void finalize() { quacker.clear(); } BOOST_PYTHON_MODULE(Bird) { quacker = &default_quacker; def("setQuack", &setQuack); def("callQuack", &callQuack); // Instead we register the function with the atexit module which // will be called _before_ the Python C-API is unloaded. def("_finalize", &finalize); object atexit = object(handle<>(PyImport_ImportModule("atexit"))); object finalize = scope().attr("_finalize"); atexit.attr("register")(finalize); }
2 thoughts on “Boost.Python and Boost.Function, II”