4

c++ dll のアンマネージ オブジェクトへの匿名整数ハンドルを保持する ac# dll にマネージ オブジェクトがあります。c++ dll 内では、匿名整数が std::map で使用され、アンマネージ c++ オブジェクトが取得されます。このメカニズムにより、匿名の整数ハンドルを使用して、マネージド オブジェクトとアンマネージド オブジェクトの間の緩やかな関連付けを維持できます。

管理対象オブジェクトの finalize メソッド (デストラクタ) で、管理対象外の dll を呼び出して、管理対象外オブジェクトを削除します。

C# プログラムは問題なく実行されますが、プログラムの終了時に問題が発生します。マネージド側での削除操作の順序を制御できないため、アンマネージド dll はマネージド オブジェクトの前にメモリから削除されます。したがって、管理対象オブジェクトのデストラクタが呼び出されると (次に、管理対象外のデストラクタが [少なくとも間接的に] 呼び出されます)、管理対象外オブジェクトは既に削除されており、プログラムはクラッシュします。

では、ac# プログラムのマネージド オブジェクトに関連付けられている外部 C++ DLL のアンマネージド オブジェクトを安全に削除するにはどうすればよいでしょうか。

ありがとう

アンドリュー

4

4 に答える 4

8

管理対象オブジェクトのファイナライザーはほとんどの場合、フェイルセーフとしてのみ使用する必要があります。原則として、ファイナライザロジックがある場合は、オブジェクトにを実装する必要がありますIDisposable。実装の基本的なパターンはIDisposable次のとおりです(クラス名がMyClassであるとします)。

public class MyClass : IDisposable
{
    private int extHandle;

    public MyClass()
    {
        extHandle = // get the handle
    }

    public void Dispose()
    {
        Dispose(true);

        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if(disposing)
        {
            // call dispose() on any managed objects you might have
        }

        // release the handle
    }

    ~MyClass()
    {
        Dispose(false);
    }
}

これは、このオブジェクトを作成および使用するコードはすべて、オブジェクトの存続期間を管理できる必要があることも意味します。using最も簡単な方法は、次のようにインスタンスをブロックで囲むことです。

using(MyClass c = new MyClass())
{
    // do things with c
}

ブロックの最後でオブジェクトがスコープから外れると、usingブロックは自動的にオブジェクトを呼び出します。Disposeもちろん、オブジェクトが単一の関数の外部に存在する必要がある場合は、事態はさらに複雑になります。いずれにせよ、オブジェクトが終了するたびDisposeに呼び出す必要があります。

于 2009-12-22T02:22:39.187 に答える
3

C# オブジェクトのファイナライザーで Environment.HasShutdownStarted をチェックすることで、これをすばやく解決できる場合があります (HasShutdownStarted が true の場合は、C++ DLL を呼び出したり、C++ オブジェクトを削除したりしません)。メインの AppDomain にいない場合は、代わりに AppDomain.Current.IsFinalizingForUnload をチェックする必要があるかもしれません (実際、これは一般的に安全かもしれません)。

これは、解放されたライブラリの呼び出しを回避する (つまり、アンマネージ デストラクタの実行を回避する) だけであることに注意してください。アンマネージ ライブラリが、プロセスのシャットダウン時に自動的に解放されないリソースを保持していた場合、そのリソースがリークされる可能性があります。(ほとんどの OS リソースはプロセスのシャットダウン時に解放されるため、多くの場合、これは問題になりません。) また、Adam が指摘するように、CLR ファイナライザーはフェイルセーフとして意図されています。実際には、リソースをより決定論的に解放したいと考えています。したがって、構造的に可能であれば、C# クラスに IDisposable を実装し、決定論的にオブジェクトを Dispose するという Igor の提案が望ましいでしょう。

于 2009-12-22T02:20:56.867 に答える
1

これを行う通常の方法は、 IDisposableから管理対象オブジェクトを派生させることです。

object.Disposeオブジェクトを使い終わったら、常に明示的に呼び出すようにしますが、あなたの場合にそれが必要かどうかはわかりません。私が読んだドキュメントは、dllがアンロードされる前にDispose()が呼び出されることを保証するかどうかについては不明です。

私自身のコードでは、マネージコードドメインは、アンマネージアプリが終了する前に明示的に破棄されるため、その特定の問題について心配する必要はありません。

于 2009-12-22T02:26:21.693 に答える
1

Dipose管理対象オブジェクトのメソッドから管理対象外オブジェクトを削除する必要があります。また、ガベージ コレクターがコードに到達する前にコードが呼び出されなかった場合に備えてDisposeFinalizeメソッドから呼び出す必要があります。DisposeAdam Robinson の回答は、それをよりよく示しています。

したがって、Dispose 呼び出し (およびブロックの使用) に熱心であれば、usingシャットダウン クラッシュは発生しないはずです。

編集:問題は、実際には、ファイナライザーが実行される前にアンマネージ DLL がアンロードされることだと思います。古い「アプリがシャットダウンされると、どの順序でアンロードされるかについての保証はありません」.

アンマネージ リソースをマネージ C++ アセンブリに入れてみることはできますか? そうすれば、DLL が完成する前に DLL が暴走することはなく、厄介な P/Invoke 作業を行う必要がないことがわかります。

MSDN の例を次に示します。

ref struct A {
   // destructor cleans up all resources
   ~A() {
      // clean up code to release managed resource
      // ...
      // to avoid code duplication 
      // call finalizer to release unmanaged resources
      this->!A();
   }

   // finalizer cleans up unmanaged resources
   // destructor or garbage collector will
   // clean up managed resources
   !A() {
      // clean up code to release unmanaged resource
      // ...
   }
};

詳細はこちらhttp://msdn.microsoft.com/en-us/library/ms177197.aspx

上記は C# のパターンと同じですが、マネージ C++ アセンブリにアンマネージ リソースが含まれている場合があります。それらをアンマネージ DLL (静的アンマネージ ライブラリではない) に含める必要がある場合は、スタックしてしまい、同じシャットダウンの問題が発生します。

于 2009-12-22T02:17:52.270 に答える