回撥於CollectedDelegate MDA

Note

本文專指 .NET 框架。 此規定不適用於較新的 .NET 實作,包括 .NET 6 及更新版本。

callbackOnCollectedDelegate當代理從受管理程式碼編組到非管理程式碼作為函式指標,並在該代理被垃圾回收後對該函式指標放置回調時,管理除錯助理(MDA)就會啟動。

Symptoms

存取違規發生在嘗試透過從受管理代理取得的函式指標呼叫受管理程式碼時。 這些失敗雖然不是常見的語言執行時(CLR)錯誤,但可能看起來是因為存取違規發生在 CLR 程式碼中。

失敗並不一致;有時函式指標的呼叫成功,有時則失敗。 失敗可能只在高負載或隨機次數的嘗試中發生。

原因

產生函式指標並暴露給未管理程式碼的代理會被垃圾回收。 當未管理元件嘗試呼叫函式指標時,會產生存取違規。

失敗看起來是隨機的,因為它取決於垃圾回收發生的時間。 若代表有資格被回收,垃圾回收可在回撥後進行,且通話成功。 有時垃圾回收發生在回調前,回調產生存取違規,程式停止。

失敗的機率取決於從封送代理到函式指標回調之間的時間,以及垃圾回收的頻率。 如果從召集代表到隨後回電的時間很短,失敗是零星的。 這種情況通常發生在未管理的方法接收函式指標時,沒有儲存該函式指標以備後續使用,而是立即回調函式指標以完成操作後返回。 同樣地,當系統負載過重時,垃圾回收會增加,這使得垃圾回收更可能在回調前發生。

解決方法

一旦代理被編組為非管理函式指標,垃圾回收器就無法追蹤其壽命。 相反地,你的程式碼必須在非管理函式指標的存續期間,持續引用代理。 但在你做到這點之前,你必須先確定是哪位代表被收集到。 當 MDA 被啟動時,會提供代理的類型名稱。 用這個名稱搜尋你的程式碼,尋找平台呼叫或 COM 簽章,這些簽章會將該授權傳給非管理程式碼。 違規代表會透過這些通話網站之一發放。 你也可以啟用 gcUnmanagedToManaged MDA,強制每次回調執行時前先進行垃圾回收。 這將消除垃圾回收帶來的不確定性,確保垃圾回收總是在回調前進行。 一旦知道收集了哪個代理,就修改程式碼,讓該代理在管理端的管理型函數指標的生命週期內持續使用該代理。

對運行時間的影響

當代理被封組為函式指標時,執行時會分配一個 thunk,完成從非管理轉為管理的過程。 這個 thunk 是非管理程式碼實際呼叫的,直到最終被呼叫管理代理。 若未 callbackOnCollectedDelegate 啟用 MDA,當代理被收集時,未管理的編組碼會被刪除。 啟用 callbackOnCollectedDelegate MDA 後,當代理被收集時,未管理的編組碼不會立即被刪除。 相反地,最後1,000個實例會預設存活,並在被召喚時自動啟動MDA。 在再收集1,001名代表後,這個敲擊聲最終被刪除。

Output

MDA 在其未管理函式指標上回報在嘗試回調前收集的代理型別名稱。

Configuration

以下範例展示了應用程式的設定選項。 它將MDA維持的thunk數量設定為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"); }
}

另請參閱