有几十个SO问题和博客文章描述了用C API包装C++类。用于C消耗的包装C++类API的示例
这些答案和博客文章中的大多数都是这样的:
typedef void* CMyClass;
但也有人说这很糟糕,因为它没有提供任何类型的安全性。他们提出了不透明结构的各种变体,没有任何解释。我可以复制上面的片段,继续我的生活(在此期间我会这样做(,但我想一劳永逸地知道
- 哪种形式最好
- 它对
void*
提供哪些保证 - 它是如何工作的
在C++中使用struct MyType
。
使用typedef struct MyType* pMyType;
作为常用句柄。
您的"C"API应该同时在C和C++中编译(在C++中使用extern "C"
包装器以获得正确的链接(。您将接近最大类型的安全性。
现在,struct MyHandle{void* private_ptr;};
是另一个选项:这避免了将C++类型的名称暴露给C。只要将与private_ptr
的直接交互隔离为少数函数,它在其他任何地方都是类型安全的。
void *
的问题在于,它无法防止意外分配不兼容的指针。
typedef void *CMyClass;
int i = 1;
CMyClass c = &i; // No complaints
如果您将def改为某个唯一的不透明类型,编译器将帮助您。
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 *'
首先,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};
// ...
}
}