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