WRAPPER C/C++
DEVELOPING A C WRAPPER API FOR OO C++ CODE,
Develop a set of C APIs that will wrap around our existing C++ APIs to access our core logic (written in object-oriented C++). This will essentially be a glue API that allows our C++ logic to be usable by other languages. We based this development on a specific design pattern called hourglass pattern. All we ended up doing was: Every object is passed about in C an opaque handle. Constructors and destructors are wrapped in pure functions Member functions are pure functions. Other built-ins are mapped to C equivalents where possible.
FIRST APPROACH
So a class like this (C++ header)
class MyClass { public: explicit MyClass( std::string & s ); ~MyClass(); int doSomething( int j ); }
Would map to a C interface like this (C header):
struct HMyClass; // An opaque type that we'll use as a handle typedef struct HMyClass HMyClass; HMyClass * myStruct_create( const char * s ); void myStruct_destroy( HMyClass * v ); int myStruct_doSomething( HMyClass * v, int i );
The implementation of the interface would look like this (C++ source)
#include "MyClass.h" extern "C" { HMyClass * myStruct_create( const char * s ) { return reinterpret_cast<HMyClass*>( new MyClass( s ) ); } void myStruct_destroy( HMyClass * v ) { delete reinterpret_cast<HMyClass*>(v); } int myStruct_doSomething( HMyClass * v, int i ) { return reinterpret_cast<HMyClass*>(v)->doSomething(i); } }
- reinterpret_cast only guarantees that if you cast a pointer to a different type, and then reinterpret_cast it back to the original type, you get the original value…
- static_casting a pointer to and from void* preserves the address
Exceptions are not part of the C ABI so you cannot let Exceptions ever be thrown past
the C++ code. So your header is going to look like this:
#ifdef __cplusplus extern "C" { #endif void * myStruct_create( const char * s ); void myStruct_destroy( void * v ); int myStruct_doSomething( void * v, int i ); #ifdef __cplusplus } #endif
And your wrapper’s .cpp file will look like this:
void * myStruct_create( const char * s ) { MyStruct * ms = NULL; try { /* The constructor for std::string may throw */ ms = new MyStruct(s); } catch (...) {} return static_cast<void*>( ms ); }
void myStruct_destroy( void * v ) { MyStruct * ms = static_cast<MyStruct*>(v); delete ms; } int myStruct_doSomething( void * v, int i ) { MyStruct * ms = static_cast<MyStruct*>(v); int ret_value = -1; /* Assuming that a negative value means error */ try { ret_value = ms->doSomething(i); } catch (...) {} return ret_value; } }
Even better: If you know that all you need as a single instance of MyStruct, don’t take the risk of
dealing with void pointers being passed to your API. Do something like this instead:
static MyStruct * _ms = NULL; int myStruct_create( const char * s ) { int ret_value = -1; /* error */ try { /* The constructor for std::string may throw */ _ms = new MyStruct(s); ret_value = 0; /* success */ } catch (...) {} return ret_value; } void myStruct_destroy() { if (_ms != NULL) { delete _ms; } } int myStruct_doSomething( int i ) { int ret_value = -1; /* Assuming that a negative value means error */ if (_ms != NULL) { try { ret_value = _ms->doSomething(i); } catch (...) {} } return ret_value; }
OTHER POINTS
Namespace should be replaced by a prefix for each function.
To be continued…
The Hourglass pattern – Case study – PDF format