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


81

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


4
查看zeromq来源以获取灵感。该库当前使用C ++编写,并具有C绑定。zeromq.org
哈桑·赛义德

1
相关(甚至重复):包装C消耗的C ++类API
用户

Answers:


69

手工很难做到这一点,但要取决于您界面的大小。我这样做的情况是允许在纯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 );
}

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

我们从原始类派生不透明的句柄,以避免进行任何强制转换,并且(这似乎不适用于我当前的编译器)。由于C不支持类,我们必须将句柄设为结构。

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

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


11
我建议您使用void以外的其他东西,例如,对于返回的对象,使用匿名结构而不是void *。这可以为返回的句柄提供某种类型的安全性。请查看stackoverflow.com/questions/839765/…以获得有关它的更多信息。
Laserallan'1

3
我同意Laserallan,并已相应地重构了我的代码
Michael Anderson 2010年

2
@Mike Weller extern“ C”块中的new和delete很好。外部“ C”仅影响名称修饰。C编译器永远不会看到该文件,只会看到标头。
Michael Anderson 2010年

2
我也错过了用C进行编译所需的typedef。奇怪的typdef结构Foo Foo。“骇客”。代码更新
迈克尔·安德森

5
@MichaelAnderson,您的myStruct_destroymyStruct_doSomething函数中有两种错别字。应该是reinterpret_cast<MyClass*>(v)
firegurafiku 2014年

17

我认为迈克尔·安德森(Michael Anderson)的答案是正确的,但我的做法会有所不同。您必须担心另外一件事:异常。异常不是C ABI的一部分,因此您不能让异常抛出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的风险。做这样的事情:

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
欲了解更多有关异常处理这种情况看看下面的线程:stackoverflow.com/questions/847279/...
Laserallan

2
当我知道我的C ++库还将有一个C API时,我将API错误代码int封装在我的异常基类中。在投掷站点更容易知道确切的错误情况,并提供非常特定的错误代码。外部C API函数中的try-catch“包装器”只需要检索错误代码并将其返回给调用者。有关其他标准库例外,请参阅Laserallan的链接。
Emile Cormier 2010年

2
catch(...){}是纯净的邪恶。我唯一的遗憾是我只能投票一次。
Terry Mahaffey 2010年

2
@Terry Mahaffey我完全同意你的看法,这是邪恶的。最好的方法是按照Emile的建议去做。但是,如果必须保证包装的代码永远不会抛出,则别无选择,只能在所有已识别的其他捕获的底部放置一个捕获(...)。之所以如此,是因为您要包装的库可能记录不佳。没有可用于强制执行仅抛出一组异常的C ++构造。两个邪恶中的较小者是什么?在包装的代码试图扔给C调用者时捕获(...)或冒着运行时崩溃的风险?
figurassa 2010年

1
catch(...){std :: terminate(); }是可以接受的。catch(...){}是一个潜在的安全漏洞
Terry Mahaffey,2010年

10

将C ++代码公开给C并不难,只需使用Facade设计模式

我假设您的C ++代码已内置到库中,您需要做的就是在C ++库中创建一个C模块,作为与纯C头文件一起使用的库的Facade。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库,而不必担心

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

显然,这是一个人为的示例,但这是将C ++库公开给C的最简单方法


嗨@hhafez,您有一个简单的hello world示例吗?一个带琴弦?
杰森·福利亚

对于非cpp的家伙来说,这很可爱
NicklasAvén17年

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可以自由使用,分发,


2
痛饮只是过来杀掉,如果一切他想做的就是让C ++库可从C.
hhafez

1
多数民众赞成的意见,不包含任何实际有用的反馈。如果原始代码在以下方面,SWIG会有所帮助:快速更改,没有C ++资源来维护它,只有C资源可用以及开发人员希望自动化C API生成。这些是使用SWIG的常见且肯定是有效的原因。
user1363990


2

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

高频问题需要长期解决。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.