Interop between C++ and C or LabVIEW

One of the questions that recur from time to time is: ‘How
do I use C++ classes in a language that has only C bindings, like C itself or
LabVIEW?'

The short answer: you can’t.

The long answer: you can’t, but you can wrap the C++ code in
C functions, and export those in a wrapper dll, thus allowing you to manipulate
C++ objects through a C interface.

The simple solution

The general way to do this is to export a function for
creating objects that returns the object pointer to the caller. Another
function will take that pointer to do something with it, and a third function
will take the pointer to delete it.

You have to cast the pointer to something that can be
recognized by the caller. You cannot export a class pointer, because C or
LabVIEW have no idea what a class is. To be safe, you have to cast it to an
INT_PTR to insure that it is always large enough to hold a pointer.

 

#define OBJPTR INT_PTR

The 3 essential functions look like this:

int
simple_CreateObject(OBJPTR *ObjPtr)
{
  *ObjPtr = (OBJPTR) new
SomeClass();
  if(NULL ==
*ObjPtr)
    return 1;

  return
NO_ERROR;
}

int
simple_UseObject(OBJPTR ObjPtr)
{
  return
((SomeClass*)ObjPtr)->DoSomething();
}

int
simple_DeleteObject(OBJPTR ObjPtr)
{
  delete
(SomeClass*)ObjPtr;
  return
NO_ERROR;
}

This approach is simple and it works. It has at least one
serious problem though: the application will crash if it accidentally asks the
wrapper to delete the same pointer twice, or to dereference an already deleted
pointer.

Especially if you develop this wrapper for use by LabVIEW, I
consider it bad taste if you do not shield the LabVIEW program against crashes
caused by trivial mistakes.

A better solution

A better solution is to map all object pointers to a unique
integer value. The calling program will only receive the integer values that it
can supply to other functions to manipulate the C++ object.

The other wrapper functions have to find the pointer that is
mapped to that integer value. If there is no mapping, it can simply return an
error code instead of crashing the application.

When the object is deleted it is removed from the map so
that the application cannot use it anymore.

The integer reference is incremented with each object that
is created so that there will never be a duplicate reference.

map<BETTER_OBJ_REF,
SomeClass*> g_PtrMap;

int g_Index = 0;


int

better_CreateObject(BETTER_OBJ_REF *ObjRef)
{
  SomeClass* ptr = NULL;

  ptr = new
SomeClass();

  if(NULL ==
ptr)
    return 1;

  if(NO_ERROR
== errorCode)
  {
    g_PtrMap[++g_Index] = ptr;
    *ObjRef = g_Index;
  }

  return
NO_ERROR;
}


int

better_UseObject(BETTER_OBJ_REF ObjRef)
{
  map<BETTER_OBJ_REF,
SomeClass*>::iterator iter;
  iter = g_PtrMap.find(ObjRef);

  if(iter !=
g_PtrMap.end())
    return
(iter->second)->DoSomething();

  else
    return 1;
}

int
better_DeleteObject(BETTER_OBJ_REF ObjRef)
{
  map<BETTER_OBJ_REF,
SomeClass*>::iterator iter;
  iter = g_PtrMap.find(ObjRef);

  if(iter !=
g_PtrMap.end())
  {
    SomeClass* ptr = iter->second;
    g_PtrMap.erase(iter);
    delete ptr;

    return NO_ERROR;

  }

  else
   
return 1;

}

 

Afterthoughts

These 2 examples are proof of concept examples to
demonstrate how to use C++ objects in any language that has C bindings.

This is not production quality code though, because of the
following reasons:

  • It
    does not catch C++ exceptions that could be thrown somewhere in the C++
    code.
  • It
    does not properly handle the overflow of the g_Index variable after 2^32-1
    object allocations.
  • There
    is no synchronization of access to the global map. This means the
    better_xxx functions are not safe for multithreaded usage.
  • There
    is no NULL pointer checking in the simple_xxx functions yet.

You can download the demo project with C++ and LabVIEW code.
It is licensed under the MIT license so you can use it any way you want.

One thought on “Interop between C++ and C or LabVIEW”

Leave a Reply

Your email address will not be published. Required fields are marked *