callbackOnCollectedDelegate MDA

Note

この記事は .NET Framework に固有のものです。 .NET 6 以降のバージョンを含む、.NET の新しい実装には適用されません。

callbackOnCollectedDelegate マネージド デバッグ アシスタント (MDA) は、デリゲートが関数ポインターとしてマネージド コードからアンマネージ コードにマーシャリングされ、デリゲートがガベージ コレクションされた後にその関数ポインターにコールバックが配置された場合にアクティブになります。

Symptoms

アクセス違反は、マネージド デリゲートから取得された関数ポインターを介してマネージド コードを呼び出そうとしたときに発生します。 これらのエラーは、共通言語ランタイム (CLR) のバグではありませんが、CLR コードでアクセス違反が発生するため、そのように見える場合があります。

エラーに一貫性がありません。場合によっては、関数ポインターの呼び出しが成功し、失敗することがあります。 このエラーは、負荷が高い場合、またはランダムな回数の試行でのみ発生する可能性があります。

原因

関数ポインターが作成され、アンマネージ コードに公開されたデリゲートがガベージ コレクションされました。 アンマネージ コンポーネントが関数ポインターを呼び出そうとすると、アクセス違反が生成されます。

エラーは、ガベージ コレクションが発生するタイミングによって異なるため、ランダムに表示されます。 デリゲートがコレクションの対象である場合、コールバックの後にガベージ コレクションが発生し、呼び出しが成功する可能性があります。 それ以外の場合は、コールバックの前にガベージ コレクションが発生し、コールバックによってアクセス違反が生成され、プログラムが停止します。

エラーの確率は、デリゲートのマーシャリングと関数ポインターのコールバックの間の時間と、ガベージ コレクションの頻度によって異なります。 デリゲートのマーシャリングからその後のコールバックまでの時間が短い場合、エラーは散発的です。 これは通常、関数ポインターを受け取るアンマネージ メソッドが後で使用するために関数ポインターを保存せず、代わりに関数ポインターを直ちに呼び出して、戻る前にその操作を完了する場合です。 同様に、システムの負荷が高い場合はガベージ コレクションが増え、コールバックの前にガベージ コレクションが発生する可能性が高くなります。

Resolution

デリゲートがアンマネージ関数ポインターとしてマーシャリングされると、ガベージ コレクターはその有効期間を追跡できません。 代わりに、コードはアンマネージ関数ポインターの有効期間中、デリゲートへの参照を保持する必要があります。 ただし、これを行う前に、最初に収集されたデリゲートを特定する必要があります。 MDA がアクティブになると、デリゲートの型名が提供されます。 この名前を使用して、そのデリゲートをアンマネージ コードに渡すプラットフォーム呼び出しまたは COM 署名をコードで検索します。 問題のある代理人は、これらの呼び出しサイトのいずれかを通じて渡されます。 また、 gcUnmanagedToManaged MDA を有効にして、ランタイムへのコールバックの前にガベージ コレクションを強制することもできます。 これにより、コールバックの前に常にガベージ コレクションが発生することを確認することで、ガベージ コレクションによってもたらされる不確定性が解消されます。 収集されたデリゲートがわかったら、マーシャリングされたアンマネージ関数ポインターの有効期間中、マネージド側でそのデリゲートへの参照を保持するようにコードを変更します。

ランタイムへの影響

デリゲートが関数ポインターとしてマーシャリングされると、ランタイムはアンマネージからマネージドへの移行を行うサンクを割り当てます。 このサンクは、マネージド デリゲートが最終的に呼び出される前に、アンマネージ コードが実際に呼び出すものです。 callbackOnCollectedDelegate MDA が有効になっていない場合、デリゲートの収集時にアンマネージド マーシャリング コードが削除されます。 callbackOnCollectedDelegate MDA を有効にすると、デリゲートの収集時にアンマネージド マーシャリング コードはすぐには削除されません。 代わりに、最後の 1,000 個のインスタンスが既定で保持され、呼び出されたときに MDA をアクティブにするように変更されます。 さらに 1,001 人のマーシャリングされたデリゲートが収集された後、サンクは最終的に削除されます。

アウトプット

MDA は、アンマネージ関数ポインターでコールバックが試行される前に収集されたデリゲートの型名を報告します。

コンフィギュレーション

次の例は、アプリケーション構成オプションを示しています。 MDA が生き続けるサンクの数を 1,500 に設定します。 既定の listSize 値は 1,000、最小値は 50、最大値は 2,000 です。

<mdaConfig>
  <assistants>
    <callbackOnCollectedDelegate listSize="1500" />
  </assistants>
</mdaConfig>

Example

次の例は、この MDA をアクティブ化できる状況を示しています。

// Library.cpp : Defines the unmanaged entry point for the DLL application.
#include "windows.h"
#include "stdio.h"

void (__stdcall *g_pfTarget)();

void __stdcall Initialize(void __stdcall pfTarget())
{
    g_pfTarget = pfTarget;
}

void __stdcall Callback()
{
    g_pfTarget();
}
// C# Client
using System;
using System.Runtime.InteropServices;

public class Entry
{
    public delegate void DCallback();

    public static void Main()
    {
        new Entry();
        Initialize(Target);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Callback();
    }

    public static void Target()
    {
    }

    [DllImport("Library", CallingConvention = CallingConvention.StdCall)]
    public static extern void Initialize(DCallback pfDelegate);

    [DllImport ("Library", CallingConvention = CallingConvention.StdCall)]
    public static extern void Callback();

    ~Entry() { Console.Error.WriteLine("Entry Collected"); }
}

こちらも参照ください