How to use C++ classes with ctypes?
Besides Boost.Python(which is probably a more friendly solution for larger projects that require one-to-one mapping of C++ classes to python classes), you could provide on the C++ side a C interface. It's one solution of many so it has its own trade offs, but I will present it for the benefit of those who aren't familiar with the technique. For full disclosure, with this approach one wouldn't be interfacing C++ to python, but C++ to C to Python. Below I included an example that meets your requirements to show you the general idea of the extern "c" facility of C++ compilers.
//YourFile.cpp (compiled into a .dll or .so file)#include <new> //For std::nothrow//Either include a header defining your class, or define it here. extern "C" //Tells the compile to use C-linkage for the next scope.{ //Note: The interface this linkage region needs to use C only. void * CreateInstanceOfClass( void ) { // Note: Inside the function body, I can use C++. return new(std::nothrow) MyClass; } //Thanks Chris. void DeleteInstanceOfClass (void *ptr) { delete(std::nothrow) ptr; } int CallMemberTest(void *ptr) { // Note: A downside here is the lack of type safety. // You could always internally(in the C++ library) save a reference to all // pointers created of type MyClass and verify it is an element in that //structure. // // Per comments with Andre, we should avoid throwing exceptions. try { MyClass * ref = reinterpret_cast<MyClass *>(ptr); return ref->Test(); } catch(...) { return -1; //assuming -1 is an error condition. } }} //End C linkage scope.
You can compile this code with
gcc -shared -o test.so test.cpp#creates test.so in your current working directory.
In your python code you could do something like this (interactive prompt from 2.7 shown):
>>> from ctypes import cdll>>> stdc=cdll.LoadLibrary("libc.so.6") # or similar to load c library>>> stdcpp=cdll.LoadLibrary("libstdc++.so.6") # or similar to load c++ library>>> myLib=cdll.LoadLibrary("/path/to/test.so")>>> spam = myLib.CreateInstanceOfClass()>>> spam[outputs the pointer address of the element]>>> value=CallMemberTest(spam)[does whatever Test does to the spam reference of the object]
I'm sure Boost.Python does something similar under the hood, but perhaps understanding the lower levels concepts is helpful. I would be more excited about this method if you were attempting to access functionality of a C++ library and a one-to-one mapping was not required.
For more information on C/C++ interaction check out this page from Sun: http://dsc.sun.com/solaris/articles/mixing.html#cpp_from_c
The short story is that there is no standard binary interface for C++ in the way that there is for C. Different compilers output different binaries for the same C++ dynamic libraries, due to name mangling and different ways to handle the stack between library function calls.
So, unfortunately, there really isn't a portable way to access C++ libraries in general. But, for one compiler at a time, it's no problem.
This blog post also has a short overview of why this currently won't work. Maybe after C++0x comes out, we'll have a standard ABI for C++? Until then, you're probably not going to have any way to access C++ classes through Python's ctypes
.
The answer by AudaAero is very good but not complete (at least for me).
On my system (Debian Stretch x64 with GCC and G++ 6.3.0, Python 3.5.3) I have segfaults as soon has I call a member function that access a member value of the class.I diagnosticated by printing pointer values to stdout that the void* pointer coded on 64 bits in wrappers is being represented on 32 bits in Python. Thus big problems occurs when it is passed back to a member function wrapper.
The solution I found is to change:
spam = myLib.CreateInstanceOfClass()
Into
Class_ctor_wrapper = myLib.CreateInstanceOfClassClass_ctor_wrapper.restype = c_void_pspam = c_void_p(Class_ctor_wrapper())
So two things were missing: setting the return type to c_void_p (the default is int) and then creating a c_void_p object (not just an integer).
I wish I could have written a comment but I still lack 27 rep points.