26

dllに抽象クラスがあります。

class IBase {
  protected:
       virtual ~IBase() = 0;
  public:
       virtual void f() = 0;
};

IBasedllをロードするexeファイルを取得したい。最初の方法は、次の関数を作成することです

IBase * CreateInterface();

に仮想関数を追加しRelease()ますIBase

2番目の方法は別の関数を作成することです

boost::shared_ptr<IBase> CreateInterface();

Release()関数は必要ありません。

質問。

1)2番目のケースでは、デストラクタとメモリの割り当て解除がdll(exeファイルではなく)で呼び出されるというのは本当ですか?

2) exeファイルとdllが異なるコンパイラ(または異なる設定)でコンパイルされた場合、2番目のケースはうまく機能しますか?

4

4 に答える 4

20

最初の質問に対する答え: dll の仮想デストラクタが呼び出されます。その場所に関する情報がオブジェクト (vtable) に埋め込まれます。メモリの割り当て解除の場合、ユーザーがどれだけ規律を持っているかによって異なりますIBase。呼び出す必要があることを知っていてRelease()、例外が予期しない方向に制御フローをバイパスする可能性があると考える場合、正しいものが使用されます。

しかし、CreateInterface()返さshared_ptr<IBase>れた場合は、正しい割り当て解除関数をこのスマート ポインターにバインドできます。ライブラリは次のようになります。

Destroy(IBase* p) {
    ... // whatever is needed to delete your object in the right way    
}

boost::shared_ptr<IBase> CreateInterface() {
    IBase *p = new MyConcreteBase(...);
    ...
    return shared_ptr<IBase>(p, Destroy); // bind Destroy() to the shared_ptr
}                                         // which is called instead of a plain
                                          // delete

したがって、DLL のすべてのユーザーは、リソース リークを簡単に防ぐことができます。呼び出しに煩わされRelease()たり、驚くほど制御フローを迂回して例外に注意を払ったりする必要はありません。

2番目の質問に答えるには:このアプローチの欠点は、他の回答で明確に述べられています:あなたは聴衆であり、あなたと同じコンパイラ、リンカー、設定、ライブラリを使用する必要があります。そして、それらが非常に多くなる可能性がある場合、これはライブラリにとって大きな欠点になる可能性があります. 選択する必要があります: 安全性とより多くの聴衆

ただし、抜け穴の可能性がありshared_ptr<IBase>ます。アプリケーションで使用します。

{
    shared_ptr<IBase> p(CreateInterface(), DestroyFromLibrary);
    ...
    func();
    ...
}

したがって、実装固有のオブジェクトが DLL 境界を越えて渡されることはありません。それにもかかわらず、ポインタは、 が例外をスローするかどうかにかかわらず、適切なタイミングでshared_ptr呼び出している の背後に安全に隠されています。DestroyFromLibraryfunc()

于 2009-10-22T08:43:30.837 に答える
8

shared_ptrインターフェイスでの使用はお勧めしません。(「externC」のみのルーチンとは対照的に)DLLのインターフェイスでC ++を使用する場合でも、名前を変更すると別のコンパイラでDLLを使用できなくなるため、問題があります。すでに特定したように、DLLのクライアントが呼び出し元shared_ptrと同じ実装を使用するという保証はないため、使用は特に問題があります。shared_ptr(これは、shared_ptrがテンプレートクラスであり、実装が完全にヘッダーファイルに含まれているためです。)

特定の質問に答えるには:

  1. ここで何を求めているのかよくわかりません...DLLにはから派生したクラスの実装が含まれていると思いますIBase。デストラクタのコード(および残りのコード)は、どちらの場合もDLLに含まれます。ただし、クライアントがオブジェクトの破棄を開始した場合(delete最初のケースで呼び出すか、2番目のケースで最後のインスタンスをshared_ptrスコープから外す)、デストラクタはクライアントコードから呼び出されます。

  2. 名前マングリングは通常、DLLが別のコンパイラで使用されるのを防ぎます...しかし、shared_ptr同じコンパイラの新しいリリースでも実装が変更される可能性があり、問題が発生する可能性があります。私は2番目のオプションを使用することを躊躇します。

于 2009-10-22T08:07:19.743 に答える
2
  1. を使用shared_ptrすると、DLL 内でリソース解放関数が確実に呼び出されます。
  2. この質問への回答をご覧ください。

この問題を解決する方法は、純粋な C インターフェイスと、その周りに薄い完全にインライン化された C++ ラッパーを作成することです。

于 2009-10-22T08:19:06.243 に答える
1

あなたの最初の質問について:私は経験から話をするのではなく、知識に基づいた推測をしていますが、2番目のケースではメモリの割り当て解除は「.exe内」と呼ばれるようです。電話をかけると2つのことが起こりますdelete object;:最初に、デストラクタが呼び出され、次に、オブジェクトのメモリが解放されます。最初の部分であるデストラクタの呼び出しは、期待どおりに機能し、dll内の適切なデストラクタを呼び出します。ただし、shared_ptrはクラステンプレートであるため、そのデストラクタは.exeで生成されます。したがって、.dllではなくexeで演算子delete()が呼び出されます。2つが異なるランタイムバージョンに対してリンクされている場合(または同じランタイムバージョンに対して静的にリンクされている場合)、これは恐ろしい未定義の動作につながるはずです(これは私が完全に確信していない部分ですが、そのように論理的であるようです) 。私が言ったことが本当かどうかを確認する簡単な方法があります-グローバル演算子deleteをオーバーライドしますあなたのexeでは、しかしあなたのdllではなく、それにブレークポイントを置き、2番目のケースで何が呼ばれるかを見てください(私はそれを自分で行いますが、残念ながら、これだけの時間を緩めることができます)。

最初のケースにも同じ落とし穴が存在することに注意してください(あなたはそれを理解しているようですが、念のため)。exeファイルでこれを行う場合:

IBase *p = CreateInterface();
delete p;

次に、同じトラップになります。dllで演算子newを呼び出し、exeで演算子deleteを呼び出します。適切なメモリを呼び出すためだけに、dllに対応するDeleteInterface(IBase * p)関数、またはIBaseにRelease()メソッド(仮想である必要はなく、インラインにする必要はありません)が必要です。割り当て解除機能。

于 2009-10-22T09:44:15.437 に答える