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

时间:2010-01-11 23:47:24

标签: c++ c wrapper

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

6 个答案:

答案 0 :(得分:63)

手动操作并不难,但取决于界面的大小。 我完成它的情况是允许在纯C代码中使用我们的C ++库,因此SWIG没有太大帮助。 (好吧也许SWIG可以用来做到这一点,但我不是SWIG大师,它似乎并非无足轻重)

我们最终做的就是:

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

    class MyClass
    {
      public:
      explicit MyClass( std::string & s );
      ~MyClass();
      int doSomething( int j );
    }
    

    会映射到这样的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 );
    

    接口的实现看起来像这样(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);
      }
    }
    

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

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

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

答案 1 :(得分: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

你的包装器.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;
}

更好的是:如果您知道所有需要作为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;
}

此API更安全。

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

希望这有帮助

答案 2 :(得分:9)

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

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

// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);

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

答案 3 :(得分: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可以免费使用,分发和修改,用于商业和非商业用途。

答案 4 :(得分:4)

只需用void *替换对象的概念(通常在面向C的库中称为opaque类型),并重用从C ++中知道的所有内容。

答案 5 :(得分:2)

我认为使用SWIG是最好的答案......它不仅可以避免重新发明轮子,而且可靠,并且可以促进发展的连续性,而不是一个人解决问题。

长期解决方案需要解决高频问题。