web-dev-qa-db-ja.com

dllからstd ::オブジェクト(ベクター、マップなど)を含むクラスをエクスポートする

Std :: vectorsやstd :: stringsなどのオブジェクトを含むDLLからクラスをエクスポートしようとしています-クラス全体が以下を介してdllエクスポートとして宣言されています:

    class DLL_EXPORT FontManager
{

問題は、複合型のメンバーに対して次の警告が表示されることです。

警告C4251: 'FontManager :: m__fonts':クラス 'std :: map <_Kty、_Ty>'は、[_Kty = std :: string、_Ty = tFontInfoRefを含むクラス 'FontManager'のクライアントが使用するdllインターフェイスを持つ必要があります]

メンバー変数自体の型は変更していませんが、次のフォワードクラス宣言を警告の前に置くことで、いくつかの警告を削除できます。

template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
template class DLL_EXPORT std::vector<tCharGlyphProviderRef,std::allocator<tCharGlyphProviderRef> >;
std::vector<tCharGlyphProviderRef> m_glyphProviders;

前方宣言は、メンバーがコンパイルされるときにDLL_EXPORTを「注入」するように見えますが、安全ですか?クライアントがこのヘッダーをコンパイルし、自分の側でstdコンテナーを使用すると、本当に何か変更されますか?そのようなコンテナDLL_EXPORTの将来の使用をすべて行いますか(インラインではないかもしれません)?そして、それは警告が警告しようとする問題を本当に解決しますか?

この警告は私が心配する必要があるものですか、これらの構成の範囲内で無効にするのが最善ですか?クライアントとdllは常に同じライブラリとコンパイラのセットを使用して構築され、それらはヘッダーのみのクラスです...

Visual Studio 2003と標準STDライブラリを使用しています。

----アップデート----

答えが一般的であることがわかりますが、ここでは、stdコンテナとタイプ(std :: stringなど)について説明していますが、実際の問題は次のとおりです。

同じライブラリヘッダーを介してクライアントとdllの両方で使用できる標準のコンテナと型の警告を無効にし、intまたは他の組み込み型と同様に処理できますか? (それは私の側で正しく動作するようです。)もしそうなら、私たちがこれを行うことができる条件でしょうか?

または、そのようなコンテナの使用を禁止するか、割り当て演算子、コピーコンストラクタなどがdllクライアントにインライン化されないように少なくとも超注意する必要がありますか?

一般に、そのようなオブジェクトを持つdllインターフェイスを設計すること(およびたとえば、戻り値型としてクライアントにものを返すためにそれらを使用する)が良いアイデアかどうか、そしてなぜかを知りたいと思います-私は持ってみたいこの機能への「高レベル」インターフェース...おそらく最良の解決策は、ニール・バターワースが提案したものです-静的ライブラリを作成しますか?

58
RnR

クライアントからクラスのメンバーに触れるときは、DLLインターフェイスを提供する必要があります。 DLLインターフェースとは、コンパイラーがDLL自体に関数を作成し、インポート可能にすることを意味します。

コンパイラは、DLL_EXPORTEDクラスのクライアントが使用するメソッドを認識していないため、すべてのメソッドがdllエクスポートされるように強制する必要があります。クライアントがアクセスできるすべてのメンバーは、その機能もdllエクスポートする必要があります。これは、コンパイラがエクスポートされていないメソッドを警告し、クライアントのリンカーがエラーを送信しているときに発生します。

すべてのメンバーにdll-exportのマークを付ける必要はありません。クライアントがアクセスできないプライベートメンバー。ここで、警告を無視/無効にすることができます(コンパイラが生成したdtor/ctorに注意してください)。

それ以外の場合、メンバーはメソッドをエクスポートする必要があります。 DLL_EXPORTでそれらを前方宣言しても、これらのクラスのメソッドはエクスポートされません。コンパイル単位の対応するクラスをDLL_EXPORTとしてマークする必要があります。

要約すると...(dllエクスポート不可能なメンバー向け)

  1. クライアントが使用していない/使用できないメンバーがある場合は、警告をオフにします。

  2. クライアントが使用する必要があるメンバーがある場合は、dll-exportラッパーを作成するか、インダイレクションメソッドを作成します。

  3. 外部から見えるメンバーの数を減らすには、 PIMPLイディオム などのアプローチを使用します。


template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;

これにより、現在のコンパイル単位でテンプレートの特殊化のインスタンス化が作成されます。これにより、dllにstd :: allocatorのメソッドが作成され、対応するメソッドがエクスポートされます。これはテンプレートクラスのインスタンス化にすぎないため、具象クラスでは機能しません。

53
Christopher

この警告は、DLL=のユーザーはDLL境界を越えてコンテナメンバー変数にアクセスできないことを示しています。明示的にエクスポートすると、それらは使用可能になりますが、それは良いアイデアですか?

一般的に、DLLからstdコンテナをエクスポートすることは避けたいと思います。 DLLが同じランタイムおよびコンパイラバージョンで使用されることを完全に保証できる場合は、安全になります。DLL同じメモリマネージャを使用して割り当てを解除します。

そのため、DLL境界を越えてコンテナを直接公開しないでください。コンテナ要素を公開する必要がある場合は、アクセサメソッドを使用して公開します。 DLLレベルでのインターフェース。stdコンテナの使用は、DLLのクライアントがアクセスする必要のない実装の詳細です。

または、Neilが提案することを行い、DLLの代わりに静的ライブラリを作成します。実行時にライブラリをロードする機能は失われ、ライブラリを変更するたびにライブラリのコンシューマは再リンクする必要があります。これらが共存できる妥協点であるなら、静的ライブラリは少なくともこの問題を乗り越えるでしょう。実装の詳細を不必要に公開していると私は主張しますが、特定のライブラリにとって意味があるかもしれません。

17
Aaron Saarela

他にも問題があります。

一部のSTLコンテナは(ベクトルなど)エクスポートするのに「安全」であり、一部はそうではありません(マップなど)。

たとえば、MS STLディストリビューションのマップには、_Nilという静的メンバーが含まれており、その値が反復で比較されて終了をテストするため、マップは安全ではありません。 STLでコンパイルされたモジュールはすべて_Nilの値が異なるため、1つのモジュールで作成されたマップは別のモジュールから反復できません(終了を検出して爆発することはありません)。

これは、_Nilの値が何であるかを保証できないため(初期化されていないため)、libに静的にリンクする場合でも適用されます。

STLPortはこれを行わないと思います。

7
Adrien

このシナリオを処理するために見つけた最良の方法は次のとおりです。

ライブラリを作成し、ブーストライブラリとまったく同じように、ライブラリ名に含まれるコンパイラとstlバージョンで命名します。

例:

-FontManager-msvc10-mt.dll MSVC10コンパイラ固有のdllバージョン用、デフォルトstl。

-FontManager-msvc10_stlport-mt.dll MSVC10コンパイラ固有のdllバージョン、stlポート付き。

-FontManager-msvc9-mt.dll MSVC 2008コンパイラに固有のdllバージョン、デフォルトstl

-libFontManager-msvc10-mt.libデフォルトのstlを使用した、静的ライブラリバージョン用、MSVC10コンパイラ専用.

このパターンに従うと、異なるstl実装に関連する問題を回避できます。 vc2008のstl実装は、vc2010のstl実装とは異なります。

Boost :: configライブラリを使用した例を参照してください。

#include <boost/config.hpp>

#ifdef BOOST_MSVC
#  pragma warning( Push )
#  pragma warning( disable: 4251 )
#endif

class DLL_EXPORT FontManager
{
public:
   std::map<int, std::string> int2string_map;
}

#ifdef BOOST_MSVC
#  pragma warning( pop )
#endif

少数の人々が検討していると思われる1つの選択肢は、DLLを使用するのではなく、静的.LIBライブラリに対して静的にリンクすることです。そうすれば、エクスポート/インポートの問題はすべてなくなります(ただし、別のコンパイラを使用すると名前のマングリングの問題が発生します。)もちろん、関数の実行時読み込みなど、DLLアーキテクチャの機能は失われますが、これにより、多くの場合、支払うための小さな価格です。

5
anon

見つかった この記事 。要するに、アーロンは上記の「本当の」答えを持っています。ライブラリの境界を越えて標準コンテナを公開しないでください。

4

このスレッドはかなり古いものですが、最近問題を発見したため、エクスポートされたクラスにテンプレートがあることを改めて考えました。

Std :: map型のプライベートメンバーを持つクラスを作成しました。リリースモードでコンパイルされるまで、すべてが非常にうまく機能しました。ビルドシステムで使用する場合でも、すべてのターゲットですべてのコンパイラ設定が同じになります。マップは完全に非表示で、クライアントに直接公開されるものはありませんでした。

その結果、リリースモードでコードがクラッシュしました。実装とクライアントコード用に異なるバイナリstd :: mapインスタンスが作成されたためだと思います。

これはコンパイラ固有のものであるため、C++標準では、エクスポートされたクラスでこれをどのように処理するかについては何も言っていません。したがって、最大の移植性ルールは、インターフェイスを公開し、できるだけPIMPLイディオムを使用することだと思います。

啓発をありがとう

3
TheBigW

Dllからstd ::オブジェクト(ベクター、マップなど)を含むクラスをエクスポートする

また、MicrosoftのKB 168958の記事 Standard Template Library(STL)クラスとSTLオブジェクトであるデータメンバーを含むクラスのインスタンス化をエクスポートする方法 も参照してください。記事から:

STLクラスをエクスポートするには

  1. DLLと.exeファイルの両方で、同じDLLバージョンのCランタイムとリンクします。両方ともMsvcrt.lib(リリースビルドとリンクします。 )または両方をMsvcrtd.libとリンクします(デバッグビルド)。
  2. DLLで、テンプレートのインスタンス化宣言に__declspec指定子を指定して、DLLからSTLクラスのインスタンス化をエクスポートします。
  3. .exeファイルで、テンプレートのインスタンス化宣言にexternおよび__declspec指定子を指定して、DLLからクラスをインポートします。これにより、警告C4231「標準外の拡張機能が使用されました:テンプレートの明示的なインスタンス化の前に「extern」」この警告は無視できます。

そして:

STLオブジェクトであるデータメンバーを含むクラスをエクスポートするには

  1. DLLと.exeファイルの両方で、同じDLLバージョンのCランタイムとリンクします。両方ともMsvcrt.lib(リリースビルドとリンクします。 )または両方をMsvcrtd.libとリンクします(デバッグビルド)。
  2. DLLで、テンプレートのインスタンス化宣言に__declspec指定子を指定して、DLLからSTLクラスのインスタンス化をエクスポートします。

    注:ステップ2はスキップできません。データメンバーの作成に使用するSTLクラスのインスタンスをエクスポートする必要があります。
  3. DLLで、クラスの宣言に__declspec指定子を指定して、DLLからクラスをエクスポートします。
  4. .exeファイルで、クラスの宣言に__declspec指定子を指定して、DLLからクラスをインポートします。エクスポートするクラスに1つ以上の基本クラスがある場合、基本クラスもエクスポートする必要があります。

    エクスポートするクラスにクラスタイプのデータメンバーが含まれている場合、データメンバーのクラスもエクスポートする必要があります。
2
jww

そのような場合は、pimplイディオムの使用を検討してください。単一のvoid *の背後にあるすべての複雑なタイプを非表示にします。コンパイラは通常、メンバーがプライベートであり、すべてのメソッドがDLLに含まれていることに気づきません。

1
MSalters

stlコンテナーのようなテンプレートクラス内の静的データメンバーのため、上記の回避策はいずれもMSVCでは受け入れられません。

各モジュール(dll/exe)は、各静的定義の独自のコピーを取得します...すごい!このようなデータを何らかの方法で「エクスポート」すると(上記のように)、これはひどいことにつながります。

http://support.Microsoft.com/kb/172396/en-us を参照してください

0
kcris

このようなシナリオで使用する最適なアプローチは、PIMPL設計パターンを使用することです。

0
munsingh

DLLを使用する場合、イベント "DLL PROCESS ATTACH"ですべてのオブジェクトの初期化を行い、そのクラス/オブジェクトへのポインターをエクスポートします。

0
lsalamon