callbackOnCollectedDelegate MDA

Note

Questo articolo è specifico per .NET Framework. Non si applica alle implementazioni più recenti di .NET, tra cui .NET 6 e versioni successive.

L'assistente callbackOnCollectedDelegate al debug gestito viene attivato se un delegato viene sottoposto a marshalling da codice gestito a codice non gestito come puntatore a funzione e un callback viene posizionato sul puntatore di funzione dopo che il delegato è stato sottoposto a Garbage Collection.

Symptoms

Le violazioni di accesso si verificano quando si tenta di chiamare il codice gestito tramite puntatori a funzione ottenuti dai delegati gestiti. Questi errori, anche se non i bug CLR (Common Language Runtime), potrebbero sembrare tali perché la violazione di accesso si verifica nel codice CLR.

L'errore non è coerente; a volte la chiamata sul puntatore di funzione ha esito positivo e a volte ha esito negativo. L'errore può verificarsi solo in caso di carico elevato o su un numero casuale di tentativi.

Cause

Il delegato da cui è stato creato il puntatore di funzione ed esposto al codice non gestito è stato sottoposto a Garbage Collection. Quando il componente non gestito tenta di chiamare sul puntatore di funzione, genera una violazione di accesso.

L'errore viene visualizzato in modo casuale perché dipende da quando si verifica l'operazione di Garbage Collection. Se un delegato è idoneo per la raccolta, l'operazione di Garbage Collection può verificarsi dopo il callback e la chiamata ha esito positivo. In altri casi, l'operazione di Garbage Collection si verifica prima del callback, il callback genera una violazione di accesso e il programma si arresta.

La probabilità dell'errore dipende dal tempo tra il marshalling del delegato e il callback sul puntatore alla funzione, nonché la frequenza di Garbage Collection. L'errore è sporadico se il tempo tra il marshalling del delegato e l'esecuzione del callback è breve. Questo è in genere il caso se il metodo non gestito che riceve il puntatore di funzione non salva il puntatore di funzione per un uso successivo, ma richiama invece sul puntatore alla funzione immediatamente per completare l'operazione prima di restituire . Analogamente, più Garbage Collection si verificano quando un sistema è sottoposto a un carico elevato, il che rende più probabile che si verifichi un'operazione di Garbage Collection prima del callback.

Risoluzione

Una volta eseguito il marshalling di un delegato come puntatore a funzione non gestito, il Garbage Collector non può tenere traccia della durata. Al contrario, il codice deve mantenere un riferimento al delegato per la durata del puntatore a funzione non gestito. Ma prima di poter eseguire questa operazione, è necessario identificare quale delegato è stato raccolto. Quando l'assistente al debug gestito viene attivato, fornisce il nome del tipo del delegato. Usare questo nome per cercare nel codice le firme PLATFORM invoke o COM che passano tale delegato al codice non gestito. Il delegato offensivo viene passato attraverso uno di questi siti di chiamata. È anche possibile abilitare l'assistente gcUnmanagedToManaged al debug gestito per forzare un'operazione di Garbage Collection prima di ogni callback nel runtime. In questo modo si rimuove l'incertezza introdotta dalla Garbage Collection assicurandosi che un'operazione di Garbage Collection si verifichi sempre prima del callback. Dopo aver appreso il delegato raccolto, modificare il codice in modo da mantenere un riferimento a tale delegato sul lato gestito per la durata del puntatore a funzione non gestito con marshalling.

Effetto sul runtime

Quando i delegati vengono distribuiti come puntatori a funzione, il runtime alloca un threadk che esegue la transizione da non gestito a gestito. Questo thread è ciò che il codice non gestito chiama effettivamente prima che il delegato gestito venga infine richiamato. Senza l'assistente callbackOnCollectedDelegate al debug gestito abilitato, il codice di marshalling non gestito viene eliminato quando viene raccolto il delegato. Con l'assistente callbackOnCollectedDelegate al debug gestito abilitato, il codice di marshalling non gestito non viene eliminato immediatamente quando viene raccolto il delegato. Le ultime 1.000 istanze vengono invece mantenute attive per impostazione predefinita e modificate per attivare l'assistente al debug gestito quando viene chiamato. Dopo la raccolta di 1.001 delegati con marshalling vengono eliminati.

Risultato

L'assistente al debug gestito segnala il nome del tipo del delegato raccolto prima del tentativo di callback sul puntatore a funzione non gestito.

Configuration

Nell'esempio seguente vengono illustrate le opzioni di configurazione dell'applicazione. Imposta il numero di batch che l'MDA mantiene attivo su 1.500. Il valore predefinito listSize è 1.000, il valore minimo è 50 e il valore massimo è 2.000.

<mdaConfig>
  <assistants>
    <callbackOnCollectedDelegate listSize="1500" />
  </assistants>
</mdaConfig>

Example

L'esempio seguente illustra una situazione in grado di attivare questo assistente al debug gestito:

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

Vedere anche