Python Capsules are useful for passing C pointers between different Python modules. In particular, one can encapsulate a C function pointer in one module and unpack and call it in another module.
To begin, suppose we have two Boost.Python modules:
- Bird, which has methods setQuack() and callQuack().
- CppDuck, which has a method getQuack().
Our goal will be to get a function pointer with CppDuck.getQuack(), set it in another module in Bird.setQuack(), and finally call it with Bird.callQuack().
First let’s look at the code for CppDuck.cpp:
#include <boost/python.hpp> #include <iostream> using namespace boost::python; void quack() { std::cout << "C++ Quack" << std::endl; } boost::python::object getQuack() { PyObject *result = PyCapsule_New((void*)&quack, "quacker", NULL); return object(boost::python::detail::new_reference(result)); } BOOST_PYTHON_MODULE(CppDuck) { def("getQuack", &getQuack); }
Note that we define a void (void)
function “quack” which we will eventually pass to the Bird module. The method “getQuack” encapsulates it, and returns it as a Boost.Python object. Since PyCapsule_New() returns a new reference, we use boost::python::detail::new_reference
to get the reference counting correct.
After compiling the module, we can try it out:
>>> import CppDuck >>> CppDuck.getQuack() <capsule object "quacker" at 0x100464930>
Let’s move on and see now how we can unpack the function pointer and call it in Bird.cpp:
#include <boost/python.hpp> #include <iostream> using namespace boost::python; typedef void (quacker_t)(void); quacker_t *quacker; void default_quacker() { std::cout << "No noise came out." << std::endl; } void setQuack(object obj) { quacker = reinterpret_cast<quacker_t*>(PyCapsule_GetPointer(obj.ptr(), "quacker")); if (PyErr_Occurred()) throw_error_already_set(); } BOOST_PYTHON_MODULE(Bird) { quacker = &default_quacker; def("setQuack", &setQuack); def("callQuack", quacker); }
Note the following conveniences in the code:
- We create a typedef the function type — in this case void (void) — since we use it in multiple places.
- We set a default quack function when the Python module is initialized. Without this, the Python interpreter will crash if we call callQuack before setting a valid quack.
- PyCapsule_GetPointer gets passed the same string (i.e., “quacker”) as we passed to PyCapsule_New in Bird.cpp.
- We use
boost::python::throw_error_already_set()
to display error messages. For example>>> Bird.setQuack(map) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: PyCapsule_GetPointer called with invalid PyCapsule object
With both modules compiled, we can try it:
>>> import Bird >>> import CppDuck >>> >>> Bird.callQuack() No noise came out. >>> >>> quack = CppDuck.getQuack() >>> Bird.setQuack(quack) >>> >>> Bird.callQuack() No noise came out.
2 thoughts on “Python Capsules”