为 C 消费包装 C++ 类 API

Posted

技术标签:

【中文标题】为 C 消费包装 C++ 类 API【英文标题】:Wrapping C++ class API for C consumption 【发布时间】:2010-12-08 00:13:47 【问题描述】:

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

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

【问题讨论】:

相关(甚至重复):Developing C wrapper API for Object-Oriented C++ code 【参考方案1】:

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

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

【讨论】:

对于这个项目,我几乎需要一个与所有类方法的一对一映射。我正在包装一个“汇编程序”类,除其他外,它有大约 50 个左右的方法来表示指令集中的每条指令。还有其他类表示寄存器、内存位置、指针等...... 我想知道这些天这种机械工作是否有任何自动化......【参考方案2】:

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

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

PS2 当然,听Cătălin 并让你的界面尽可能的小和简单。

【讨论】:

【参考方案3】:

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

【讨论】:

那不是好的代码或最佳实践。 C++ FAQ Lite 中的另一个坏例子。【参考方案4】:

我的一些经验:

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

例如:

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

例如你的函数应该是这样的:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget)
    Ui* ui = (Ui*)ui;
    if(ui.Signature != 1234)
    return BAD_HUI;

应使用从 DLL 导出的函数创建和释放对象,因为 DLL 中的内存分配方法和使用应用程序可能不同。

例如:

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

例如:

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

例如

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

【讨论】:

【参考方案5】:

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

【讨论】:

typedef void* CFred; 过于笼统,会引起问题。我会使用我认为更好的typedef Foo* CFred;,因为它使编译器进行一些类型检查。现在您不能将任何类型传递给以 CFred 作为参数的 C 函数。 我不相信。怎么能证明这一点?它只是一个指针? 如果我这样做,我会使用 struct 关键字而不是 class,以便两个编译器都能理解代码。也就是说,我会这样做:#ifdef __cplusplus struct Fred ... ; #else struct Fred; typedef Fred* CFred; #endif @Nawaz:好的。看看你要去吗。 typedef struct _CFred *CFred 代替 typedef void *CFred 怎么样?这样它是一个唯一类型的指针,在“C”中是不透明的。【参考方案6】:

虽然 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;
    

【讨论】:

我相信将 C 端类型定义为 void* 而不是 void 更简洁,就像 Loki 所做的那样,因为类对象将在 C 和 C++ 代码之间作为 void* 传递。 是cpp文件中需要的extern "C" 这是否意味着 C 代码将简单地调用(和维护)对象实例化并通过“盲”C 指针调用对象方法和对象析构函数?

以上是关于为 C 消费包装 C++ 类 API的主要内容,如果未能解决你的问题,请参考以下文章

用c语言或c++编写编程实现生产者消费者或读写者的同步问题

C++ 简单的消费者生产者问题

C++ 智能指针和指针到指针输出 API。模板化的“包装器”

如何围绕 C++ 代码编写 C 包装器以公开类方法

条件变量 + 互斥体 + pthreads 的 C++ 生产者消费者问题

为python(使用opencv)包装c ++类,给出错误