Hourglass pattern

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