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.

#include <iostream>

// A simple class with a constuctor and some methods...

class Foo
{
    public:
        Foo(int);
        void bar();
        int foobar(int);
    private:
        int val;
};

Foo::Foo(int n)
{
    val = n;
}

void Foo::bar()
{
    std::cout << "Value is " << val << std::endl;
}

int Foo::foobar(int n)
{
    return val + n;
}

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

// Define C functions for the C++ class - as ctypes can only talk to C...

extern "C"
{
    Foo* Foo_new(int n) {return new Foo(n);}
    void Foo_bar(Foo* foo) {foo->bar();}
    int Foo_foobar(Foo* foo, int n) {return foo->foobar(n);}
}

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.txtto build foo.cpp.

cmake_minimum_required(VERSION 2.8.9)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}")
set(CMAKE_MACOSX_RPATH 1)

project (foo)
set (SOURCE foo.cpp)
add_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…

import ctypes

lib = ctypes.cdll.LoadLibrary('./libfoo.so')

class Foo(object):
    def __init__(self, val):
        lib.Foo_new.argtypes = [ctypes.c_int]
        lib.Foo_new.restype = ctypes.c_void_p

        lib.Foo_bar.argtypes = [ctypes.c_void_p]
        lib.Foo_bar.restype = ctypes.c_void_p

        lib.Foo_foobar.argtypes = [ctypes.c_void_p, ctypes.c_int]
        lib.Foo_foobar.restype = ctypes.c_int

        self.obj = lib.Foo_new(val)

    def bar(self):
        lib.Foo_bar(self.obj)
    
    def foobar(self, val):
        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 (etc.).

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

For example:

from foo import Foo

# We'll create a Foo object with a value of 5...
f=Foo(5)

# Calling f.bar() will print a message including the value...
f.bar()

# Now we'll use foobar to add a value to that stored in our Foo object, f
print (f.foobar(7))

# Now we'll do the same thing - but this time demonstrate that it's a normal
# Python integer...

x = f.foobar(2)
print (type(x))

The full source code for this simple demo can be found here:

https://github.com/Auctoris/ctypes_demo

2 thoughts on “Calling C++ Classes from Python, with ctypes…

    1. CMake is a simple way to manage building complex code on Linux / Mac systems. Have a look here (http://derekmolloy.ie/hello-world-introductions-to-cmake/) for a good tutorial.

      Essentially you create the CMakeLists.txt file as described above, then run cmake to create a standard UNIX Makefile. Then you just run make to do the building. In this case the libfoo.so file will be build in the current working directory.

      I hope that helps – and sorry for the delay in replying.

Leave a Reply

Your email address will not be published. Required fields are marked *