web-dev-qa-db-ja.com

CからC ++をエレガントに呼び出す

いくつかのプロジェクトをプレーンC(C99)で開発します。ただし、C++にはソースコードとして1つのライブラリ(数学ライブラリ)があります。このライブラリが必要なので、このソースコードを統合する最もエレガントな方法は何ですか?

CC++のサイズの比率は20:1であるため、C++への移行はオプションではありません。静的ライブラリを使用する必要がありますか? DLL? (それはすべてWindows上にあります)。

47
Cartesius00

編集:コメントの議論に基づいて、物事をC互換の_struct duck_と派生した_class Duck_に分けることはおそらく不要であることを指摘すべきです。おそらく、実装を安全に_struct duck_にシャベルし、_class Duck_を排除して、real(…)を不要にすることができます。しかし、私はこれについて決定的な答えを提供するのに十分なほどC++をよく知りません(特に、Cユニバースと相互作用する方法)。


すべてのCおよびC++コードを単一のバイナリに単純にリンクできない理由はありません。

C++コードとのインターフェイスを使用するには、C APIでC++ APIをラップする必要があります。これを行うには、C++コードをコンパイルするときに_extern "C" { ... }_内の一連の関数を宣言し、Cクライアントコードをコンパイルするときにextern宣言を使用しません。例えば。:

_#ifdef __cplusplus
extern "C" {
#endif

typedef struct duck duck;

duck* new_duck(int feet);
void delete_duck(duck* d);
void duck_quack(duck* d, float volume);

#ifdef __cplusplus
}
#endif
_

C++ソースでduck構造体を定義し、そこから実際のDuckクラスを継承することもできます。

_struct duck { };

class Duck : public duck {
public:
    Duck(int feet);
    ~Duck();

    void quack(float volume);
};

inline Duck* real(duck* d) { return static_cast<Duck*>(d); }

duck* new_duck(int feet) { return new Duck(feet); }
void delete_duck(duck* d) { delete real(d); }
void duck_quack(duck* d, float volume) { real(d)->quack(volume); }
_
55
Marcelo Cantos

Duck構造体から継承する唯一の理由は、一部をC APIの属性に公開することです。これは、とにかく一般的に悪いスタイルと見なされます。継承がない場合、Cヘッダーは次のようになります。

struct Duck;

struct Duck* new_Duck(int feet);
void delete_Duck(struct Duck* d);
void Duck_quack(struct Duck* d, float volume);

これは対応する実装であり、型キャストの必要はありません。

extern "C" {
#include "Duck.h"
}

class Duck {
public:
    Duck(int feet) : {}
    ~Duck() {}

    void quack(float volume) {}
};

struct Duck* new_Duck(int feet) { return new Duck(feet); }
void delete_Duck(struct Duck* d) { delete d; }
void Duck_quack(struct Duck* d, float volume) { d->quack(volume); }

同様に、C APIは、C++インターフェイス(純粋な仮想クラス)とその実装用に作成できます。その場合、コンストラクターのみが具体的な実装に基づいている必要があります(例:new_RubberDuck(2))。デストラクタと他のすべての関数は、C++と同じように、正しい実装で自動的に動作します。

7
A.Robert

C++数学ライブラリは、ユーティリティクラス(静的メンバーのみ)に実装することができます。この場合、はるかに単純なアプローチをとることができます。

class FPMath {
public:
    static double add(double, double);
    static double sub(double, double);
    static double mul(double, double);
    static double div(double, double);
};

Cインターフェイスのヘッダーは次のようになります。

double FPMath_add(double, double);
double FPMath_sub(double, double);
double FPMath_mul(double, double);
double FPMath_div(double, double);

対応する実装は次のとおりです。

double FPMath_add(double a, double b) { return FPMath::add(a, b); }
double FPMath_sub(double a, double b) { return FPMath::sub(a, b); }
double FPMath_mul(double a, double b) { return FPMath::mul(a, b); }
double FPMath_div(double a, double b) { return FPMath::div(a, b); }

しかし、これは明白なことを述べているかもしれません....

7
A. Robert

isいくつかのオブジェクトのメンバー関数を直接呼び出すことができる「ハック」を作成する方法。

最初に行う必要があるのは、extern "C"ファクトリー関数。ポインターを返します(void*)オブジェクトに。

2番目に必要なのは、メンバー関数のマングル名です。

次に、マングルされた名前を使用して関数を呼び出し、最初の引数としてファクトリー関数から返されたポインターを渡すことができます。

警告:

  • もちろん、他のオブジェクト、参照、または他のC++のもの、またはC型と互換性のないオブジェクトまたは型を返す関数を必要とするメンバー関数の呼び出しは機能しません。
  • 仮想メンバー関数では動作せず、仮想関数が呼び出されていなくても、仮想関数を含むオブジェクトではおそらく動作しません
  • マングルされた名前は有効なCシンボルでなければなりません
  • さらに多く...

これは私が推奨するものではなく、実際はまったく逆です。この回答に記載されているようなことをしないことを強くお勧めします。サポートされておらず、おそらく未定義の動作であり、奇妙で予測不可能な方法で破損する可能性があります。