4

与えられた: 実行可能ファイルは dll を使用します。それらには異なる c/c++ ランタイムがあります。それらの間のインターフェースにはどの制限が存在しますか? さらに、同じコンパイラ、同じ Boost バージョン (ただし、プリコンパイル済み Boost ライブラリは異なる) を使用します。

ランタイムごとにヒープが異なる可能性があることを理解しています。したがって、delete は同じヒープからの new に対応している必要があります。

最も重要なのは、インターフェイス STL オブジェクトを介して渡すことができないことです。これは、exe をビルドするときに STL オブジェクトが 1 つのランタイムにリンクされ、dll をビルドするときに同じオブジェクト (参照によって渡すか、インターフェイスを介してコピーする場合) が別のランタイムにリンクされるためです。また、別のランタイムは、そのオブジェクトの異なる実装を持つことができます。

ケースを考えてみましょう:

  1. 以下は安全だと思います。DLL は、パラメーターを持つ関数をエクスポートします: プライベート STL クラスをメンバーとして含む、エクスポートされたユーザー定義クラスへの参照。DLL は、このオブジェクトにメモリを割り当てます。Exe は、このオブジェクトを削除したいときに、このオブジェクトの Release メソッドを呼び出します。

  2. 以下は安全ではないと思います。ユーザー定義クラスは exe でインスタンス化され、exe/dll インターフェイス経由で渡されます。このクラスにはプライベート STL クラスがメンバとして含まれています。exe と dll は、このユーザー クラスのヘッダー/実装ファイルを共有します。このクラスが別のプロジェクトでビルドされる場合、異なる STL 実装が使用されます。たとえば、(異なるランタイムからの) string::size() の異なる実装が、メモリ内の同じオブジェクトに適用されます。

  3. 以下は安全だと思います。ユーザー定義クラスは exe でインスタンス化され、exe/dll インターフェイス経由で渡されます。このクラスは標準ライブラリに依存せず、プリミティブな C++ 型のみを使用します。exe と dll は、このユーザー クラスのヘッダー/実装ファイルを共有します。また、new と delete が同じヒープに対応するように制御する必要があります。たとえば、new / delete をオーバーロードして、::GetProcessHeap を使用することができます。

  4. 以下は安全ではないと思います: 標準ライブラリ クラスに依存する可能性があるため、exe/dll インターフェイス経由でブースト オブジェクトを渡します。また、delete は new のヒープに対応していない場合があります。

  5. 次のことは安全ではないと思います: ブースト オブジェクトを exe/dll インターフェイス経由で渡し、それらが標準ライブラリ クラスに依存せず、ヘッダーのみとして実装されていない場合でも、オブジェクトは 1 つのブースト ライブラリで作成できます (1 つのランタイムに対して)。 )および別のboost lib(別のランタイム用)で使用されます。また、delete は new のヒープに対応していない場合があります。

また、スマート ポインターのフレーバーを使用して、オブジェクト (項目 3 で説明) への参照を exe から dll および dll から exe に渡したいと考えています。このスマート ポインターは、デフォルト プロセス ヒープから参照カウンターを割り当てるために、new/delete もオーバーロードする必要があると思います。ポイントされたオブジェクトを削除しようとすると、このオブジェクトによってオーバーロードされた delete が呼び出されます (item3 のように)

項目 1 のオブジェクトの場合、ポイントされたオブジェクトのリリース メソッドを呼び出すカスタム スマート ポインターを使用したい (boost::shared_ptr とカスタム リリースを使用)

言及されていない問題はどれですか? 訂正してください。

4

1 に答える 1

4

あなたの質問に対する一般的な答えは次のとおりです。EXE/DLL から受け取ったオブジェクトに対して何かを実行しても、実際に実行されることがランタイムに依存しない場合は安全です。たとえば、vtable 呼び出し、関数ポインター呼び出し、DLL からの明示的な関数呼び出し。

ランタイムに依存するもの (ヘッダー ファイルからのインライン メソッド、STL オブジェクト レイアウトに関する仮定を行うものなど) は安全ではありません。

あなたの例に戻ります:

  1. Release() メソッドを呼び出す場合は、EXE ファイルを作成するコンパイラによって作成された別の実装ではなく、DLL から Release() の実装を呼び出すように注意してください。これを確実にする最も簡単な方法は、呼び出しが常に vtable (DLL によって提供される) からのメソッド ポインターを使用する呼び出しになるように、Release() を仮想化することです。

  2. そうです、string::size() のような STL のインライン メソッドは、ここで問題を引き起こします。

  3. 右。新規/削除の両方が GetProcessHeap() を使用するようにオーバーロードされている場合、コードは機能します。
  4. 一般的に、そうです。特に、必要なブースト クラスの実装を参照してください。
  5. それらが STL に依存しておらず、メモリ フットプリントが両方のコンパイラで同じであることが確実であり、カスタムの new/delete() を提供している場合、通常は問題になりません (以下の備考を参照)。

備考:

上記に記載されていない問題を引き起こす可能性のある可能性の低いことがいくつかあります。

  1. 異なるコンパイラを使用すると、既定で異なる呼び出し規約 (cdecl/stdcall) が使用される場合があります。
  2. デフォルトの配置オプションも異なる場合があり、メモリ レイアウトが異なります。
  3. DLL から例外をスローすると、ランタイムが異なる exe は例外をキャッチせず、代わりにクラッシュする可能性があります。
  4. メソッドを DLL インポート/エクスポート属性でマークし、それらがインライン化されていないことを確認することは、非常に面倒な場合があります。さらに、別のコンパイラを使用している場合は、C++ の名前マングリング アルゴリズムが異なる可能性があります。

要約したら、Microsoft COM での実装方法を調べて、同様のものを設計することをお勧めします。つまり、EXE/DLL の相互作用を次のように制限します。

  1. インターフェイス。つまり、純粋仮想メソッドのみを持つ C++ クラスです。仮想 Release() を使用してオブジェクトを削除します。
  2. シンプルで明確に定義された構造。すべてのコンパイラの整列オプションが一致していることを確認してください。自明ではない構造を使用したい場合で、別のコンパイラを使用している場合は、偏執的になり、次のようなチェックを入れることをお勧めします。

    struct MyStruct
    {
        ...
    };
    C_ASSERT(sizeof(MyStruct) == ...);
    C_ASSERT(FIELD_OFFSET(MyStruct, MyMember) = ... );
    
  3. インターフェイスへのポインターを渡したり返したりする C に似た関数。

  4. EXE によって呼び出されたメソッドから例外をスローしない
于 2012-07-22T20:48:34.150 に答える