callbackOnCollectedDelegate (MDA)

Nota

Questo articolo è specifico per .NET Framework. Non si applica alle implementazioni più recenti di .NET, incluse .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.

Sintomi

Le violazioni di accesso si verificano durante il tentativo di chiamare codice gestito mediante puntatori a funzione ottenuti da delegati gestiti. Questi errori possono sembrare bug di Common Language Runtime (CLR), anche se non lo sono, perché la violazione di accesso si verifica nel codice CLR.

L'errore non è coerente. L’esito della chiamata sul puntatore a funzione è a volte positivo e a volte negativo. L'errore può verificarsi solo in situazioni di carico eccessivo o in un numero casuale di tentativi.

Causa

Il delegato dal quale il puntatore a funzione è stato creato ed esposto al codice non gestito è stato sottoposto a Garbage Collection. Quando il componente non gestito tenta una chiamata sul puntatore a funzione, viene generata una violazione di accesso.

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

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. Spesso accade quando il metodo non gestito che riceve il puntatore a funzione non lo salva per un utilizzo futuro ma esegue subito il callback sul puntatore a funzione per completare l'operazione prima della restituzione. Analogamente, si verificano altre Garbage Collection quando un sistema è sottoposto a un carico eccessivo che rende più probabile che si verifichi una 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. È invece necessario che il codice mantenga un riferimento al delegato per la durata del puntatore a funzione non gestito. Tuttavia, è necessario prima identificare quale delegato sia stato raccolto. Quando l'MDA è attivato, fornisce il nome del tipo del delegato. Usare questo nome per cercare il codice per pInvoke o per le firme COM che passano il delegato al codice non gestito. Il delegato interessato viene passato attraverso uno di questi siti di chiamata. È anche possibile abilitare l'MDA gcUnmanagedToManaged per imporre una Garbage Collection prima di ogni callback nel runtime. Questa operazione rimuoverà le incertezze introdotte dalla Garbage Collection garantendone l'esecuzione 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 sull'ambiente di esecuzione

Quando i delegati vengono distribuiti come puntatori a funzione, il runtime alloca un threadk che esegue la transizione da non gestito a gestito. Questo thunk è l'oggetto effettivamente chiamato dal codice non gestito prima di richiamare il delegato gestito. 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. Per impostazione predefinita le ultime 1.000 istanze vengono invece conservate e modificate per attivare l'MDA quando viene chiamato. Dopo la raccolta di 1.001 delegati con marshalling vengono eliminati.

Output

L'MDA indica il nome del tipo del delegato raccolto prima di un tentativo di callback al relativo puntatore a funzione non gestito.

Impostazione

L'esempio seguente mostra le opzioni di configurazione dell’applicazione. Imposta il numero di thunk mantenuto attivo dall'MDA 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>

Esempio

L'esempio seguente illustra una situazione in cui è possibile attivare questo 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"); }
}

Vedi anche