web-dev-qa-db-ja.com

オブジェクト、特にSTLオブジェクトをDLLと安全にやり取りするにはどうすればよいですか?

クラスオブジェクト、特にSTLオブジェクトをC++ DLLとやり取りするにはどうすればよいですか?

私のアプリケーションはDLLファイルの形式でサードパーティのプラグインと対話する必要があり、これらのプラグインがどのコンパイラーでビルドされているかを制御することはできません。 STLオブジェクト。アプリケーションが不安定になることを心配しています。

99
computerfreaker

@computerfreakerは、タイプ定義がユーザーの制御下にあり、両方でまったく同じトークンシーケンスが使用されている場合でも、ABIがないために一般的な場合にDLL境界を越えてC++オブジェクトを渡すことができない理由について素晴らしい説明を書いていますプログラム。 (機能する2つのケースがあります:標準レイアウトクラスと純粋なインターフェイス)

C++標準で定義されたオブジェクトタイプ(標準テンプレートライブラリから適合したものを含む)の場合、状況ははるかに悪くなります。これらの型を定義するトークンは、複数のコンパイラで同じではありません。C++標準では完全な型定義は提供されず、最小要件のみが提供されるためです。さらに、これらの型定義に表示される識別子の名前検索では、同じ名前が解決されません。 C++ ABIが存在するシステムでも、モジュールの境界を越えてそのようなタイプを共有しようとすると、1つの定義ルール違反により、大規模な未定義の動作が発生します。

G ++のlibstdc ++は事実上の標準であり、事実上すべてのプログラムがそれを使用しているため、Linuxプログラマーが対処することに慣れていなかったため、ODRを満たします。 clangのlibc ++はその前提を破り、C++ 11はほぼすべての標準ライブラリタイプに対する必須の変更を伴いました。

モジュール間で標準ライブラリタイプを共有しないでください。未定義の動作です。

17
Ben Voigt

ここでの回答のいくつかは、C++クラスの受け渡しが本当に怖いように聞こえますが、別の観点を共有したいと思います。他のいくつかの応答で言及されている純粋な仮想C++メソッドは、実際にはあなたが思っているよりもきれいであることがわかりました。コンセプトに基づいてプラグインシステム全体を構築しましたが、何年も非常にうまく機能しています。 LoadLib()とGetProcAddress()を使用して、指定されたディレクトリからDLLを動的にロードする「PluginManager」クラスがあります(Linuxの同等物なので、実行可能ファイルをクロスプラットフォームにします)。

信じられないかもしれませんが、このメソッドは、純粋な仮想インターフェイスの最後に新しい関数を追加し、その新しい関数なしでインターフェイスに対してコンパイルされたdllをロードしようとするような奇妙なことをしても、寛容です-それらはうまくロードされます。もちろん...バージョン番号をチェックして、実行可能ファイルがその関数を実装する新しいdllの新しい関数のみを呼び出すことを確認する必要があります。しかし、良いニュースは、次のとおりです。ある意味では、時間の経過とともにインターフェイスを進化させる粗雑な方法があります。

純粋な仮想インターフェイスに関するもう1つのクールな点-必要な数のインターフェイスを継承でき、ダイヤモンドの問題に遭遇することはありません!

このアプローチの最大の欠点は、パラメーターとして渡す型について非常に注意しなければならないことです。最初に純粋な仮想インターフェイスでラップしないクラスやSTLオブジェクトはありません。構造体はありません(プラグマパックのブードゥーを経ることなく)。単なるプリミティブ型と他のインターフェイスへのポインタ。また、関数をオーバーロードすることはできません。これは不便ですが、ショーストッパーではありません。

幸いなことに、数行のコードで、再利用可能な汎用クラスとインターフェイスを作成して、STL文字列、ベクター、およびその他のコンテナクラスをラップできます。または、GetCount()やGetVal(n)などの関数をインターフェイスに追加して、リストをループできるようにすることもできます。

私たちのためにプラグインを作成している人は、それが非常に簡単だと感じています。 ABIの境界などの専門家である必要はありません。関心のあるインターフェイスを継承し、サポートする機能をコーディングし、サポートしていない機能についてはfalseを返します。

私が知る限り、このすべてを実現するテクノロジーは、どの標準にも基づいていません。私が収集したものから、MicrosoftはCOMを作成できるように仮想テーブルをそのように行うことを決定し、他のコンパイラライターはこれに従うことにしました。これには、GCC、Intel、Borland、および他のほとんどの主要なC++コンパイラが含まれます。あいまいな組み込みコンパイラの使用を計画している場合、このアプローチはおそらく機能しません。理論的には、どのコンパイラー会社もいつでも仮想テーブルを変更して事態を壊すことができますが、このテクノロジーに依存する長年にわたって書かれた大量のコードを考慮すると、主要なプレーヤーのいずれかがランクを破ることを決めた場合、私は非常に驚くでしょう。

話の教訓は...いくつかの極端な状況を除いて、ABI境界がプリミティブ型でクリーンな状態を維持し、過負荷を回避できることを確認できるインターフェイスの担当者が1人必要です。その規定に問題がなければ、コンパイラ間でDLL/SOのクラスへのインターフェイスを共有することを恐れません。クラスを直接共有することは==トラブルですが、純粋な仮想インターフェイスを共有することはそれほど悪くはありません。

15
Ph0t0n

すべてのモジュール(.EXEおよび.DLL)が同じC++コンパイラバージョンと同じ設定およびCRTのフレーバーでビルドされていない限り、DLL境界を越えてSTLオブジェクトを安全に渡すことはできません。非常に制約的であり、明らかにあなたの場合ではありません。

DLLからオブジェクト指向のインターフェイスを公開する場合は、C++の純粋なインターフェイスを公開する必要があります(これはCOMと同じです)。 CodeProjectに関するこの興味深い記事を読むことを検討してください。

HowTo:C++クラスをDLLからエクスポートする

また、DLL境界で純粋なCインターフェイスを公開し、呼び出し元サイトでC++ラッパーを構築することを検討することもできます。
これは、Win32で起こることと似ています。Win32実装コードはほとんどC++ですが、多くのWin32 APIが純粋なCインターフェイスを公開します(COMインターフェイスを公開するAPIもあります)。次に、ATL/WTLおよびMFCは、これらの純粋なCインターフェイスをC++クラスおよびオブジェクトでラップします。

8
Mr.C64