Поделиться через


callbackOnCollectedDelegate MDA

Примечание.

Эта статья относится к .NET Framework. Он не применяется к более новым реализациям .NET, включая .NET 6 и более поздние версии.

Помощник callbackOnCollectedDelegate по управляемой отладке (MDA) активируется, если делегат маршалируется из управляемого в неуправляемый код в качестве указателя функции и обратный вызов помещается на этот указатель функции после того, как делегат был собран мусор.

Симптомы

Нарушение прав доступа происходит при попытке вызова управляемого кода посредством указателей функций, которые были получены из управляемых делегатов. Такие сбои, не будучи распространенными ошибками среды CLR, могут возникать из-за нарушения прав доступа в коде среды CLR.

Этот сбой не является постоянным; иногда вызов указателя функции выполняется успешно, а иногда происходит сбой. Этот сбой может возникать только в условиях большой нагрузки или при произвольном числе попыток.

Причина

Делегат, из которого указатель функции был создан и открыт для неуправляемого кода, был собран как мусор. Когда неуправляемый компонент пытается вызвать указатель функции, он создает нарушение прав доступа.

Этот сбой появляется произвольно, так как он зависит от того, когда выполняется сборка мусора. Если делегат является доступным для коллекции, сборка мусора может произойти после обратного вызова, и тогда вызов выполняется успешно. В других случаях сборка мусора происходит перед обратным вызовом, обратный вызов создает нарушение прав доступа, и программа останавливается.

Вероятность сбоя зависит от времени между маршалированием делегата и обратным вызовом на указателе функции, а также частотой сборок мусора. Сбой является спорадическим, если время между маршалированием делегата и последующим обратным вызовом является коротким. Обычно так происходит, если неуправляемый метод, получающий указатель функции, не сохраняет этот указатель функции для дальнейшего использования, а вместо этого выполняет обратный вызов указателя функции немедленно для завершения работы перед возвратом. Аналогично сборка мусора происходит чаще, если система работает в условиях большой нагрузки, что увеличивает вероятность того, что сборка мусора произойдет перед выполнением обратного вызова.

Разрешение

После того как делегат был маршалирован как неуправляемый указатель функции, сборщик мусора не может отслеживать его время существования. Вместо этого код должен хранить ссылку на делегат в течение времени существования неуправляемого указателя функции. Однако прежде чем делать это, необходимо определить, какой делегат был собран. При активации MDA он предоставляет имя типа делегата. Используйте это имя для поиска в коде платформенного вызова или в сигнатурах COM, которые передают этот делегат в неуправляемый код. Делегат, вызвавший ошибку, передается вовне посредством одного из этих мест вызова. Можно также включить в MDA gcUnmanagedToManaged принудительный запуск сборки мусора перед каждым обратным вызовом в среде выполнения. Это удалит неопределенность, вносимую сбором мусора, так как обеспечит сбор мусора всегда перед обратным вызовом. Когда вы знаете, какой делегат был собран, измените код, чтобы сохранить ссылку на этот делегат на управляемой стороне в течение времени существования маршалированного неуправляемого указателя функции.

Влияние на среду выполнения

Когда делегаты маршаллируются как указатели функций, среда выполнения выделяет thunk, которая выполняет переход от неуправляемого к управляемому. Этот преобразователь — то, что неуправляемый код фактически вызывает перед финальным вызовом управляемого делегата. Без включения MDA неуправляемый callbackOnCollectedDelegate код маршалинга удаляется при сборе делегата. При включении MDA неуправляемый callbackOnCollectedDelegate код маршалинга не сразу удаляется при сборе делегата. Вместо этого последние 1000 экземпляров хранятся по умолчанию и изменяются, чтобы активировать MDA при вызове. Thunk в конечном итоге удаляется после сбора более 1001 более маршаллированных делегатов.

Выходные данные

MDA сообщает имя типа собранного делегата до попытки обратного вызова в его неуправляемом указателе функции.

Настройка

В следующем примере показаны параметры конфигурации приложения. В нем устанавливается число преобразователей, которые сохраняются MDA, равное 1500. Значение listSize по умолчанию — 1000; минимальное значение — 50; максимальное значение — 2000.

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

См. также