Invoke C++ from Python without bindings thanks to ROOT


With ROOT, it's possible to use interactively C++ libraries from Python without the need to write bindings. The C++ entities known to the interpreter are exposed to Python transparently. Let's dive into an example.

The following sources include a class, a function and a template.

Header File

In [1]:
%%python
code='''
#include <iostream>
#include <typeinfo>

/// A trivial class
class A {
public:
   A();
   ~A();
};

/// A trivial function
int CountCharacters(const std::string s);

/// A trivial template
template<class T>
class B {
public:
   B()
   {
      std::cout << "The typeid name of the template argument is " << typeid(T).name() << std::endl;
   }
};
'''
with open('myLibrary.h','w') as f_out:
    f_out.write(code)

Implementation

In [2]:
%%python
code='''
#include "myLibrary.h"    
A::A()
{
   std::cout << "This is the constructor of A" << std::endl;
}

A::~A()
{
   std::cout << "This is the destructor of A" << std::endl;
}

int CountCharacters(const std::string s)
{
   return s.size();
}
'''
with open('myLibrary.cc','w') as f_out:
    f_out.write(code)

Creation of the Library

It's trivial to create a shared object starting from the sources above:

In [3]:
%%bash
g++ -o libmyLibrary.so -shared -fPIC myLibrary.cc
In [4]:
%%bash
ls *so
libmyLibrary.so

So far, so good. Now we'll see how easy it is to use this library from within Python thanks to ROOT.

Interactivity without bindings

In order to interact with the C++ entities contained in the library, we need to carry out to tasks:

  1. We need to make known to the interpreter the interfaces. Concretely this means including one or more headers.
  2. We need to make accessible to the interpreter the implementations of such C++ entities. Concretely this means loading the library.

In code:

In [5]:
import ROOT
ROOT.gInterpreter.ProcessLine('#include "myLibrary.h"')
ROOT.gSystem.Load("./libmyLibrary.so")
Welcome to JupyROOT 6.07/07
Out[5]:
0

That's it! We can now start exploring the content of the library. If you are wondering what a return code equal to 0 means, ROOT is telling us that the loading of the library happened without problems!

In [6]:
a = ROOT.A()
This is the constructor of A
In [7]:
del a
This is the destructor of A
In [8]:
b_doublePtr = ROOT.B("double*")()
The typeid name of the template argument is Pd

Notice how the "impedence mismatch" generated by the concept of templates is ironed out in this case. The template parameter is specified as string in parentheses.

In [9]:
ROOT.CountCharacters("This interactivity without bindings is really impressive.")
Out[9]:
57

Interactivity within C++

Interactive usage of C++ libraries is possible also in C++ mode. In this case, no wrapper is interposed between the statement written by the user and the call - ABI compatibility is leveraged. For example:

In [10]:
%%cpp
A a;
This is the constructor of A