callbackOnCollectedDelegate MDA

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

症状

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

このエラーには一貫性がありません。関数ポインターでの呼び出しに成功することも失敗することもあります。 エラーは、高負荷のときに限って発生することも、不定回数の試行時に発生することもあります。

原因

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

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

エラーが発生する確率は、デリゲートのマーシャリングと関数ポインターでのコールバックとの間隔、およびガベージ コレクションの頻度により決まります。 デリゲートのマーシャリングとその後のコールバックとの間隔が短い場合、エラーは散発的になります。 通常、このような状態になるのは、関数ポインターを受け取るアンマネージ メソッドが後で使用するためにその関数ポインターを保存せずに、戻る前に操作を完了するために、直ちに関数ポインターでコールバックする場合です。 同様に、システムが高負荷の場合は頻繁にガベージ コレクションが発生するため、コールバック前にガベージ コレクションが発生しやすくなります。

解決策

デリゲートがアンマネージ関数ポインターとしてマーシャリングされると、ガベージ コレクターはその有効期間を追跡できなくなります。 その代わりに、アンマネージ関数ポインターの有効期間中は、デリゲートへの参照をコードに維持する必要があります。 しかし、その作業を行う前に、収集されたデリゲートを特定する必要があります。 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>

使用例

この 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"); }
}

参照

参照

MarshalAsAttribute

gcUnmanagedToManaged MDA

概念

マネージ デバッグ アシスタントによるエラーの診断

相互運用マーシャリング

その他の技術情報

相互運用性