Call C++ From Python Using Boost
Overview
Many times I have come across the same problem: I want to test an algorithm that is written in C/C++ and be able to prod/plot/or otherwise review algorithm outputs. I have never been a fan of gnuplot, so writing code to plot program outputs directly from C/C++ is out. Likewise, the effort needed to translate the algorithm from C/C++ to python can be significant, so this doesn’t work in practice, either. Enter a third option: wrap that code and call it from python!
There are a few options for doing this, but the big two are SWIG and Boost. In this post, I will talk about the second option. So, without further ado, let’s get to it!
What is Boost?
Boost is a library project written in C++ that incorporates a very wide array of functionality; from geometric modeling, to memory management, to, you guessed it, wrapping C++ code in python. I won’t spend much time talking about the Boost project in its entirety; I refer interested readers to the Boost homepage. What I will focus on in the remainder of this post, is how to expose a simplified API written in C++ to python. In less jargony prose: This post will show you how to call C++ from python.
Prerequisites
To follow along with the remainder of this post on your own, I invite you to checkout the GitHub repo and build the project. Pay particular attention to the README.md in the repo, as it explains all of the necessary steps to build and test the project.
What is going on in the repo?
I’ll assume that, because of my awesome documentation skills, you were able to build and run the project targets . I’ll briefly describe what the source files in this repo are trying to do. In what follows, project_dir
will refer to the top-level project directory.
The C++ API
In project_dir/include
, there is class definition file called, unsurprisingly, class_def.hpp
:
#pragma once
//! # defines
//! c/c++ system headers
#include <cmath>
//! other headers
//!(this is where you might put references to the cpp code
//! you would like to call from python)
class Exponentiate {
public:
// if no base value is provided in the constructor, use "e"
Exponentiate() { base_ = M_E; }
// constructor takes the base as an argument
Exponentiate(double base) { base_ = base; }
// destructor
~Exponentiate() {}
// this class has a single method that raises a base to a power
double RaiseToPower(double in) { return pow(base_, in); }
private:
// class has one private member, the base to raise to some user-input
// power
double base_;
};
With this file, I intended to create a simplified class that embodies interesting C++ features, namely
- multiple constructors
- external library calls (in this case, to the
cmath::pow
method)
From this simple class defintion, it is easy to see that Exponentiate
has one method, and that method raises some base, which can be defaulted to e ~ 2.71828
or set via constructor, to some power that is input via external call. To test how instances of this class function in an executable, I included test_cpp_rtp
. This executable is built alongside the python package. I will discuss the python package in a bit. For now, let’s experiment with test_cpp_rtp
to see how the API works. I suggest you walk through the source file project_dir/test/main.cpp
so that you understand what it does. There are three acceptable input schemes, and I will discuss them now:
0 inputs
./test_cpp_rtp
No inputs entered, defaulting to base = e and power = 1.
Final answer: base^power = 2.71828.
By default, when neither a base nor a power is input on the command line, the default constructor is called, which sets the base equal to e
, and the power is set to 1.
1 input
./test_cpp_rtp 2
User entered power = 2. Using e as base.
Final answer: base^power = 7.38906
2 inputs
./test_cpp_rtp 2 3
User entered base = 2 and power = 3
Final answer: base^power = 8
Ok great, we have a simple class that can take in a base and raise it to some power. Now what?
Enter Boost
Now we get to the fun part: wrapping the code using Boost. I will walk through the source file that accomplishes this, chunk-by-chunk.
//! c/c++ system headers
//! other headers
#include <class_def.hpp>
#include <boost/python.hpp>
Here, I am just getting the class definition, where the class Exponentiate
is defined, and the Boost header files.
// create class that will wrap our c++ code into python using boost
struct BoostPyWrapper {
// we can expose either constructor from class_def, as well
// as both. Let's expose both. This just means that we can init
// python version using either an input argument or not.
BoostPyWrapper() : e_(new Exponentiate()) {}
BoostPyWrapper(double base) : e_(new Exponentiate(base)) {}
// create a python-callable method to raise base to an input power
double raise_to_power(double in) {
return e_->RaiseToPower(in);
}
// create a shared pointer to our Exponentiate instance
boost::shared_ptr<Exponentiate> e_;
};
This chunk creates a wrapper class whose sole member is a shared pointer to an instance of Exponentiate
. This instance is initialized by either the default or the input constructor. Conceivably, the choice of constructor will be made by an init
method called from python, but how can we accomplish this?
// define boost python module
BOOST_PYTHON_MODULE(pyRTP) {
using namespace boost::python;
// this is where the magic happens
// here is where we define what is actually exposed to python
// and how to reference it
class_<BoostPyWrapper>("Exponentiate", init<>()) // default constructor
.def(init<double>()) // constructor that takes a single argument
.def("raise_to_power", &BoostPyWrapper::raise_to_power, "perform the computation"); // ref to our single method
}
This final chunk is where the Boost-under-the-hood magic is used. Using Boost, we are able to create a python module with the macro BOOST_PYTHON_MODULE
(make sure that the name matches the name of the shared object library built in cmake!!). We define a single python object, called Exponentiate
, that is a module within pyRTP
. This object can be initialized with no arguments (this will invoke the default constructor Exponentiate::Exponentiate()
) or with a double
(the constructor that takes a double as an argument). These two initialization methods are accomplished with the init<>
and init<double>
definitions. Finally, we expose Exponentiate::RaiseToPower
by passing a reference to BoostPyWrapper::raise_to_power
, which is essentially a passthrough. As far as the Boost part of the project goes, that’s it. Pretty cool, right?
Does it work?
This is perhaps the most important question of all. I have included a test file that provides a few unit tests that you can run to verify that the wrapping process worked. In this case, the python library numpy
has a power function that can be used to test if the wrapped code gives the correct outputs. In practice, you probably won’t have such a simple unit test procedure, but thankfully we do in this case.
Parting thoughts
In this tutorial, I provided a procedure for wrapping C++ code using the Boost libraries. I tried to give a simple example that could be easily extended to more complicated APIs. I wanted to put this together because many times in the past I have had occasion to port C++ into python for a variety of reasons and I really wished that I had found a tutorial similar to this one. I hope that you found it useful. Cheers!