GCHandle.FromIntPointer ne fonctionne pas comme prévu

Voici un programme très simple (complet) pour utiliser GCHandle.FromIntPointer:

using System; using System.Runtime.InteropServices; namespace GCHandleBugTest { class Program { static void Main(ssortingng[] args) { int[] arr = new int[10]; GCHandle handle = GCHandle.Alloc(arr, GCHandleType.Pinned); IntPtr pointer = handle.AddrOfPinnedObject(); GCHandle handle2 = GCHandle.FromIntPtr(pointer); } } } 

Notez que ce programme est essentiellement une translittération de la procédure décrite en anglais sur CLR via C # (4e) à la page 547. Son exécution entraîne toutefois une exception non gérée, telle que:

Additional Information: The runtime has encountered a fatal error. The address of the error was at 0x210bc39b, on thread 0x21bc. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

Pensant que cela pourrait être un bogue dans .NET 4.5, et comme je ne vois rien manifestement être faux, j’ai essayé exactement le même programme dans Mono sous Linux (v2.10.8.1). J’ai eu une exception légèrement plus GCHandle value belongs to a different domain. mais toujours déroutante GCHandle value belongs to a different domain.

Autant que je sache, le handle appartient vraiment au même AppDomain que le code où j’appelle GCHandle.FromIntPtr . Mais le fait que je voie une exception dans les deux implémentations me fait penser qu’il me manque des détails importants. Que se passe t-il ici?

AddrOfPinnedObject n’est pas le contraire de FromIntPtr . Vous voulez plutôt ToIntPtr :

 IntPtr pointer = handle.ToIntPtr (); GCHandle handle2 = GCHandle.FromIntPtr (pointer); 

FromIntPtr ne prend pas l’adresse de l’object, il prend une valeur opaque (définie comme IntPtr), utilisée pour récupérer l’object avec ToIntPtr .

Vous avez le mauvais modèle mental. FromIntPtr () peut uniquement reconvertir la valeur obtenue de ToIntPtr (). Ce sont des méthodes pratiques, utiles en particulier pour stocker une référence à un object géré (et le garder en vie) dans du code non géré. La classe de modèle gcroot <> en dépend, utilisée dans les projets C ++. C’est pratique car le code non managé doit seulement stocker le pointeur.

La valeur sous-jacente, le pointeur réel, s’appelle un “handle”, mais il s’agit en réalité d’un pointeur dans une table que le garbage collector conserve. La table crée des références supplémentaires aux objects, en plus de celles trouvées par le récupérateur de place. Permettre à un object géré de survivre même si le programme n’a plus de référence valide à l’object.

GCHandle.AddrOfPinnedObject () renvoie un pointeur complètement différent. Il pointe sur l’object géré actuel et non sur le “descripteur”. Le message d’exception “appartient à un autre domaine” est compréhensible car la table que j’ai mentionnée est associée à un AppDomain.

Le crash dans .NET 4.5 ressemble fortement à un bogue. Il effectue un test avec une fonction CLR interne appelée MarshalNative :: GCHandleInternalCheckDomain (). La version v2 du CLR déclenche une ArgumentException avec le texte du message “Impossible de transmettre un GCHandle à travers AppDomains.”. Mais la version v4 se bloque à l’intérieur de cette méthode, ce qui génère l’exception ExecutionEngineException. Cela ne semble pas intentionnel.

Rapport de commentaires déposé sur connect.microsoft.com