Partager via


callbackOnCollectedDelegate (MDA)

Remarque

Cet article est spécifique au .NET Framework. Elle ne s’applique pas aux implémentations plus récentes de .NET, notamment .NET 6 et versions ultérieures.

L’Assistant Débogage managé (MDA, Managed Debugging Assistant) callbackOnCollectedDelegate est activé si un délégué est marshalé d’un code managé en code non managé en tant que pointeur de fonction et si un rappel est placé sur ce pointeur de fonction après que le délégué a été nettoyé de la mémoire.

Symptômes

Des violations d'accès se produisent lors des tentatives d'appel de code managé par les pointeurs de fonction obtenus par des délégués managés. Ces échecs, qui ne sont pas des bogues du common language runtime (CLR), peuvent pourtant y ressembler, car la violation d'accès se produit dans le code CLR.

Les échecs ne surviennent pas de façon cohérente : tantôt l'appel sur le pointeur de fonction aboutit, tantôt il échoue. Un échec peut se produire dans une situation de surcharge uniquement ou sur un nombre aléatoire de tentatives.

Cause

Le délégué à partir duquel le pointeur de fonction a été créé et ensuite exposé au code non managé a été récupéré par le garbage collector. Quand le composant non managé tente d'appeler sur le pointeur de fonction, il génère une violation d'accès.

L'échec est aléatoire, car il dépend du moment où l'opération garbage collection se produit. Si un délégué peut faire l'objet d'une opération garbage collection, celle-ci peut avoir lieu après le rappel et, dans ce cas, l'appel aboutit. Dans d'autres cas où l'opération garbage collection se produit avant le rappel, le rappel génère une violation d'accès et le programme s'arrête.

La probabilité d’un échec dépend du laps de temps entre le marshaling du délégué et le rappel sur le pointeur de fonction ainsi que de la fréquence des opérations de nettoyage de la mémoire. L’échec est sporadique si le laps de temps entre le marshaling du délégué et le rappel qui s’ensuit est court. Cela est généralement le cas si la méthode non managée qui reçoit le pointeur de fonction ne l'enregistre pas pour une utilisation ultérieure, mais le rappelle immédiatement pour terminer son opération avant d'être retournée. De la même façon, davantage d'opérations garbage collection se produisent quand un système est en surcharge, ce qui rend plus probable l'exécution d'une opération garbage collection avant le rappel.

Résolution

Quand un délégué a été marshalé en tant que pointeur de fonction non managé, le récupérateur de mémoire ne peut pas effectuer le suivi de sa durée de vie. Votre code doit alors conserver une référence au délégué pendant toute la durée de vie du pointeur de fonction non managé. Pour cela, vous devez d'abord identifier le délégué qui a été collecté. Si l'Assistant Débogage managé est activé, il fournit le nom de type du délégué. Utilisez ce nom pour rechercher votre code d'appel de code non managé ou les signatures COM qui transmettent ce délégué au code non managé. Le délégué incriminé est transmis par l'un de ces sites d'appel. Vous pouvez également activer l’Assistant Débogage managé gcUnmanagedToManaged pour forcer une opération garbage collection avant chaque rappel dans le runtime. Vous êtes ainsi certain qu’une opération garbage collection se produit toujours avant le rappel. Une fois que vous savez quel délégué a été collecté, modifiez votre code pour conserver une référence à ce délégué dans le code managé pendant toute la durée de vie du pointeur de fonction non managé marshalé.

Effet sur le runtime

Quand les délégués sont marshalés en tant que pointeurs de fonction, le runtime alloue un thunk qui effectue la transition du non managé au managé. Ce thunk est en fait ce que le code non managé appelle avant que le délégué managé soit finalement appelé. Si l’Assistant Débogage managé callbackOnCollectedDelegate n’est pas activé, le code de marshaling non managé est supprimé quand le délégué est collecté. Si l’Assistant Débogage managé callbackOnCollectedDelegate est activé, le code de marshaling non managé n’est pas supprimé immédiatement quand le délégué est collecté. Au lieu de cela, les 1 000 dernières instances restent actives par défaut et sont modifiées pour activer l'Assistant Débogage managé au moment de l'appel. Le thunk est finalement supprimé après la collecte de 1 001 délégués marshalés supplémentaires.

Sortie

L'Assistant Débogage managé signale le nom de type du délégué qui a été collecté avant une tentative de rappel sur son pointeur de fonction non managé.

Configuration

L'exemple suivant montre les options de configuration de l'application. Il définit le nombre des thunks que l'Assistant Débogage managé garde actifs à 1 500. La valeur listSize par défaut est 1 000, le minimum 50 et le maximum 2 000.

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

Exemple

L'exemple suivant illustre une situation qui peut activer cet Assistant Débogage managé :

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

Voir aussi