Wrapping C++ class API for C consumption

拈花ヽ惹草 提交于 2019-11-27 17:27:24

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);
}

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;
    }
}

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

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);
Daniel Daranas

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.

Doug T.

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?

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!