Calling C++ Classes from Python, with ctypes…


I recently found myself needing to call a C++ class from within Python. I didn’t want to call a separate process (as I had previously when using Thrift – see Using Apache Thrift for Python & C++); but rather to call a C++ library directly.

Before I go on I should say that there are a lot of different ways to do this is Python – and I’ve picked one that worked for me. Other techniques are available – and opinion seems quite divided as to which (if any) technique is best.

To start with we have our C++ class, written in the usual way.

 1#include <iostream>
 2
 3// A simple class with a constuctor and some methods...
 4
 5class Foo
 6{
 7    public:
 8        Foo(int);
 9        void bar();
10        int foobar(int);
11    private:
12        int val;
13};
14
15Foo::Foo(int n)
16{
17    val = n;
18}
19
20void Foo::bar()
21{
22    std::cout << "Value is " << val << std::endl;
23}
24
25int Foo::foobar(int n)
26{
27    return val + n;
28}

Next we need to place a C wrapper around the C++ code – since the ctypes system cannot directly use C++ code… To do this we add the following to the bottom of the file.

1// Define C functions for the C++ class - as ctypes can only talk to C...
2
3extern "C"
4{
5    Foo* Foo_new(int n) {return new Foo(n);}
6    void Foo_bar(Foo* foo) {foo->bar();}
7    int Foo_foobar(Foo* foo, int n) {return foo->foobar(n);}
8}

Note that we need to provide a non-class-based name for each Method we want to call.

We now need to build a lib.so file from our code.

We could do this by hand:

$ g++ -c -fPIC foo.cpp -o foo.o
$ g++ -shared -W1,-soname,libfoo.so -o libfoo.so foo.o

Or we could use CMake.

The following is the CMakeLists.txt to build foo.cpp.

1cmake_minimum_required(VERSION 2.8.9)
2set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
3set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}")
4set(CMAKE_MACOSX_RPATH 1)
5
6project (foo)
7set (SOURCE foo.cpp)
8add_library(foo MODULE ${SOURCE})

Note that I was building on my Mac – hence the MacOS specific line 4. This will work okay on Linux; but it’s not needed.

Now that we’ve written and compiled our C++ – we now need to build a Python wrapper for the Class…

 1import ctypes
 2
 3lib = ctypes.cdll.LoadLibrary('./libfoo.so')
 4
 5class Foo(object):
 6    def __init__(self, val):
 7        lib.Foo_new.argtypes = [ctypes.c_int]
 8        lib.Foo_new.restype = ctypes.c_void_p
 9
10        lib.Foo_bar.argtypes = [ctypes.c_void_p]
11        lib.Foo_bar.restype = ctypes.c_void_p
12
13        lib.Foo_foobar.argtypes = [ctypes.c_void_p, ctypes.c_int]
14        lib.Foo_foobar.restype = ctypes.c_int
15
16        self.obj = lib.Foo_new(val)
17
18    def bar(self):
19        lib.Foo_bar(self.obj)
20    
21    def foobar(self, val):
22        return lib.Foo_foobar(self.obj, val)

Note the requirement to define the argument types, and the type of the return value (even if there isn’t one – e.g. you’re returning void). Without this you’ll get a segmentation fault.

Now that we’ve done everything we need to build the module, we can simply import it from within Python.

For example:

 1from foo import Foo
 2
 3# We'll create a Foo object with a value of 5...
 4f=Foo(5)
 5
 6# Calling f.bar() will print a message including the value...
 7f.bar()
 8
 9# Now we'll use foobar to add a value to that stored in our Foo object, f
10print (f.foobar(7))
11
12# Now we'll do the same thing - but this time demonstrate that it's a normal
13# Python integer...
14
15x = f.foobar(2)
16print (type(x))

The full source code for this simple demo can be found here: https://github.com/Auctoris/ctypes_demo