callbackOnCollectedDelegate MDA
Управляемый помощник по отладке (MDA) callbackOnCollectedDelegate активируется, если делегат маршалируется из управляемого в неуправляемый код в качестве указателя функции, и обратный вызов помещается на данный указатель функции после того, как память, занимаемая делегатом, была освобождена в процессе сборки мусора.
Признаки
Нарушения прав доступа происходят при попытках вызова управляемого кода посредством указателей функций, которые были получены из управляемых делегатов. Эти ошибки, не будучи стандартными ошибками среды CLR, могут казаться таковыми, поскольку нарушения прав доступа происходят в коде среды CLR.
Ошибка не является постоянной; иногда удается выполнить вызов указателя функции, а иногда не удается. Ошибка может возникать только в условиях интенсивной нагрузки или при произвольном числе попыток.
Причина
Делегат, из которого указатель функции был создан и предоставлен неуправляемому коду, был ликвидирован в процессе сборки мусора. При попытке вызова указателя функции неуправляемым компонентом генерируется нарушение прав доступа.
Ошибка появляется произвольно, поскольку зависит от того, когда происходит процесс сборки мусора. Если делегат отвечает за сборку, сборка мусора может произойти после обратного успешного вызова. В других случаях сборка мусора происходит перед обратным вызовом, обратный вызов генерирует нарушение прав доступа и программа останавливает работу.
Вероятность возникновения ошибки зависит от интервала времени между маршалингом делегата и обратным вызовом указателя функции, а также от частоты процессов сборки мусора. Ошибка является спорадической при коротком интервале времени между маршалингом делегата и выполнением обратного вызова. Чаще всего так и происходит, если неуправляемый метод, получающий указатель функции, не сохраняет указатель функции для дальнейшего использования, а немедленно выполняет обратный вызов указателя функции для завершения его работы перед возвращением. Также процесс сборки мусора происходит чаще, если система работает в условиях интенсивной нагрузки, вследствие чего процесс сборки мусора, скорее всего, произойдет перед выполнением обратного вызова.
Решение
После маршалинга делегата в качестве неуправляемого указателя функции, сборщик мусора неспособен отслеживать его время существования. Вместо этого код должен хранить ссылку на делегат в течение всего времени существования неуправляемого указателя функции. Но прежде, чем это будет возможно, следует определить, какой делегат был ликвидирован в процессе сборки. При активации MDA он обеспечивает имя типа для делегата. Данное имя следует использовать для поиска в коде платформенного вызова или подписей COM, которые передают этот делегат в неуправляемый код. Делегат, вызвавший ошибку, передается вовне посредством одного из данных мест вызова. Можно также включить MDA gcUnmanagedToManaged для принудительного запуска процесса сборки перед каждым обратным вызовом в среде выполнения. Это позволит избавиться от неопределенности в отношении процесса сборки мусора, поскольку гарантирует, что сборка мусора будет всегда происходить перед выполнением обратного вызова. После определения делегата, ликвидированного в процессе сборки мусора, следует изменить код, чтобы он хранил ссылку на этот делегат на управляемой стороне в течение всего времени существования маршалированного неуправляемого указателя функции.
Влияние на среду выполнения
При маршалинге делегата в качестве указателя функции среда выполнения выделяет память для преобразователя, который осуществляет передачу из неуправляемого в управляемый код. Именно этот преобразовать вызывается неуправляемым кодом перед конечным вызовом управляемого делегата. При отключенном MDA callbackOnCollectedDelegate неуправляемый код маршалинга удаляется, когда делегат ликвидируется в процесcе сборки мусора. При включенном MDA callbackOnCollectedDelegate неуправляемый код маршалинга не удаляется немедленно, когда делегат ликвидируется в процессе сборки мусора. Вместо этого последние 1000 экземпляров хранятся по умолчанию и изменяются, чтобы активировать MDA при вызове. Преобразователь удаляется после того, как еще 1001 маршалированный делегат ликвидируется в процессе сборки.
Output
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"); }
}
См. также
Ссылки
Основные понятия
Диагностика ошибок посредством управляемых помощников по отладке