C++クラスをCAPIでラップすることを説明するSO質問とブログ投稿が数十あります。例 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
代わりに、一意の不透明(OPAQUE)型に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 *'
まず、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};
// ...
}
}