callbackOnCollectedDelegate MDA

注释

本文特定于 .NET Framework。 它不适用于 .NET 的较新版本实现,包括 .NET 6 及更高版本。

callbackOnCollectedDelegate如果委托作为函数指针从托管代码封送到非托管代码,并在委托被垃圾回收后将回调放置在该函数指针上,则会激活托管调试助手 (MDA)。

症状

尝试通过从托管委托获取的函数指针调用托管代码时,会发生访问冲突。 这些失败(虽然不是公共语言运行时 (CLR) bug,但似乎如此,因为访问冲突发生在 CLR 代码中。

失败不一致;有时对函数指针的调用成功,有时失败。 故障可能仅在负载过大或尝试的随机次数下发生。

原因

从中创建函数指针并将其公开给非托管代码的委托被垃圾回收。 当非托管组件尝试调用函数指针时,它将生成访问冲突。

失败是随机的,因为它取决于何时发生垃圾回收。 如果委托有资格进行回收,则垃圾回收可以在回调后发生,调用成功。 在其他情况下,垃圾回收发生在回调之前,回调将生成访问冲突,程序停止。

失败的概率取决于封送委托与函数指针的回调以及垃圾回收的频率之间的时间。 如果封送委托和随后的回调之间的时间较短,则失败是零星的。 如果接收函数指针的非托管方法不会保存函数指针供以后使用,但会立即回击函数指针,以便在返回之前立即完成其操作,则这种情况通常是这样。 同样,当系统负载过重时,会发生更多的垃圾回收,这使得垃圾回收更有可能在回调之前发生。

解决方案

将委托封送为非托管函数指针后,垃圾回收器将无法跟踪其生存期。 相反,代码必须在非托管函数指针的生存期内保留对委托的引用。 但是,在执行此操作之前,必须先确定收集的委托。 激活 MDA 后,它提供委托的类型名称。 使用此名称在代码中搜索平台调用或 COM 签名,这些签名将委派给非托管代码。 冒犯委托通过其中一个呼叫站点传出。 还可以在每次回调运行时之前启用 gcUnmanagedToManaged MDA 强制垃圾回收。 这将通过确保垃圾回收始终在回调之前发生来消除垃圾回收引入的不确定性。 了解收集的委托后,更改代码以在托管端保留对托管端的引用,以在封送的非托管函数指针的生存期内保留该委托的引用。

对运行时的影响

当委托作为函数指针进行封送时,运行时会分配一个 thunk,用于执行从非托管到托管的转换。 此 thunk 是非托管代码在最终调用托管委托之前实际调用的内容。 callbackOnCollectedDelegate如果未启用 MDA,则收集委托时会删除非托管封送代码。 callbackOnCollectedDelegate启用 MDA 后,收集委托时,不会立即删除非托管封送代码。 相反,最后 1,000 个实例默认保持活动状态,并在调用时更改为激活 MDA。 收集了 1,001 个封送的委托后,最终会删除 thunk。

输出

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

另见