共用方式為


callbackOnCollectedDelegate MDA

注意

本文專屬於 .NET Framework。 它不適用於較新的 .NET 實作,包括 .NET 6 和更新版本。

callbackOnCollectedDelegate如果委派從 Managed 封送至 Unmanaged 程式代碼做為函式指標,並在委派被垃圾收集之後,將回呼放在該函式指標上,則會啟動 Managed 偵錯小幫手 (MDA)。

徵兆

嘗試透過從 Managed 委派取得的函式指標,呼叫至 Managed 程式碼中時,就會發生存取違規。 這些失敗雖然不是 Common Language Runtime (CLR) 錯誤,但看起來可能很像,因為 CLR 程式碼中發生存取違規。

失敗情況不一致,有時在函式指標上呼叫成功,有時則失敗。 只有在負載過重,或嘗試不定次數時,可能會發生失敗。

原因

用來建立函式指標並向 Unmanaged 程式碼公開的委派被回收記憶體。 當 Unmanaged 元件嘗試在函式指標上呼叫時,會產生存取違規。

此失敗會隨機發生,因為要視發生記憶體回收的時機而定。 如果委派符合記憶體回收資格,在回呼之後就會發生記憶體回收,且呼叫成功。 在其他時候,記憶體回收會發生在回呼之前,回呼會產生存取違規,而程式會停止。

失敗的機率取決於封送處理委派與函式指標回呼的時間,以及垃圾收集的頻率。 如果封送處理委派與隨後回呼之間的時間很短,則失敗是零星的。 如果接收函式指標的 Unmanaged 方法沒有儲存函式指標以供稍後使用,而是立即在函式指標上回呼,完成其作業後再返回,通常都會發生這種情況。 同樣地,當系統負載過重時,會發生更多記憶體回收,這樣更有可能會在回呼之前發生記憶體回收。

解決方法

一旦委派封送出為 Unmanaged 函式指標,垃圾收集行程就無法追蹤其存留期。 相反地,您的程式碼必須保留委派的參考,以供 Unmanaged 函式指標的存留期使用。 但是,您必須先識別已回收哪個委派,才能這麼做。 當啟用 MDA 時,它會提供委派的類型名稱。 使用此名稱,在您的程式碼中搜尋平台叫用,或是將該委派傳出至 Unmanaged 程式碼的 COM 簽章。 違規的委派會透過其中一個呼叫位置傳遞出去。 您也可以啟用 gcUnmanagedToManaged MDA,以在每次回呼至執行階段中之前,強制進行記憶體回收。 如此可以確保在回呼之前,一律會發生記憶體回收,進而消除因記憶體回收而產生的不確定性。 一旦您知道所收集的委派,請變更您的程序代碼,以在封送處理 Unmanaged 函式指標的存留期內保留該委派的參考。

對執行階段的影響

當委派封送處理為函式指標時,運行時間會配置一個 Thunk,以執行從 Unmanaged 轉換為 Managed 的轉換。 這個 Thunk 是最後叫用 Managed 委派之前,Unmanaged 程式碼實際呼叫的項目。 callbackOnCollectedDelegate若未啟用 MDA,則會在收集委派時刪除 Unmanaged 封送處理程式代碼。 callbackOnCollectedDelegate啟用 MDA 之後,收集委派時,不會立即刪除 Unmanaged 封送處理程式代碼。 相反地,最後 1,000 個執行個體會依預設保持運作,並且在被呼叫時,變更為啟用 MDA。 收集 1,001 個封送委派之後,最終會刪除 Thunk。

輸出

MDA 會報告在其 Unmanaged 函式指標上嘗試回呼之前,所回收之委派的類型名稱。

組態

下列範例顯示應用程式組態選項。 它將 MDA 保持運作的 Thunk 數目設為 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"); }
}

另請參閱