オブジェクト指向C ++コード用のCラッパーAPIの開発


81

私は、既存のC ++ APIをラップアラウンドしてコアロジック(オブジェクト指向C ++で記述)にアクセスする一連のCAPIを開発しようとしています。これは基本的に、C ++ロジックを他の言語で使用できるようにする接着剤APIになります。オブジェクト指向C ++の周りにCをラップすることに関連する概念を紹介するいくつかの良いチュートリアル、本、またはベストプラクティスは何ですか?


4
インスピレーションを得るためにzeromqソースをチェックしてください。ライブラリは現在C ++で記述されており、Cバインディングがあります。zeromq.org
Hassan Syed

回答:


70

これは手作業で行うのはそれほど難しいことではありませんが、インターフェイスのサイズによって異なります。私がそれを行ったのは、純粋な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インターフェイスが得られます。例外処理を統合できる1つの方法を示すより完全な例が必要な場合は、githubで私のコードを試すことができます:https//gist.github.com/mikeando/5394166

楽しい部分は、必要なすべてのC ++ライブラリをより大きなライブラリに正しくリンクできるようにすることです。gcc(またはclang)の場合、これはg ++を使用して最終リンクステージを実行することを意味します。


11
返されたオブジェクトには、void *の代わりに匿名の構造体など、void以外のものを使用することをお勧めします。これにより、返却されたハンドルにある種のタイプセーフティを与えることができます。チェックアウトstackoverflow.com/questions/839765/...をそれについての詳細は。
Laserallan 2010年

3
Laserallanに同意し、それに応じてコードをリファクタリングしました
Michael Anderson

2
@Mike Weller extern "C"ブロック内のnewおよびdeleteは問題ありません。extern "C"は、名前マングリングにのみ影響します。Cコンパイラはそのファイルを見ることはなく、ヘッダーだけを見ます。
Michael Anderson

2
また、すべてをCでコンパイルするために必要なtypedefも見逃しました。奇妙なtypdef struct Foo Foo; "ハック"。コードが更新されました
Michael Anderson

5
@MichaelAnderson、あなたmyStruct_destroymyStruct_doSomething関数には2つのタイプミスがあります。する必要がありますreinterpret_cast<MyClass*>(v)
firegurafiku 2014年

17

Michael Andersonの答えは正しい方向に進んでいると思いますが、私のアプローチは異なります。あなたはもう一つのことを心配しなければなりません:例外。例外はCABIの一部ではないため、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ははるかに安全です。

しかし、マイケルが述べたように、リンクはかなりトリッキーになるかもしれません。

お役に立てれば


2
この場合の例外処理についての詳細は以下のスレッドを見てみましょう:stackoverflow.com/questions/847279/...
Laserallan

2
C ++ライブラリにもCAPIが含まれることがわかっている場合は、APIエラーコードintを例外基本クラス内にカプセル化します。投球場所で正確なエラー状態が何であるかを知り、非常に具体的なエラーコードを提供する方が簡単です。外部CAPI関数のtry-catch「ラッパー」は、エラーコードを取得して、呼び出し元に返すだけです。その他の標準ライブラリの例外については、Laserallanのリンクを参照してください。
Emile Cormier 2010年

2
catch(...){}は純粋な純粋な悪です。私の唯一の後悔は、私が反対票を投じることができるのは一度だけだということです。
Terry Mahaffey 2010年

2
@TerryMahaffey私はそれが悪であることに絶対に同意します。最善は、エミールが提案したことを行うことです。ただし、ラップされたコードがスローされないことを保証する必要がある場合は、識別された他のすべてのキャッチの下部にキャッチ(...)を配置する以外に選択肢はありません。これは、ラップしているライブラリが十分に文書化されていない可能性があるためです。一連の例外のみがスローされるように強制するために使用できるC ++構造はありません。2つの悪のうちの小さい方は何ですか?ラップされたコードがC呼び出し元にスローしようとすると、キャッチ(...)または実行時クラッシュのリスクがありますか?
figurassa 2010年

1
catch(...){std :: terminate(); }は許容されます。catch(...){}は潜在的なセキュリティホールです
Terry Mahaffey 2010年

10

C ++コードをCに公開することは難しくありません。ファサード・デザイン・パターンを使用するだけです。

C ++コードがライブラリに組み込まれていると仮定します。必要なのは、純粋なCヘッダーファイルとともに、ライブラリのファサードとしてC ++ライブラリに1つのCモジュールを作成することだけです。Cモジュールは関連するC ++関数を呼び出します

これを行うと、Cアプリケーションとライブラリは公開したCAPIに完全にアクセスできるようになります。

たとえば、これはサンプルのファサードモジュールです

#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簡単なHelloWorldの例はありますか?文字列のあるもの?
Jason Foglia 2016

非CPPの男のために、これは素敵です
ニクラス・アベン

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から利用できるようにすることであるならばSWIGは、ただキルを超えている
hhafez

1
それは意見であり、実際に役立つフィードバックは含まれていません。SWIGは、元のコードが次の場合に役立ちます。急速に変化する、それを維持するためのC ++リソースはなく、使用可能なCリソースのみ、および開発者がCAPI生成を自動化する場合。これらは、SWIGを使用する一般的で確かに正当な理由です。
user1363990 2018

5

オブジェクトの概念をvoid *(C指向のライブラリでは不透明(OPAQUE)型と呼ばれることが多い)に置き換えて、C ++で知っているすべてのものを再利用するだけです。


2

SWIGを使用することが最善の答えだと思います...車輪の再発明を回避するだけでなく、信頼性が高く、問題を解決するのではなく、開発の継続性を促進します。

高周波の問題は、長期的な解決策によって対処する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.