有数十个SO问题和博客文章描述了用C API包装C ++类。示例Wrapping C++ class API for C consumption
这些答案和博文中的大多数都是这样的:
typedef void* CMyClass;
但是其他人说这很不好,因为它不提供类型安全性。他们提出了不透明结构的各种变体,没有任何解释。我可以复制上面的代码片段,然后继续我的生活(同时我将继续这样做),但我想一劳永逸地知道
void*
的出价?答案 0 :(得分:8)
在C ++中使用struct MyType
。
使用typedef struct MyType* pMyType;
作为常见句柄。
您的“ C” API应同时在C和C ++中进行编译(在C ++中使用extern "C"
包装器以获取正确的链接)。这样您将接近最大类型安全性。
现在,struct MyHandle{void* private_ptr;};
是另一个选择:避免将C ++类型的名称公开给C。而且,只要将与private_ptr
的直接交互隔离到少数几个函数中,它将在其他任何地方键入安全。
答案 1 :(得分:7)
void *
的问题在于,它无法避免意外分配不兼容的指针。
typedef void *CMyClass;
int i = 1;
CMyClass c = &i; // No complaints
如果您将typedef键入某些唯一的不透明类型,则编译器将为您提供帮助。
typedef struct MyClass *CMyClass;
int i = 1;
CMyClass c = &i; // BOOM!
我认为在C语言中这不是错误,但是Clang 6.0会警告我(即使未启用任何警告)
warning: incompatible pointer types initializing 'CMyClass' (aka 'struct MyClass *') with an expression of type 'int *'
答案 2 :(得分:1)
第一件事是void *
确实不是一个好选择,因为它通过静默接受任何不相关的指针,使API更加容易出错。因此,更好的主意是向某些结构添加前向声明并接受指向该结构的指针:
#ifdef __cplusplus
extern "C"
{
#endif
struct CMyClassTag;
typedef struct CMyClassTag CMyClass;
void CMyClass_Work(CMyClass * p_self);
#ifdef __cplusplus
}
#endif
下一步是通过将指针隐藏为不必要的实现细节来明确告诉用户该指针是不透明的,并且不应取消对该指针的引用:
typedef struct CMyClassTag * CMyClassHandle;
void CMyClass_Work(CMyClassHandle h_my_class);
此外,不是依靠用户正确地使用此接口,而是可以使真正的句柄类型而不是不透明的指针。这可以通过几种方式完成,但是主要思想是传递一些晦涩的整数标识符,并在运行时在库侧执行从其到实指针的映射:
typedef uintptr_t CMyClassHandle;
void CMyClass_Work(CMyClassHandle h_my_class);
// impl
void CMyClass_Work(CMyClassHandle h_my_class)
{
auto it{s_instances_map.find(h_my_class)};
if(s_instances_map.end() != it)
{
auto & self{it->second};
// ...
}
}