In short what you want to do is not very difficult as long as you understand the differences between C++ and Python, and let both C++ and Python handle the differences between the languages. The method I have found the easiest and safest is to use Python ctypes to define a Python class wrapper for your C++ class, and define an - extern “C” - wrapper to bridge your C++ class to the Python Class.
The advantages to this approach are that Python can handle all of the memory management, reference counts, etc…; while C++ can handle all of the type conversion, and error handling. Also if there are any future changes to the Python C API, you will not need to worry about it. Instead you can just focus on what is important your code.
Compared to wrapping the C++ class within the Python C API, this is way, way easier! As well as this method does not require anything not included with either C++ or Python standard libraries.
Below you will find an arbitrary example put together mainly from other Stack Overflow posts (cited in the Python wrapper). That I created when I was trying to figure out how to interface Python and C++. The code is heavily commented with details on how each portion of code is implemented. It is one way to do it.
The Python Wrapper:
""" My C++ & Python ctypes test class. The following Stack Overflow URLs either answered my questions as I figured this out, inspired code ideas, or where just downright informative. However there are were other useful pages here and there that I did not record links for. http://stackoverflow.com/questions/1615813/how-to-use-c-classes-with-ctypes http://stackoverflow.com/questions/17244756/python-ctypes-wraping-c-class-with-operators http://stackoverflow.com/questions/19198872/how-do-i-return-objects-from-a-c-function-with-ctypes """ # Define imports. from ctypes import cdll, c_int, c_void_p, c_char_p # Load the shared library. lib = cdll.LoadLibrary("MyClass.dll") # Explicitly define the return types and argument types. # This helps both clarity and troubleshooting. Note that # a 'c_void_p' is passed in the place of the C++ object. # The object passed by the void pointer will be handled in # the C++ code itself. # # Each one of the below calls is a C function call contained # within the external shared library. lib.createClass.restype = c_void_p lib.deleteClass.argtypes = [c_void_p] lib.callAdd.argtypes = [c_void_p, c_void_p] lib.callAdd.restype = c_int lib.callGetID.argtypes = [c_void_p] lib.callGetID.restype = c_char_p lib.callGetValue.argtypes = [c_void_p] lib.callGetValue.restype = c_int lib.callSetID.argtypes = [c_void_p, c_char_p] lib.callSetID.restype = c_int lib.callSetValue.argtypes = [c_void_p, c_int] lib.callSetValue.restype = c_int class MyClass(object): """A Python class which wraps around a C++ object. The Python class will handle the memory management of the C++ object. Not that only the default constructor is called for the C++ object within the __init__ method. Once the object is defined any specific values for the object are set through library function calls. """ def __init__(self, id_str = ""): """Initialize the C++ class using the default constructor. Python strings must be converted to a string of bytes. 'UTF-8' is used to specify the encoding of the bytes to preserve any Unicode characters. NOTE: this can make for unintended side effects in the C++ code. """ self.obj = lib.createClass() if id_str != "": lib.callSetID(self.obj, bytes(id_str, 'UTF-8')) def __del__(self): """Allow Python to call the C++ object's destructor.""" return lib.deleteClass(self.obj) def add(self, other): """Call the C++ object method 'add' to return a new instance of MyClass; self.add(other). """ r = MyClass() lib.callAdd(self.obj, other.obj, r.obj) return r def getID(self): """Return the C++ object's ID. C char string also must be converted to Python strings. 'UTF-8' is the specified format for conversion to preserve any Unicode characters. """ return str(lib.callGetID(self.obj), 'utf-8') def getValue(self): """Return the C++ object's Value.""" return lib.callGetValue(self.obj) def setID(self, id_str): """Set the C++ object's ID string. Remember that Python string must be converted to C style char strings. """ return lib.callSetID(self.obj, bytes(id_str, 'utf-8')) def setValue(self, n): """Set the C++ object's value.""" return lib.callSetValue(self.obj, n) if __name__ == "__main__": x = MyClass("id_a") y = MyClass("id_b") z = x.add(y) z.setID("id_c") print("x.getID = {0}".format(x.getID())) print("x.getValue = {0}".format(x.getValue())) print() print("y.getID = {0}".format(y.getID())) print("y.getValue = {0}".format(y.getValue())) print() print("z.getID = {0}".format(z.getID())) print("z.getValue = {0}".format(z.getValue()))
The C++ class & extern C wrapper:
#include <iostream> #include <new> #include <string> using namespace std; // Manually compile with: // g++ -O0 -g3 -Wall -c -fmessage-length=0 -o MyClass.o MyClass.cpp // g++ -shared -o MyClass.dll "MyClass.o" // Check to see if the platform is a Windows OS. Note that // _WIN32 applies to both a 32 bit or 64 bit environment. // So there is no need to check for _WIN64. #ifdef _WIN32 // On Windows platforms declare any functions meant to be // called from an external program in order to allow the // function to be able to be called. Else define a DEF // file to allow the correct behaviour. (much harder!) #define DLLEXPORT __declspec(dllexport) #endif #ifndef DLLEXPORT #define DLLEXPORT #endif class MyClass { // A C++ class solely used to define an object to test // Python ctypes compatibility. In reality this would // most likely be implemented as a wrapper around // another C++ object to define the right a compatible // object between C++ and Python. public: MyClass() : val(42), id("1234567890") {}; // Notice the next constructor is never called. MyClass(string str) : val(42), id(str) {}; ~MyClass(){}; int add(const MyClass* b, MyClass* c) { // Do not allow exceptions to be thrown. Instead catch // them and tell Python about them, using some sort of // error code convention, shared between the C++ code // and the Python code. try { c->val = val + b->val; return 0; /* } catch(ExceptionName e) { // Return a specific integer to identify // a specific exception was thrown. return -99 */ } catch(...) { // Return an error code to identify if // an unknown exception was thrown. return -1; } // end try }; // end method string getID() { return id; }; int getValue() { return val; }; void setID(string str) { id = str; }; void setValue(int n) { val = n; }; private: int val; string id; }; // end class extern "C" { // All function calls that Python makes need to be made to // "C" code in order to avoid C++ name mangling. A side // effect of this is that overloaded C++ constructors must // use a separate function call for each constructor that // is to be used. Alternatively a single constructor can // be used instead, and then setters can be used to specify // any of an object instance specific values. Which is // what was implemented here. DLLEXPORT void * createClass(void) { // Inside of function call C++ code can still be used. return new(std::nothrow) MyClass; } // end function DLLEXPORT void deleteClass (void *ptr) { delete static_cast<MyClass *>(ptr); } // end function DLLEXPORT int callAdd(void *a, void *b, void *c) { // Do not allow exceptions to be thrown. Instead catch // them and tell Python about them. try { MyClass * x = static_cast<MyClass *>(a); MyClass * y = static_cast<MyClass *>(b); MyClass * z = static_cast<MyClass *>(c); return x->add(y, z); /* } catch(ExceptionName e) { // Return a specific integer to identify // a specific exception was thrown. return -99 */ } catch(...) { // Return an error code to identify if // an unknown exception was thrown. return -1; } // end try } // end function DLLEXPORT const char* callGetID(void *ptr) { try { MyClass * ref = static_cast<MyClass *>(ptr); // Inside of function call C++ code can still be used. string temp = ref->getID(); // A string must be converted to it "C" equivalent. return temp.c_str(); } catch(...) { // Return an error code to identify if // an unknown exception was thrown. return "-1"; } // end try } // end function DLLEXPORT int callGetValue(void *ptr) { try { MyClass * ref = static_cast<MyClass *>(ptr); return ref->getValue(); } catch(...) { // Return an error code to identify if // an unknown exception was thrown. return -1; } // end try } // end function DLLEXPORT int callSetID(void *ptr, char *str) { try { MyClass * ref = static_cast<MyClass *>(ptr); ref->setID(str); return 0; } catch(...) { // Return an error code to identify if // an unknown exception was thrown. return -1; } // end try } // end function DLLEXPORT int callSetValue(void *ptr, int n) { try { MyClass * ref = static_cast<MyClass *>(ptr); ref->setValue(n); return 0; } catch(...) { // Return an error code to identify if // an unknown exception was thrown. return -1; } // end try } // end function } // end extern
Note: Trog unfortunately I do not have a high enough reputation to post comments yet, as I am new to Stack Overflow. Otherwise I would like to have asked if Python ctypes was available in you embedded Python environment first. In fact this is my first post.