21

私の質問は、STLを内部に持つC++クラスのエクスポートに関連しています。例えば:

class __declspec(dllexport) Hello
{
    std::string   name;

public:
    std::string&  getName();
    void          setName(const std::string& name);
}

さまざまな記事がこれが非常に悪いことを示しているようで、それは非常に理解できます。すべてが同じコンパイラ設定とCRTバージョンでコンパイルされる必要があります。そうしないと、すべてがクラッシュして燃えます。

質問:

私が理解していないのは、なぜデータメンバーだけが問題を抱えているように見えるのかということです。以下のコードで、次のようになります。 " C4251:クラスのクライアントが使用するにはdll-interfaceが必要です"; これは、インスタンス化されたstd :: string:をエクスポートすることで明らかに修正されています。

struct __declspec(dllexport) SomeClass
{
    // Removes the warning?
    // http://www.unknownroad.com/rtfm/VisualStudio/warningC4251.html
    //   template class __declspec(dllexport) std::string;

    std::string name;  // Compiler balks at this
}

そして、修正されたバージョンは次のとおりです。

// Export the instantiations of allocator and basic_string
template class __declspec(dllexport) std::allocator<char>;
template class __declspec(dllexport) std::basic_string<char, std::char_traits<char>, std::allocator<char> >;

struct __declspec(dllexport) SomeClass
{
    std::string name;  // No more balking!
}

(これにより、DLLを使用しようとすると、LNK2005に「basic_stringがすでに定義されています」が表示されます。つまり、クライアントのCRTにリンクする必要がないため、DLLのインスタンス化を使用することになります)。

戻り値の型と引数はSTLに問題がないようであり、コンパイラーからメンバーが取得するのと同じ処理データを受け取りません。

// No exporting required?
struct __declspec(dllexport) SomeOtherClass
{
    std::string  doSomething1();                       // No problemo
    void         doSomething2(const std::string& s);   // No problemo
}

追加情報(質問は上にあります)

両者に:

class A {
    std::string foo() { return std::string(); }
    // std::string& foo(); gives the same result!
    // std::string* foo(); also gives the same result!
}

class B {
    std::string a;
}

std::basic_stringもstd::allocatorもエクスポートしていないようです。むしろ、クラスのメンバー/関数のみをエクスポートします。

ただし、質問で言及されている修正バージョンは、basic_stringとallocatorの両方をエクスポートします。

4

2 に答える 2

10

さまざまな記事は、これが非常に悪いことを示しているようです

はい、できます。そして、あなたのプロジェクト設定は、彼らが警告している種類の問題にあなたを陥れようとしています. C++ オブジェクトを値で公開するには、DLL で作成されたオブジェクトをクライアント アプリで安全に破棄できるように、DLL のクライアントが同じ CRT を使用する必要があります。そしてその逆です。これらのモジュールが同じヒープを使用する必要があります。

そして、あなたのプロジェクト設定は、コンパイラ警告の要点である可能性を妨げています。すべてのモジュールが CRT の唯一無二の実装をロードするように、CRT の共有バージョンを指定する必要があります。

プロジェクト + プロパティ、C/C++、コード生成、ランタイム ライブラリの設定で修正します。/MT にあるので、/MD に違いありません。すべてのモジュールとすべての構成に対してこれを変更します。

于 2012-12-13T19:15:44.417 に答える
4

これは、特定のものがどのように構築されるかに要約されます。

コンパイラが見たとき

__declspec(dllimport)    std::string f();
// ...

{
  std::string tmp = f();
}

何を呼び出すか、どこから取得するかを理解する必要があります。したがって、この場合:

std::string tmp; => sizeof( std::string ), new (__stack_addr) std::string;
tmp = f();       => call f(), operator=( std::string )

しかし、std::string の完全な実装が見られるため、対応するテンプレートの新しいインスタンスを使用することができます。そのため、std::string のテンプレート関数をインスタンス化して 1 日と呼び、関数を合体させてリンカー ステージに残すことができます。リンカーは、どの関数を 1 つに折りたたむことができるかを見つけようとします。唯一の不明な関数は、コンパイラが dll 自体からインポートする必要がある f() です。(それは彼のために外部とマークされています)。

メンバーは、コンパイラにとってより大きな問題です。エクスポートする関数 (コンストラクタ、コピー コンストラクタ、代入演算子、デストラクタ呼び出し) を認識している必要があり、クラスを「dllexport」としてマークすると、それらのすべてをエクスポート/インポートする必要があります。必要な関数のみを dllexport (ctor/dtor) として宣言し、コピーなどを禁止することにより、クラスの特定の部分のみを明示的にエクスポートできます。この方法では、すべてをエクスポートする必要はありません。

std::string に関する 1 つの注意点は、コンパイラ バージョン間でサイズ/内容が変更されたため、コンパイラ バージョン間で std::string を安全にコピーできないことです。(たとえば、VC6 では、文字列は 3 ポインターの大きさでしたが、現在は 16 バイト + サイズ + アロケーターのサイズであり、VS2012 では最適化されていないと思います)。インターフェイスで std::string オブジェクトを使用しないでください。エクスポートされていないインライン関数を使用して、呼び出し元サイトで std::string に変換する dll エクスポート文字列実装を作成できます。

于 2012-12-13T18:20:56.777 に答える