8

この質問は、「VS バージョン間で一貫性のある dll バイナリを作成する方法」に関連しています。

  • VC6 で構築されたアプリケーションと DLL と、VC9 で構築された新しいアプリケーションがあります。VC9 アプリは VC6 でコンパイルされた DLL を使用する必要があり、そのほとんどは C で記述され、1 つが C++ で記述されています。
  • C++ ライブラリは、名前の装飾/マングリングの問題により問題があります。
  • いくつかの副作用があるように見えるため、VC9 ですべてをコンパイルすることは現在のオプションではありません。これらを解決するには、かなりの時間がかかります。
  • C++ ライブラリを変更できますが、VC6 でコンパイルする必要があります。
  • C++ ライブラリは、本質的に別の C ライブラリの OO ラッパーです。VC9-app は、いくつかの非静的関数だけでなく、いくつかの静的関数も使用します。

静的関数は次のようなもので処理できますが

// Header file
class DLL_API Foo
{
    int init();
}

extern "C"
{
    int DLL_API Foo_init();
}

// Implementation file
int Foo_init()
{
    return Foo::init();
}

非静的メソッドではそれほど簡単ではありません。

私が理解しているように、COM のようなインターフェイスを使用するというChris Becke の提案は役に立ちません。なぜなら、インターフェイス メンバー名はまだ修飾されているため、別のコンパイラで作成されたバイナリからはアクセスできないからです。私はそこにいますか?

唯一の解決策は、オブジェクトへのハンドラーを使用して C スタイルの DLL インターフェイスを作成することですか、それとも何か不足していますか? その場合、おそらく、ラップされた C ライブラリを直接使用する方が労力がかからないでしょう。

4

5 に答える 5

9

The biggest problem to consider when using a DLL compiled with a different C++ compiler than the calling EXE is memory allocation and object lifetime.

I'm assuming that you can get past the name mangling (and calling convention), which isn't difficult if you use a compiler with compatible mangling (I think VC6 is broadly compatible with VS2008), or if you use extern "C".

Where you'll run into problems is when you allocate something using new (or malloc) from the DLL, and then you return this to the caller. The caller's delete (or free) will attempt to free the object from a different heap. This will go horribly wrong.

You can either do a COM-style IFoo::Release thing, or a MyDllFree() thing. Both of these, because they call back into the DLL, will use the correct implementation of delete (or free()), so they'll delete the correct object.

Or, you can make sure that you use LocalAlloc (for example), so that the EXE and the DLL are using the same heap.

于 2008-12-01T14:54:31.793 に答える
3

そうですね、クリス・ベッケの提案はいいと思います。ロジャーの最初のソリューションは使用しません。これは名前だけでインターフェイスを使用し、彼が言及しているように、抽象クラスと仮想メソッドの互換性のないコンパイラ処理の問題に遭遇する可能性があります。Roger は、後続ので魅力的な COM 一貫性のあるケースを指摘しています。

  1. 問題点: 少なくとも IUnknown:AddRef と IUnknown:Release に依存して、COM インターフェイス リクエストを作成し、IUnknown を適切に処理する方法を学ぶ必要があります。インターフェイスの実装が複数のインターフェイスをサポートできる場合、またはメソッドがインターフェイスを返すこともできる場合は、IUnknown:QueryInterface に慣れる必要がある場合もあります。

  2. これが重要なアイデアです。インターフェースの実装を使用する (しかしそれを実装しない) すべてのプログラムは、共通の #include "*.h" ファイルを使用して、インターフェースを構造体 (C) または C/C++ クラス (VC++) または構造体 (VC++ ではなく C++)。*.h ファイルは、C 言語プログラムと C++ 言語プログラムのどちらをコンパイルしているかに応じて、自動的に適切に適応します。*.h ファイルを使用するためだけに、その部分について知る必要はありません。*.h ファイルが行うことは、Interface 構造体または型、たとえば IFoo をその仮想メンバー関数で定義することです (関数のみであり、このアプローチではデータ メンバーへの直接的な可視性はありません)。

  3. ヘッダー ファイルは、使用する C++ コンパイラに関係なく、C で機能し、C++ で機能する方法で、COM バイナリ標準を尊重するように構築されています。(Java JNI 関係者はこれを理解しました。) これは、関数エントリ ポインター (vtable) で完全に構成される構造体がすべてのモジュールで同じようにメモリにマップされている限り、任意の起源の個別にコンパイルされたモジュール間で機能することを意味します。 (したがって、たとえば、すべて x86 32 ビット、またはすべて x64 である必要があります)。

  4. ある種のラッパー クラスを介して COM インターフェイスを実装する DLL では、ファクトリ エントリ ポイントのみが必要です。のようなもの

    extern "C" HRESULT MkIFooImplementation(void **ppv);

これは HRESULT を返し (それらについても学ぶ必要があります)、IFoo インターフェイス ポインターを受け取るために指定した場所に *pv も返します。(私はざっと目を通しているので、ここで必要となるより注意深い詳細があります。私の構文を信用しないでください) これに使用する実際の関数のステレオタイプも *.h ファイルで宣言されています。

  1. ポイントは、常に装飾されていない extern "C" であるファクトリ エントリが、必要なすべてのラッパー クラスの作成を行い、指定した場所に Ifoo インターフェイス ポインターを配信することです。つまり、クラスを作成するためのすべてのメモリ管理、クラスをファイナライズするためのすべてのメモリ管理などは、ラッパーをビルドする DLL で行われます。これは、これらの詳細に対処する必要がある唯一の場所です。

  2. ファクトリ関数から OK の結果を取得すると、インターフェイス ポインターが発行され、既に予約されています (配信されたインターフェイス ポインターに代わって暗黙の IFoo:Addref 操作が既に実行されています)。

  3. インターフェイスの操作が完了したら、インターフェイスの IFoo:Release メソッドを呼び出して解放します。ファクトリ DLL でクラスとそのインターフェイス サポートを破棄するのは、最終リリースの実装です (追加の AddRef コピーを作成した場合)。これにより、ファクトリ関数を含む DLL が呼び出しコードと同じライブラリを使用するかどうかに関係なく、インターフェイスの背後で一貫した動的ストレージ割り当てと解放に正しく依存することができます。

  4. 常に失敗する場合でも、おそらく IUnknown:QueryInterface (メソッド IFoo:QueryInterface として) も実装する必要があります。経験を積むにつれ、COM バイナリ インターフェイス モデルをより洗練されたものにしたい場合は、完全な QueryInterface 実装を提供する方法を学ぶことができます。

これはおそらく多すぎる情報ですが、異種の DLL の実装に関して直面している問題の多くは、COM バイナリ インターフェイスの定義で解決されていることを指摘したいと思います。有効なソリューションを提供するという事実は価値があります。私の経験では、このコツをつかめば、C++ と C++ の相互運用状況でこれがどれほど強力であるかを決して忘れることはありません。

*.h ファイルを作成し、共有したいライブラリのファクトリ関数ラッパーを実際に実装するために、例として参照する必要があるリソースや、学ぶ必要があることについてはスケッチしていません。さらに深く掘り下げたい場合は、叫んでください。

于 2008-12-02T02:49:18.573 に答える
3

インターフェイス メンバー名は装飾されません。それらは vtable 内の単なるオフセットです。したがって、ヘッダー ファイルでインターフェイスを定義できます (COM "インターフェイス" ではなく C 構造体を使用)。

struct IFoo {
    int Init() = 0;
};

次に、マングリングなしで DLL から関数をエクスポートできます。

class CFoo : public IFoo { /* ... */ };
extern "C" IFoo * __stdcall GetFoo() { return new CFoo(); }

互換性のある vtable を生成するコンパイラを使用している場合、これは問題なく機能します。Microsoft C++ は、(少なくとも、私が思うに) DOS 用の MSVC6.1 以降、同じ形式の vtable を生成しました。vtable は、関数へのポインターの単純なリストです (多重継承の場合はサンクを使用)。GNU C++ (私の記憶が正しければ) は、関数ポインターと相対オフセットを使用して vtables を生成します。これらは相互に互換性がありません。

于 2008-12-01T14:46:52.697 に答える
1

さまざまなライブラリでどのランタイムが使用されているかなど、他にも考慮する必要があることがあります。オブジェクトが共有されていない場合は問題ありませんが、一見したところ、そうではないように思えます。
Chris Becker の提案はかなり正確です。実際のCOM インターフェイスを使用すると、必要なバイナリ互換性を得るのに役立つ場合があります。あなたのマイレージは異なる場合があります :)

于 2008-12-01T14:44:40.393 に答える
0

楽しくないよ、男。あなたは多くのフラストレーションに直面しています。おそらくこれを与えるべきです:

唯一の解決策は、オブジェクトへのハンドラーを使用して C スタイルの DLL インターフェイスを作成することですか、それとも何か不足していますか? その場合、おそらく、ラップされた C ライブラリを直接使用する方が労力がかからないでしょう。

本当に間近で。幸運を。

于 2008-12-01T15:07:56.430 に答える