我希望开发一组 C API,封装我们现有的 C++ API 以访问我们的核心逻辑(使用面向对象的 C++ 编写)。这将是一个粘合 API,允许其他语言使用我们的 C++ 逻辑。有哪些好的教程、书籍或最佳实践介绍将 C 封装在面向对象的 C++ 环境中的概念?
这并不太难手动完成,但取决于您接口的大小。我遇到过的情况是为了使我们的C++库能够在纯C代码中使用,因此SWIG没有什么帮助。(也许SWIG可以用来做到这一点,但我不是SWIG大师,而且它似乎不容易)
我们最终所做的就是:
因此,像这样的类(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++进行最终链接阶段即可。myStruct_destroy
和myStruct_doSomething
函数中有两个错别字。应该是reinterpret_cast<MyClass*>(v)
。 - firegurafiku我认为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的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 更加安全。
但是,正如 Michael 提到的那样,链接可能会变得非常棘手。
希望这可以帮到你。
将C++代码暴露给C语言并不困难,只需使用外观设计模式。
我假设你的C++代码已经构建成一个库,你需要做的就是在C++库中创建一个C模块,作为库的外观,并提供一个纯C头文件。该C模块将调用相关的C++函数。
完成上述步骤后,C应用程序和库将完全可以访问你所暴露的C API。
例如,这里是一个外观模块的示例:
#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的最简单的方法。
我认为您可能可以从方向上获得一些想法,或者可能直接使用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可以自由地用于商业和非商业用途。
只需要用void *
(在面向C的库中通常称为不透明类型)替换对象的概念,然后重复使用你从C++中学到的一切即可。