views:

255

answers:

5

Hi there,

I have a set of related C++ classes which must be wrapped and exported from a DLL in such a way that it can be easily consumed by C / FFI libraries. I'm looking for some "best practices" for doing this. For example, how to create and free objects, how to handle base classes, alternative solutions, etc...

Some basic guidelines I have so far is to convert methods into simple functions with an extra void* argument representing the 'this' pointer, including any destructors. Constructors can retain their original argument list, but must return a pointer representing the object. All memory should be handled via the same set of process-wide allocation and free routines, and should be hot-swappable in a sense, either via macros or otherwise.

Any advice is appreciated

+2  A: 

First, you might not need to convert all your methods to C functions. If you can simplify the API and hide some of the C++ interface, it is better, since you minimize the chance to change the C API when you change C++ logic behind.

So think of a higher level abstraction to be provided through that API. Use that void* solution you described. It looks to me the most appropriate (or typedef void* as HANDLE :) ).

Cătălin Pitiș
For this project, I pretty much need a one-to-one mapping with all of the class methods. I'm wrapping an "Assembler" class, which amongst other things, has about 50 or so methods representing each instruction in the instruction set. There are also other classes representing registers, memory locations, pointers, etc...
Exponent
+2  A: 

Use vector (and string::c_str) to exchange data with non C++ APIs. (Guideline #78 from C++ Coding Standards, H. Sutter/ A. Alexandrescu).

PS It's not that true that "constructors can retain their original argument list". This is only true for argument types which are C-compatible.

PS2 Of course, listen to Cătălin and keep your interface as small and simple as possible.

Daniel Daranas
+1  A: 

This may be of interest: "Mixing C and C++" at the C++ FAQ Lite. Specifically [32.8] How can I pass an object of a C++ class to/from a C function?

Doug T.
Thats not good code or best practice. Another bad example from the C++ FAQ Lite.
Martin York
+1  A: 

Some opinions from my experience:

  • functions should return codes to represent errors. It's useful to have a function returning error description in string form. All other return values should be out parameters.

E.g.:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget);
  • put signatures into structures/classes your handles pointer to for checking handles on validness.

E.g. your function should look like:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget){
    Ui* ui = (Ui*)ui;
    if(ui.Signature != 1234)
    return BAD_HUI;
}
  • objects should be created and released using functions exported from DLL, since memory allocation method in DLL and consuming app can differ.

E.g.:

C_ERROR CreateUi(HUI* ui);
C_ERROR CloseUi(HUI hui); // usually error codes don't matter here, so may use void
  • if you are allocating memory for some buffer or other data that may be required to persist outside of your library, provide size of this buffer/data. This way users can save it to disk, DB or wherever they want without hacking into your internals to find out actual size. Otherwise you'll eventually need to provide your own file I/O api which users will use only to convert your data to byte array of known size.

E.g.:

C_ERROR CreateBitmap(HUI* ui, SIZE size, char** pBmpBuffer, int* pSize);
  • if your objects has some typical representation outside of your C++ library, provide a mean of converting to this representation (e.g. if you have some class Image and provide access to it via HIMG handle, provide functions to convert it to and from e.g. windows HBITMAP). This will simplify integration with existing API.

E.g.

C_ERROR BitmapToHBITMAP(HUI* ui, char* bmpBuffer, int size, HBITMAP* phBmp);
elder_george
+1  A: 

Foreach public method you need a C function.
You also need an opaque pointer to represent your class in the C code.
It is simpler to just use a void* though you could build a struct that contains a void* and other information (For example if you wanted to support arrays?).

Fred.h
--------------------------------

#ifdef  __cplusplus
class Fred
{
    public:
    Fred(int x,int y);
    int doStuff(int p);
};
#endif

//
// C Interface.
typedef void*   CFred;

//
// Need an explicit constructor and destructor.
extern "C" CFred  newCFred(int x,int y);
extern "C" void   delCFred(CFred);

//
// Each public method. Takes an opaque reference to the object
// that was returned from the above constructor plus the methods parameters.
extern "C" int    doStuffCFred(CFred,int p);

The the implementation is trivial.
Convert the opaque pointer to a Fred and then call the method.

CFred.cpp
--------------------------------

// Functions implemented in a cpp file.
// But note that they were declared above as extern "C" this gives them
// C linkage and thus are available from a C lib.
CFred newCFred(int x,int y)
{
    return reinterpret_cast<void*>(new Fred(x,y));
}

void delCFred(CFred fred)
{
    delete reinterpret_cast<Fred*>(fred);
}

int doStuffCFred(CFred fred,int p)
{
    return reinterpret_cast<Fred*>(fred)->doStuff(p);
}
Martin York