包装用于C消耗的C ++类API

时间:2009-10-19 13:49:33

标签: c++ c api class wrapping

我有一组相关的C ++类,必须以这样的方式包装和导出DLL,以便C / FFI库可以轻松使用它们。我正在寻找一些“最佳实践”来做这件事。例如,如何创建和释放对象,如何处理基类,替代解决方案等......

我到目前为止的一些基本指导原则是将方法转换为简单函数,并使用一个额外的void *参数来表示'this'指针,包括任何析构函数。构造函数可以保留其原始参数列表,但必须返回表示该对象的指针。所有内存都应该通过同一组进程范围的分配和自由例程来处理,并且在某种意义上应该可以通过宏或其他方式进行热交换。

6 个答案:

答案 0 :(得分:30)

Foreach公共方法需要C函数。
您还需要一个不透明的指针来表示C代码中的类 虽然你可以构建一个包含void *和其他信息的结构(例如,如果你想支持数组?),那么使用void *就更简单了。

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

实施是微不足道的 将不透明指针转换为Fred,然后调用该方法。

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

答案 1 :(得分:16)

虽然Loki Astari的答案非常好,但他的示例代码将包装代码放在C ++类中。我更喜欢将包装代码放在一个单独的文件中。另外我认为使用类名称为包装C函数添加前缀是更好的样式。

以下博客文章介绍了如何执行此操作: http://blog.eikke.com/index.php/ikke/2005/11/03/using_c_classes_in_c.html

我复制了必不可少的部分,因为博客被遗弃并可能最终消失(归功于Ikke的博客):


首先我们需要一个C ++类,使用一个头文件(Test.hh)

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

    private:
        int testint;
};

和一个实现文件(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;
}

这只是基本的C ++代码。

然后我们需要一些胶水代码。这段代码介于C和C ++之间。同样,我们有一个头文件(TestWrapper.h,只是.h,因为它不包含任何C ++代码)

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

和函数实现(TestWrapper.cc,.cc,因为它包含C ++代码):

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

答案 2 :(得分:4)

首先,您可能不需要将所有方法转换为C函数。如果您可以简化API并隐藏一些C ++接口,那就更好了,因为当您更改后面的C ++逻辑时,您最大限度地减少了更改C API的机会。

因此,请考虑通过该API提供更高级别的抽象。使用您描述的void *解决方案。它看起来最合适(或者typedef void * as HANDLE :))。

答案 3 :(得分:3)

根据我的经验提出的一些意见:

  • 函数应返回代码以表示错误。让函数以字符串形式返回错误描述是很有用的。所有其他返回值应该是参数。

E.g:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget);
  • 将签名放入您的句柄指针的结构/类中,以检查有效性的句柄。

E.g。你的功能应该如下:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget){
    Ui* ui = (Ui*)ui;
    if(ui.Signature != 1234)
    return BAD_HUI;
}
    应该使用从DLL导出的函数创建和释放
  • 对象,因为DLL和使用应用程序中的内存分配方法可能不同。

E.g:

C_ERROR CreateUi(HUI* ui);
C_ERROR CloseUi(HUI hui); // usually error codes don't matter here, so may use void
  • 如果要为某些缓冲区或可能需要在库外部保留的其他数据分配内存,请提供此缓冲区/数据的大小。这样,用户可以将其保存到磁盘,数据库或任何他们想要的地方,而无需入侵内部以查找实际大小。否则,您最终需要提供自己的文件I / O api,用户只能将数据转换为已知大小的字节数组。

E.g:

C_ERROR CreateBitmap(HUI* ui, SIZE size, char** pBmpBuffer, int* pSize);
  • 如果您的对象在C ++库之外有一些典型的表示,请提供转换为此表示的方法(例如,如果您有一个类Image并通过HIMG句柄提供对它的访问,请提供用于将其转换为例如windows HBITMAP的函数。这将简化与现有API的集成。

E.g。

C_ERROR BitmapToHBITMAP(HUI* ui, char* bmpBuffer, int size, HBITMAP* phBmp);

答案 4 :(得分:2)

使用vector(和string :: c_str)与非C ++ API交换数据。 ( C++ Coding Standards 的指南#78,H。Sutter / A. Alexandrescu)。

PS“构造函数可以保留其原始参数列表”并非如此。这仅适用于与C兼容的参数类型。

PS2当然,请听Cătălin并保持您的界面尽可能小巧。

答案 5 :(得分:2)

这可能是有趣的:{C ++ FAQ Lite中的"Mixing C and C++"。具体来说是[32.8] How can I pass an object of a C++ class to/from a C function?