Wednesday, July 6, 2011

C++ Wrapper Generator - Part1

There are several ways to wrap C++ and integrate it with Python, have a look on stackoverflow. Many wrapper generators rely on GccXML, but the project has been dead for years and has trouble parsing some newer C++. Another option is Swig, still active, but appears to require hand written wrapper code, and lacks support for things like nested classes. Clang was yet another option, it was able to output its parse tree as XML, but this feature was recently broken. So after much searching I finally found CppHeaderParser by Jashua Cloutier on SourceForge, the source code is all contained in a single file and very easy to understand. Since then (back in March), in my free time, I have been hacking away on CppHeaderParser; adding support for parsing more complex C++, resolving typedef's, nested classes, etc. For anyone else interesting in building a wrapper generator on top of it, you will be happy to hear it remains less than 2,000 lines of code, and very easy to modify to fit your particular needs. It is already being used successfully by the Emscripten project to generate a C wrapper and Javascript bindings, kripken's blog.

RPythonic-0.3.8 uses CppHeaderParser-2.0 as a backend to parse the C++ code and generate a C wrapper and Python-ctypes bindings. There is going to be a speed hit calling C++ through a C API over ctypes. Future work will solve the speed problem by generation of RPython (RFFI) bindings so that RPython can call into C++ code.

Work on both CppHeaderParser and the wrapper generator will continue, and should stablize in the next few releases. Basic features that are working now include: operators (==, +=, -=, etc.), enums, typedefs, classes and nested classes, class properties, structs, arrays, doxygen, and C compatible types. The major missing feature is wrapping of templates.

download all wrapper generator samples here

C Wrapper Sample - OgrePaged Method Call

/* Forests::TreeLoader3D.addTree */
/** \brief Adds an entity to the scene with the specified location, rotation, and scale.
\param entity The entity to be added to the scene.
\param position The desired position of the tree
\param yaw The desired rotation around the vertical axis in degrees
\param scale The desired scale of the entity

While TreeLoader3D allows you to provide full 3-dimensional x/y/z coordinates,
you are restricted to only yaw rotation, and only uniform scale.

\warning By default, scale values may not exceed 2.0. If you need to use higher scale
values than 2.0, use setMaximumScale() to reconfigure the maximum. */
/*void addTree ( Ogre : : Entity * entity , __const__ Ogre : : Vector3 & position , Ogre : : Degree yaw = Ogre : : Degree ( 0 ) , Ogre : : Real scale = 1 0f , void * userData = __null ) ;
returns_fundamental: True
returns_pointer: 0
returns_reference: False
returns: void
returns_class: False
void TreeLoader3D_addTree( void* object, void* arg0, void* arg1, void* arg2 ) {
//type: Ogre::Entity *, raw_type: Ogre::Entity, pointer: 1, ,
//type: const Ogre::Vector3 &, raw_type: Ogre::Vector3, constant: 1, reference: 1, ,
//type: Ogre::Degree, raw_type: Ogre::Degree, default: Ogre : : Degree ( 0, ,
(Ogre::Degree&)arg2 );

C Wrapper Sample - BulletPhysics - Set Property

/* --------------- class btVector3 --------------- */
/* */
/**@brief btVector3 can be used to represent 3D points and vectors.
* It has an un-used w component to suit 16-byte alignment when btVector3 is stored in containers. This extra component can be used by derived classes (Quaternion?) or by user
* Ideally, this class should be replaced by a platform optimized SIMD version that keeps the data in registers
/* ---------properties--------- */
//type: btScalar, raw_type: btScalar, typedefs: 1, fundamental: True, array: 4, ctypes_type: ctypes.c_float,
void btVector3_m_floats__property_set__( void* object,btScalar arg[4] ) {
((btVector3*)object)->m_floats[ 0 ] = arg[ 0 ];
((btVector3*)object)->m_floats[ 1 ] = arg[ 1 ];
((btVector3*)object)->m_floats[ 2 ] = arg[ 2 ];
((btVector3*)object)->m_floats[ 3 ] = arg[ 3 ]; }

C Wrapper Sample - BulletPhysics - Operator Overloading

/**@brief Scale the vector
* @param s Scale factor */
/*} inline btVector3 & operator * = ( __const__ btScalar & s ) {
returns_fundamental: False
returns_pointer: 0
returns_reference: True
returns: btVector3
returns_class: True
void* btVector3___operator____imult__( void* object, const btScalar & arg0 ) {
return (void*)(& (((btVector3&)object)*=(arg0)) );

Pass1: Generate C Wrapper

  • flatten all method calls to functions

  • the instance is passed as the first argument

  • all instances are passed as void pointers

  • C compatible types are passed directly

  • objects returned are cast to void pointers

  • create wrapper functions for operators

  • create wrapper functions for get/set properties

Pass2: Generate Python-ctypes Wrapper

  • create a Python class for each C++ class

  • __init__ calls the constructor C wrapper

  • each method calls the C wrapper function

  • methods that return instances are passed to the matching python class

  • __del__ calls the destructor

  • operators overload: __mult__, __add__, etc..

  • __getattr__, __setattr__ call the get/set wrapper functions