callbackOnCollectedDelegate (MDA)
L'Assistant Débogage managé (MDA, Managed Debugging Assistant) callbackOnCollectedDelegate est activé si un délégué est marshalé d'un code managé vers un code non managé en tant que pointeur fonction et si un rappel est placé sur ce pointeur fonction après la récupération du délégué par le garbage collector.
Symptômes
Les violations d'accès se produisent lors des tentatives d'appel de code managé par les pointeurs 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.
L'échec n'est pas cohérent ; tantôt l'appel sur le pointeur fonction aboutit, tantôt il échoue. Il se peut que l'échec se produise uniquement sous une charge élevée ou sur un nombre aléatoire de tentatives.
Cause
Le délégué à partir duquel le pointeur fonction a été créé et exposé au code non managé a été récupéré par le garbage collector. Lorsque le composant non managé tente d'appeler sur le pointeur fonction, il génère une violation d'accès.
L'échec semble aléatoire car il dépend du moment où l'opération garbage collection se produit. Si un délégué peut participer à l'opération garbage collection, celle-ci peut avoir lieu après le rappel et l'appel aboutit. Dans d'autres cas, 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é de l'échec dépend du laps de temps entre le moment où le délégué est marshalé et le rappel sur le pointeur fonction ainsi que de la fréquence des opérations garbage collection. L'échec est sporadique si le laps de temps entre le moment où le délégué est marshalé et le rappel résultant est court. Cela est généralement le cas si la méthode non managée qui reçoit le pointeur fonction n'enregistre pas le pointeur fonction pour une utilisation ultérieure mais rappelle immédiatement le pointeur fonction pour terminer son opération avant d'être retournée. De la même façon, davantage d'opérations garbage collection se produisent lorsqu'un système est sous une charge élevée, ce qui rend plus probable l'exécution d'une opération garbage collection avant le rappel.
Solution
Une fois qu'un délégué a été marshalé en tant que pointeur fonction non managé, le garbage collector ne peut pas effectuer le suivi de sa durée de vie. Votre code doit plutôt conserver une référence au délégué pour la durée de vie du pointeur fonction non managé. Pour ce faire, vous devez d'abord identifier le délégué qui a été collecté. Lorsque le MDA est activé, il fournit le nom de type du délégué. Utilisez ce nom afin de rechercher votre code pour l'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 permettre au MDA gcUnmanagedToManaged d'obliger une opération garbage collection avant chaque rappel dans le runtime. Cela permet de supprimer l'incertitude introduite par l'opération garbage collection en garantissant 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é sur le côté managé pour la durée de vie du pointeur fonction non managé marshalé.
Effet sur le runtime
Lorsque les délégués sont marshalés en tant que pointeurs fonction, le runtime alloue un thunk qui effectue la transition du code non managé au code managé. Ce thunk est en fait ce que le code non managé appelle avant que le délégué managé soit finalement appelé. Sans l'activation du MDA callbackOnCollectedDelegate, le code de marshaling non managé est supprimé lorsque le délégué est collecté. Avec l'activation du MDA callbackOnCollectedDelegate, le code de marshaling non managé n'est pas immédiatement supprimé lorsque 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 le MDA lorsqu'il est appelé. Le thunk est finalement supprimé après que 1 001 délégués marshalés supplémentaires ont été collectés.
Sortie
Le MDA signale le nom de type du délégué qui a été collecté avant une tentative de rappel sur son pointeur fonction non managé.
Configuration
L'exemple suivant montre les options de configuration de l'application. Il définit le nombre des conversions de code que le MDA garde actif à 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 ce 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"); }
}
Voir aussi
Référence
Assistant Débogage managé gcUnmanagedToManaged
Concepts
Diagnostic d'erreurs avec les Assistants de débogage managés