Boost.Python and Boost.Function, II

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);
}
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s