为面向对象的C++代码开发C包装API

the*_*tor 75 c c++ wrapper

我正在寻找开发一组C API,它们将围绕我们现有的C++ API来访问我们的核心逻辑(用面向对象的C++编写).这基本上是一个粘合API,允许我们的C++逻辑可以被其他语言使用.什么是一些很好的教程,书籍或最佳实践,介绍了围绕面向对象的C++包装C所涉及的概念?

Mic*_*son 65

这不是很难手工完成,而是取决于界面的大小.我完成它的情况是允许在纯C代码中使用我们的C++库,因此SWIG没有太大帮助.(好吧也许SWIG可以用来做这个,但我不是SWIG大师,它似乎不平凡)

我们最终做的就是:

  1. 每个对象都在C中传递一个不透明的句柄.
  2. 构造函数和析构函数包含在纯函数中
  3. 成员函数是纯函数.
  4. 其他内置函数尽可能映射到C等价物.

所以像这样的类(C++头)

class MyClass
{
  public:
  explicit MyClass( std::string & s );
  ~MyClass();
  int doSomething( int j );
}
Run Code Online (Sandbox Code Playgroud)

会映射到这样的C接口(C头):

struct HMyClass; // An opaque type that we'll use as a handle
typedef struct HMyClass HMyClass;
HMyClass * myStruct_create( const char * s );
void myStruct_destroy( HMyClass * v );
int myStruct_doSomething( HMyClass * v, int i );
Run Code Online (Sandbox Code Playgroud)

接口的实现看起来像这样(C++源代码)

#include "MyClass.h"

extern "C" 
{
  HMyClass * myStruct_create( const char * s )
  {
    return reinterpret_cast<HMyClass*>( new MyClass( s ) );
  }
  void myStruct_destroy( HMyClass * v )
  {
    delete reinterpret_cast<MyClass*>(v);
  }
  int myStruct_doSomething( HMyClass * v, int i )
  {
    return reinterpret_cast<MyClass*>(v)->doSomething(i);
  }
}
Run Code Online (Sandbox Code Playgroud)

我们从原始类派生我们的不透明句柄以避免需要任何转换,并且(这似乎不适用于我当前的编译器).我们必须使句柄成为结构,因为C不支持类.

这样就给了我们基本的C接口.如果你想要一个更完整的例子,展示一种可以集成异常处理的方法,那么你可以在github上尝试我的代码:https://gist.github.com/mikeando/5394166

现在,有趣的部分是确保您正确地将所有必需的C++库链接到更大的库中.对于gcc(或clang),这意味着只使用g ++进行最后的链接阶段.

  • 我建议你使用除void之外的其他东西,例如匿名结构而不是返回对象的void*.这可以为返回的手柄提供某种类型的安全性.有关它的更多信息,请查看http://stackoverflow.com/questions/839765/the-right-type-for-handles-in-c-interfaces. (11认同)
  • @MichaelAnderson,你的`myStruct_destroy`和`myStruct_doSomething`函数有两个拼写错误.应该是`reinterpret_cast <MyClass*>(v)`. (5认同)
  • 我同意Laserallan,并已相应地重构了我的代码 (2认同)
  • @Mike Weller extern"C"块中的new和delete很好.外部"C"只影响名称修改.C编译器永远不会看到该文件,只看到标题. (2认同)
  • 我也错过了一个需要在 C 中编译的 typedef。奇怪的 typdef struct Foo Foo; “黑客”。代码已更新 (2认同)

fig*_*ssa 14

我认为迈克尔安德森的答案是正确的,但我的方法会有所不同.你不得不担心一件事:例外.异常不是C ABI的一部分,因此您不能让Exceptions超越C++代码.所以你的标题看起来像这样:

#ifdef __cplusplus
extern "C"
{
#endif
    void * myStruct_create( const char * s );
    void myStruct_destroy( void * v );
    int myStruct_doSomething( void * v, int i );
#ifdef __cplusplus
}
#endif
Run Code Online (Sandbox Code Playgroud)

你的包装器的.cpp文件看起来像这样:

void * myStruct_create( const char * s ) {
    MyStruct * ms = NULL;
    try { /* The constructor for std::string may throw */
        ms = new MyStruct(s);
    } catch (...) {}
    return static_cast<void*>( ms );
}

void myStruct_destroy( void * v ) {
    MyStruct * ms = static_cast<MyStruct*>(v);
    delete ms;
}

int myStruct_doSomething( void * v, int i ) {
    MyStruct * ms = static_cast<MyStruct*>(v);
    int ret_value = -1; /* Assuming that a negative value means error */
    try {
        ret_value = ms->doSomething(i);
    } catch (...) {}
    return ret_value;
}
Run Code Online (Sandbox Code Playgroud)

更好的是:如果你知道你需要的只是一个MyStruct的单个实例,那么就不要冒着处理传递给你的API的void指针的风险.做这样的事情:

static MyStruct * _ms = NULL;

int myStruct_create( const char * s ) {
    int ret_value = -1; /* error */
    try { /* The constructor for std::string may throw */
        _ms = new MyStruct(s);
        ret_value = 0; /* success */
    } catch (...) {}
    return ret_value;
}

void myStruct_destroy() {
    if (_ms != NULL) {
        delete _ms;
    }
}

int myStruct_doSomething( int i ) {
    int ret_value = -1; /* Assuming that a negative value means error */
    if (_ms != NULL) {
        try {
            ret_value = _ms->doSomething(i);
        } catch (...) {}
    }
    return ret_value;
}
Run Code Online (Sandbox Code Playgroud)

这个API更安全.

但是,正如迈克尔所说,链接可能会变得非常棘手.

希望这可以帮助

  • 有关此案例的异常处理的更多信息,请查看以下主题:http://stackoverflow.com/questions/847279/code-reuse-in-exception-handling (2认同)
  • 当我知道我的C++库也有一个C API时,我在我的异常基类中封装了一个API错误代码int.在投掷站点更容易知道确切的错误条件是什么,并提供非常具体的错误代码.外部C API函数中的try-catch"包装器"只需要检索错误代码并将其返回给调用者.有关其他标准库异常,请参阅Laserallan的链接. (2认同)
  • catch(...){}纯粹是纯粹的邪恶.我唯一的遗憾是我只能投票一次. (2认同)
  • @Terry Mahaffey我绝对同意你的说法,这是邪恶的.最好是做Emile所建议的.但是如果你必须保证包装的代码永远不会抛出,你别无选择,只能在所有其他捕获的底部放置一个catch(...).情况就是这样,因为您要包装的库可能记录不清.您可以使用没有C++构造来强制只抛出一组异常.两个邪恶中较小的一个是什么?当包装的代码试图抛出C调用者时,catch(...)或冒运行时崩溃的风险? (2认同)

hha*_*fez 10

将C++代码暴露给C并不难,只需使用Facade设计模式即可

我假设您的C++代码内置于库中,您只需将C++库中的一个C模块作为库的Facade和纯C头文件.C模块将调用相关的C++函数

完成后,您的C应用程序和库将完全访问您公开的C api.

例如,这是一个示例Facade模块

#include <libInterface.h>
#include <objectedOrientedCppStuff.h>

int doObjectOrientedStuff(int *arg1, int arg2, char *arg3) {
      Object obj = ObjectFactory->makeCppObj(arg3); // doing object oriented stuff here
      obj->doStuff(arg2);
      return obj->doMoreStuff(arg1);
   }
Run Code Online (Sandbox Code Playgroud)

然后,您将此C函数公开为您的API,您可以将其作为C lib自由使用而不用担心

// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);
Run Code Online (Sandbox Code Playgroud)

显然这是一个人为的例子,但这是将C++库暴露给C的最简单方法


har*_*are 6

我认为你可能能够获得关于方向的一些想法和/或可能直接利用SWIG.我认为,通过一些示例,至少可以让您了解在将一个API包装到另一个API时需要考虑的事项.这项运动可能是有益的.

SWIG是一个软件开发工具,它将用C和C++编写的程序与各种高级编程语言连接起来.SWIG与不同类型的语言一起使用,包括常见的脚本语言,如Perl,PHP,Python,Tcl和Ruby.支持的语言列表还包括非脚本语言,如C#,Common Lisp(CLISP,Allegro CL,CFFI,UFFI),Java,Lua,Modula-3,OCAML,Octave和R.还有几种解释和编译的Scheme实现(支持Guile,MzScheme,Chicken).SWIG最常用于创建高级解释或编译的编程环境,用户界面,以及作为测试和原型化C/C++软件的工具.SWIG还可以以XML和Lisp s表达式的形式导出其解析树.SWIG可以自由使用,分发,

  • 这只是一个意见,不包含任何真正有用的反馈。如果原始代码满足以下条件,SWIG 将会有所帮助:快速变化、没有 C++ 资源来维护它并且只有 C 资源可用,并且开发人员想要自动生成 C API。这些是使用 SWIG 的常见且肯定有效的理由。 (3认同)
  • 如果他想做的就是从C开始提供C++库,那么SWIG就过度杀戮了. (2认同)