Wrapping C++ class API for C consumption
Asked Answered
T

7

37

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.

Tannin answered 19/10, 2009 at 13:49 Comment(1)
Related (or even a duplicate): Developing C wrapper API for Object-Oriented C++ codeIloilo
D
37

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);
}
Dromond answered 19/10, 2009 at 19:14 Comment(8)
typedef void* CFred; is too generic which invites problem. I would use typedef Foo* CFred; which I think is better, because it makes compiler to do some type check. Now you cannot pass any type to C function which takes CFred as argument.Asymmetric
I don't believe that. How can one prove that? It is just a pointer?Asymmetric
I would use struct keyword instead of class if I were to do that, so that both compiler will understand the code. That is, I will do this : #ifdef __cplusplus struct Fred { ... }; #else struct Fred; typedef Fred* CFred; #endifAsymmetric
@Nawaz: OK. see were you are going.Dromond
What about typedef struct _CFred *CFred instead of typedef void *CFred? That way it is a pointer of a unique type, which is opaque in "C".Downhearted
@theswine: 1) Drop the _ your identifier is illegal (reserved). 2) Your '*' is on the wrong side for C++ code. 3) Is that not what Nawaz said?Dromond
@LokiAstari 1) yeah, the reserved underscores again. OpenCL headers actually use the same code style except their struct starts with the lowercase letters, I never know for which one is two underscores reserved and for which one is one. 2) The asterisk is actually an interesting statement from you. why would it be on the wrong side in C++ particularly? 3) Yes, Nawaz's version is similar, except that the _CFred is unrelated to the original class and can coexist with it in C++ version of the code. Also my version is a one-liner.Downhearted
Shouldn't the "extern C" directive be withing the #ifdef __cplusplus ... #endif block, since it is a C++ directive? I'm assuming that we'll be including Fred.h inside a C file.Hydromancy
V
21

While Loki Astari's answer is very good, his sample code puts the wrapping code inside the C++ class. I prefer to have the wrapping code in a separate file. Also I think it is better style to prefix the wrapping C functions with the class name.

The following blog posts shows how to do that: http://blog.eikke.com/index.php/ikke/2005/11/03/using_c_classes_in_c.html

I copied the essential part because the blog is abandoned and might finally vanish (credit to Ikke's Blog):


First we need a C++ class, using one header file (Test.hh)

class Test {
    public:
        void testfunc();
        Test(int i);

    private:
        int testint;
};

and one implementation file (Test.cc)

#include <iostream>
#include "Test.hh"

using namespace std;

Test::Test(int i) {
    this->testint = i;
}

void Test::testfunc() {
    cout << "test " << this->testint << endl;
}

This is just basic C++ code.

Then we need some glue code. This code is something in-between C and C++. Again, we got one header file (TestWrapper.h, just .h as it doesn't contain any C++ code)

typedef void CTest;

#ifdef __cplusplus
extern "C" {
#endif

CTest * test_new(int i);
void test_testfunc(const CTest *t);
void test_delete(CTest *t);
#ifdef __cplusplus
}
#endif

and the function implementations (TestWrapper.cc, .cc as it contains C++ code):

#include "TestWrapper.h"
#include "Test.hh"

extern "C" {

    CTest * test_new(int i) {
        Test *t = new Test(i);

        return (CTest *)t;
    }

    void test_testfunc(const CTest *test) {
        Test *t = (Test *)test;
        t->testfunc();
    }

    void test_delete(CTest *test) {
        Test *t = (Test *)test;

        delete t;
    }
}
Vonvona answered 15/10, 2013 at 15:28 Comment(4)
I believe it's cleaner to define the C-side type as a void* instead of void, like Loki has done, since the class object will be passed between C & C++ code as a void*.Hydromancy
is the extern "C" required in the cpp fileRoadrunner
Does this imply that the C code will simply call (and maintain) object instantiation and call object methods and object destructors via "blind" C pointers?Fictile
@ElvisDukaj yesDougald
A
6

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);
Architectonics answered 19/10, 2009 at 14:17 Comment(0)
E
5

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 :) ).

Extinguish answered 19/10, 2009 at 13:53 Comment(2)
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...Tannin
I wonder if there's any automation for this kind of mechanical work these days ...Fictile
I
2

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.

Idolatrize answered 19/10, 2009 at 13:54 Comment(0)
C
1

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?

Cila answered 19/10, 2009 at 13:54 Comment(1)
Thats not good code or best practice. Another bad example from the C++ FAQ Lite.Dromond
H
0

I haven't tried it myself, but CPPBind exists now and has a C backend.

Hutment answered 26/12, 2023 at 1:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.