Skip to content

Instantly share code, notes, and snippets.

@bennof
Last active May 7, 2018 10:26
Show Gist options
  • Save bennof/bf2b5a91e1715acb9afa46fae8269f3f to your computer and use it in GitHub Desktop.
Save bennof/bf2b5a91e1715acb9afa46fae8269f3f to your computer and use it in GitHub Desktop.

pyC++

Calling C++ Functions from Python

1. C++ Classes

Classes are on of the basic constructs of C++ and are used widely in object oriented programming. More and more libraries are written in C++ using this technique to hide complexity and to keep programs readable. All this is very efficient as long as you will not try to call class function from C. Of course this is daffy, because you can easily write the entire Program in C++ and will avoid a lot of trouble.

If you started working with python it is different - OK you can use wrappers like "swig" or "boost", which will handle almost everything for you. All this will become complicated if you like to use callbacks, which are very simple to handle with ctypes[2]. Ctypes can be used to interface any C library directly from python and is the native way, if you do not want to integrate the interface in the C library by including "python.h" etc. Especially if you use a very strict model like Design Patterns, you will like to keep it simple and use ctypes module. But in that case you can not use C++ or you have to build an interface.

I have read this to often on the Internet and so decided to solve this problem for a simple C++ Class (perhaps I will write a module for this in the future). So lets start with a very simple C++ library, which can be easily enhanced:

//Header test.h
class test{
  int id;                //store id
  public:
    test();              //constuctor
    ~test();             //destructor
    int getId();         //return value of id
    void setId(int i);   //set value of id
};

//To avoid loading libc++
test* newtest();         //hide allocation by new
test* deltest(test* t);  //hide free by delete
And the source file is:

//Code test.cpp
#include "test.h"

test::test(){
  id=1;
}

test::~test(){
  id=0;
}

int test::getId(){
  return id;
}

void test::setId(int i){
  id=i;
}

test* newtest(){
  return new test();
}

test* deltest(test* t){
  delete t;
  return 0;
}

To compile this library use g++:

g++ -shared -fPIC test.cpp -o test.so

or on MacOSX (to get an .so and not a .dynlib):

g++ -fPIC -bundle -flat_namespace -undefined suppress test.cpp -o test.so 

I also created the object file by using the -c switch, to be able to link the C program statically

2. C++ Name Mangling

Name mangling[1] is a technique to resolve unique names from function names, which are absolute and not dependent on the C++ object they belong to. How this is done depends on the compiler that is used but is quite simple, if you understood the basic idea. Here I will only discuss the output of GCC 3+ and how it is done on a Unix system. For further information check the Wiki entry.

To Inspect a library you can use nm to list symbols from object files:

nm test.so 

And the output will be something like this (taken from MacOSX):

0000000000000dc6 s  stub helpers
0000000000000d52 T __Z7deltestP4test
0000000000000d86 T __Z7newtestv
0000000000000d2c T __ZN4test5getIdEv
0000000000000d3c T __ZN4test5setIdEi
0000000000000cf0 T __ZN4testC1Ev
0000000000000cdc T __ZN4testC2Ev
0000000000000d18 T __ZN4testD1Ev
0000000000000d04 T __ZN4testD2Ev
                 U __ZdlPv
                 U __Znwm
                 U ___gxx_personality_v0
0000000000000000 t __mh_bundle_header
                 U dyld_stub_binder

You will find the function of the class in the list plus some extra stuff that is available in the library which we will ignore right now. For the function deltest() in the second line it is __Z7deltestP4test. The integers encode the number of characters of the function or object name, here 7. _Z is the beginning of each mangled name, N tells that it is a series of objects, E or P as the end of the name followed by an encoding for the arguments.

From this it is possible to resolve all the used functions of our library. It is import to know that C as its own name mangling by adding an underscore in front of the name, which has to be removed from the mangled names. We will ignore the original constructors here to keep it simple and only use our functions to create a class object:

test* deltest(test *)   //_Z7deltestP4test
test* newtest()         //_Z7newtestv
int test::getId()       //_ZN4test5getIdEv
void test::setId(int i) //_ZN4test5setIdEi

3. Calling from C

From C it is simple to use this functions by including them with extern. The first argument is the address or a pointer to the class object[3].

//main.c 

//extern function
extern void *_Z7deltestP4test(void *); 
extern void *_Z7newtestv();
extern int  _ZN4test5getIdEv(void *); 
extern void _ZN4test5setIdEi(void *,int);

#include <stdio.h>

int main(){
  void *p;

  //create object
  p=_Z7newtestv();

  //setId to 23
  _ZN4test5setIdEi(p,23);

  //should print out 23
  fprintf(stderr,"p = %i \n",_ZN4test5getIdEv(p));

  //delete object
  p=_Z7deltestP4test(p);
  return 0;
}

You can keep this a little bit more comfortable by defining a structure (expect segmentation faults and other problems, if the order of arguments or the alignment is wrong):

//main.c 

//structure
typedef struct{
  int id;
}test;

//extern function
extern test *_Z7deltestP4test(test *); 
extern test *_Z7newtestv();
extern int  _ZN4test5getIdEv(test *); 
extern void _ZN4test5setIdEi(test *,int);

#include <stdio.h>

int main(){
  test *p;

  //create object
  p=_Z7newtestv();

  //setId to 23
  _ZN4test5setIdEi(p,23);

  //should print out 23
  fprintf(stderr,"p.id = %i \n",_ZN4test5getIdEv(p));

  //delete object
  p=_Z7deltestP4test(p);
  return 0;
}

To compile this, you need to link the libstdc++:

gcc main.c test.o -lstdc++-static

4. Calling from Python

For python I will do almost the same, just using the ctypes module:

from ctypes import *
test=cdll.LoadLibrary("test.so")

#setting return and argument types of the used functions
test._Z7newtestv.restype=c_void_p
test._Z7deltestP4test.restype=c_void_p
test._Z7deltestP4test.argtypes=[c_void_p]
test._ZN4test5setIdEi.argtypes=[c_void_p,c_int]
test._ZN4test5getIdEv.argtypes=[c_void_p]

#same example as the c program
p=test._Z7newtestv()
test._ZN4test5setIdEi(p,c_int(23))
print "p.id = %i"%(test._ZN4test5getIdEv(p))
p=test._Z7deltestP4test(p)

5. Assembly

Missing: I will present a way to use the new and the delete statements by loading the C++-Library.

6. Conclusion

All in all this is a not very safe way to work with C++ libraries, but the focus was on how to interact with those libraries and perhaps to have a basis for an equivalent to ctypes for C++. It is important to be very careful if you like to try something like this because it can cause a lot of problems and is experimental work.

7. Links

[1]Name Mangling

[2]Ctypes

[3]Calling Conventions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment