web-dev-qa-db-ja.com

C消費のためのC ++クラスAPIのラッピング

C/FFIライブラリで簡単に使用できるようにDLLからラップしてエクスポートする必要がある、関連するC++クラスのセットがあります。いくつかの「ベスト」を探しています。これを行うためのプラクティス」。たとえば、オブジェクトを作成して解放する方法、基本クラスを処理する方法、代替ソリューションなど。

私がこれまでに持っているいくつかの基本的なガイドラインは、メソッドを、デストラクタを含む「this」ポインタを表す追加のvoid *引数を持つ単純な関数に変換することです。コンストラクターは元の引数リストを保持できますが、オブジェクトを表すポインターを返す必要があります。すべてのメモリは、プロセス全体の割り当てと空きルーチンの同じセットを介して処理する必要があり、マクロまたはその他の方法で、ある意味でホットスワップ可能である必要があります。

33
Exponent

パブリックメソッドごとに、C関数が必要です。
Cコードでクラスを表すための不透明なポインターも必要です。
void *やその他の情報を含む構造体を作成することもできますが(たとえば、配列をサポートしたい場合)、void *を使用する方が簡単です。

Fred.h
--------------------------------

#ifdef  __cplusplus
class Fred
{
    public:
    Fred(int x,int y);
    int doStuff(int p);
};
#endif

//
// C Interface.
typedef void*   CFred;

//
// Need an explicit constructor and destructor.
extern "C" CFred  newCFred(int x,int y);
extern "C" void   delCFred(CFred);

//
// Each public method. Takes an opaque reference to the object
// that was returned from the above constructor plus the methods parameters.
extern "C" int    doStuffCFred(CFred,int p);

実装は簡単です。
不透明なポインターをFredに変換してから、メソッドを呼び出します。

CFred.cpp
--------------------------------

// Functions implemented in a cpp file.
// But note that they were declared above as extern "C" this gives them
// C linkage and thus are available from a C lib.
CFred newCFred(int x,int y)
{
    return reinterpret_cast<void*>(new Fred(x,y));
}

void delCFred(CFred fred)
{
    delete reinterpret_cast<Fred*>(fred);
}

int doStuffCFred(CFred fred,int p)
{
    return reinterpret_cast<Fred*>(fred)->doStuff(p);
}
31
Martin York

Loki Astariの答えは非常に良いですが、彼のサンプルコードは、ラッピングコードをC++クラス内に配置しています。私はラッピングコードを別のファイルに入れることを好みます。また、ラッピングC関数の前にクラス名を付ける方が良いスタイルだと思います。

次のブログ投稿は、その方法を示しています。 http://blog.eikke.com/index.php/ikke/2005/11/03/using_c_classes_in_c.html

ブログが放棄され、最終的に消滅する可能性があるため、重要な部分をコピーしました(Ikkeのブログへのクレジット):


まず、1つのヘッダーファイル(Test.hh)を使用するC++クラスが必要です。

class Test {
    public:
        void testfunc();
        Test(int i);

    private:
        int testint;
};

および1つの実装ファイル(Test.cc)

#include <iostream>
#include "Test.hh"

using namespace std;

Test::Test(int i) {
    this->testint = i;
}

void Test::testfunc() {
    cout << "test " << this->testint << endl;
}

これは単なる基本的なC++コードです。

次に、グルーコードが必要です。このコードは、CとC++の中間にあります。ここでも、ヘッダーファイルが1つあります(TestWrapper.h、C++コードが含まれていないため.hのみ)

typedef void CTest;

#ifdef __cplusplus
extern "C" {
#endif

CTest * test_new(int i);
void test_testfunc(const CTest *t);
void test_delete(CTest *t);
#ifdef __cplusplus
}
#endif

および関数の実装(C++コードが含まれているためTestWrapper.cc、.cc):

#include "TestWrapper.h"
#include "Test.hh"

extern "C" {

    CTest * test_new(int i) {
        Test *t = new Test(i);

        return (CTest *)t;
    }

    void test_testfunc(const CTest *test) {
        Test *t = (Test *)test;
        t->testfunc();
    }

    void test_delete(CTest *test) {
        Test *t = (Test *)test;

        delete t;
    }
}
18
codingFriend1

まず、すべてのメソッドをC関数に変換する必要がない場合があります。 APIを単純化し、C++インターフェイスの一部を非表示にできる場合は、C++ロジックを変更するときにCAPIを変更する機会を最小限に抑えることができるため、より優れています。

したがって、そのAPIを介して提供されるより高いレベルの抽象化について考えてみてください。説明したvoid *ソリューションを使用してください。私には最も適切に見えます(またはHANDLEとしてtypedef void * :))。

4

私の経験からのいくつかの意見:

  • 関数は、エラーを表すコードを返す必要があります。エラーの説明を文字列形式で返す関数があると便利です。他のすべての戻り値はoutパラメーターである必要があります。

例えば。:

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget);
  • ハンドルの有効性をチェックするために、ハンドルポインタを指す構造/クラスに署名を入れます。

例えば。関数は次のようになります。

C_ERROR BuildWidget(HUI ui, HWIDGET* pWidget){
    Ui* ui = (Ui*)ui;
    if(ui.Signature != 1234)
    return BAD_HUI;
}
  • DLLと消費するアプリは異なる可能性があるため、DLLからエクスポートされた関数を使用してオブジェクトを作成および解放する必要があります。

例えば。:

C_ERROR CreateUi(HUI* ui);
C_ERROR CloseUi(HUI hui); // usually error codes don't matter here, so may use void
  • ライブラリの外部で永続化する必要がある可能性のあるバッファまたはその他のデータにメモリを割り当てる場合は、このバッファ/データのサイズを指定してください。このようにして、ユーザーは内部をハッキングして実際のサイズを見つけることなく、ディスク、DB、または好きな場所に保存できます。それ以外の場合は、最終的に、ユーザーがデータを既知のサイズのバイト配列に変換するためにのみ使用する独自のファイルI/OAPIを提供する必要があります。

例えば。:

C_ERROR CreateBitmap(HUI* ui, SIZE size, char** pBmpBuffer, int* pSize);
  • オブジェクトにC++ライブラリの外部に典型的な表現がある場合は、この表現に変換する手段を提供します(たとえば、クラスImageがあり、HIMGハンドルを介してアクセスできる場合は、関数を提供します)たとえば、windows HBITMAPとの間で変換します)。これにより、既存のAPIとの統合が簡素化されます。

例えば。

C_ERROR BitmapToHBITMAP(HUI* ui, char* bmpBuffer, int size, HBITMAP* phBmp);
3
elder_george

ベクトル(およびstring :: c_str)を使用して、C++以外のAPIとデータを交換します。 (C++コーディング標準、H。Sutter/ A. Alexandrescuのガイドライン#78)。

PS「コンストラクターが元の引数リストを保持できる」というのは真実ではありません。これは、C互換の引数タイプにのみ当てはまります。

PS2もちろん、 Cătălin を聞いて、インターフェイスをできるだけ小さくシンプルに保ちます。

2
Daniel Daranas

これは興味深いかもしれません: "CとC++の混合" C++でFAQ Lite。具体的には [32.8]どうすればC関数との間のC++クラス?

2
Doug T.