Pourquoi Assembly.Load ne semble pas affecter le thread actuel lors de la résolution des références (et non par reflection)?

Je m’excuse par avance si le titre n’a pas de sens. Je suis très nouveau dans les domaines d’applications et de chargement d’assemblages et je ne sais pas comment dire ce que j’essaie de demander.

Je me suis amusé à charger des DLL incorporées dans une application lors de l’exécution et je n’arrive pas à comprendre pourquoi cela fonctionne dans un sens mais pas dans l’autre. Il semble que si vous essayez de charger des DLL (à partir d’un tableau d’octets) dans le domaine d’application actuel, les objects / threads créés après pourront résoudre les références à la nouvelle bibliothèque chargée. bibliothèque nouvellement chargée.

Voici mon exemple de bibliothèque qui sera chargé en tant que ressource incorporée lors de l’exécution (nécessite une référence à WPF PresentationFramework.dll pour MessageBox):

namespace LoaderLibrary { public class LoaderLibrary { public static void Test() { System.Windows.MessageBox.Show("success"); } } } 

Dans mon fichier .csproj de l’application console, j’ajoute manuellement la ressource incorporée suivante pour ce projet et inclut également une référence de projet à LoaderLibrary :

    EmbeddedResource.LoaderLibrary.dll   

Voici le code de mon application console qui charge cette bibliothèque ( nécessite une référence de projet à LoaderLibrary csproj ) ALSO : Vous devez définir CopyLocal sur false pour la référence LoaderLibrary:

 namespace AssemblyLoaderTest { class Program { static void Main(ssortingng[] args) { EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll"); System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); }; var app = new TestApp(); } } public class TestApp { public TestApp() { LoaderLibrary.LoaderLibrary.Test(); } } public class EmbeddedAssembly { static System.Collections.Generic.Dictionary assemblies = new System.Collections.Generic.Dictionary(); public static void Load(ssortingng embeddedResource) { using (System.IO.Stream stm = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedResource)) using (var mstream = new System.IO.MemoryStream()) { stm.CopyTo(mstream); var assembly = System.Reflection.Assembly.Load(mstream.ToArray()); assemblies.Add(assembly.FullName, assembly); return; } } public static System.Reflection.Assembly Get(ssortingng assemblyFullName) { return (assemblies.Count == 0 || !assemblies.ContainsKey(assemblyFullName)) ? null : assemblies[assemblyFullName]; } } } 

Ce code est capable de charger et d’exécuter avec succès la fonction LoaderLibrary.LoaderLibrary.Test ().

Ma question est pourquoi ce qui suit ne fonctionne pas?

 static void Main(ssortingng[] args) { EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll"); System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); }; LoaderLibrary.LoaderLibrary.Test(); // very unhappy line of code } 

Cela ne fonctionne pas non plus:

 static void Main(ssortingng[] args) { EmbeddedAssembly.Load("EmbeddedResource.LoaderLibrary.dll"); System.AppDomain.CurrentDomain.AssemblyResolve += (s, a) => { return EmbeddedAssembly.Get(a.Name); }; var app = new TestApp(); LoaderLibrary.LoaderLibrary.Test(); // very unhappy line of code } 

Un grand merci à Hans Passant et à Dthorpe d’avoir expliqué ce qui se passait.

J’ai trouvé la bonne explication de dthorpe sur le fonctionnement du compilateur JIT ici: compilation C # JIT et .NET

Pour citer dthorpe ici:

Oui, le code JIT d’IL implique la traduction de l’IL en instructions machine natives.

Oui, le runtime .NET interagit avec le code machine natif de JIT, en ce sens que le runtime possède les blocs de mémoire occupés par le code machine natif, les appels du runtime au code machine natif, etc.

Vous avez raison de dire que le runtime .NET n’interprète pas le code IL dans vos assemblys.

Que se passe-t-il lorsque l’exécution atteint une fonction ou un bloc de code (comme une clause else d’un bloc if) qui n’a pas encore été compilé JIT en code machine natif, le JIT’r est appelé pour comstackr ce bloc d’IL en code machine natif . Lorsque cela est fait, l’exécution du programme entre le code machine fraîchement émis pour exécuter sa logique de programme. Si, lors de l’exécution de ce code machine natif, l’exécution atteint un appel de fonction à une fonction qui n’a pas encore été compilée en code machine, le JIT’r est appelé pour comstackr cette fonction “juste à temps”. Etc.

JIT’r ne comstack pas nécessairement toute la logique d’un corps de fonction en un code machine à la fois. Si la fonction a des instructions if, les blocs d’instructions des clauses if ou else peuvent ne pas être JIT compilés jusqu’à ce que l’exécution passe réellement par ce bloc. Les chemins de code qui n’ont pas été exécutés restnt sous forme IL jusqu’à leur exécution.

Le code machine natif compilé est conservé en mémoire pour pouvoir être réutilisé lors de la prochaine exécution de la section de code. La deuxième fois que vous appelez une fonction, celle-ci s’exécutera plus rapidement que la première fois, car aucune étape JIT n’est nécessaire la deuxième fois.

Dans desktop .NET, le code machine natif est conservé en mémoire pendant toute la durée de vie du domaine d’application. Dans .NET CF, le code machine natif peut être jeté si l’application manque de mémoire. Il sera compilé à nouveau à partir du code IL d’origine lors de la prochaine exécution via ce code.

Avec les informations de cette question et celles de Hans Passant, ce qui se passe est très clair:

  1. Le compilateur JIT tente de convertir le bloc de code de point d’entrée entier (dans ce cas, ma fonction Main() ) en code natif. Cela nécessite de résoudre toutes les références.
  2. L’assembly incorporé LoaderLibrary.dll N’A PAS été chargé dans AppDomain car le code qui fait cela est défini dans la fonction Main() (et il ne peut pas exécuter de code qui n’a pas été compilé).
  3. Le compilateur JIT tente de résoudre la référence à LoaderLibrary.dll en effectuant une recherche dans AppDomain, Global Assembly Cache, App.config / Web.config et en effectuant une vérification (environnement PATH, répertoire de travail en cours, etc.). l’article MSDN ici: Comment le Runtime localise les assemblys
  4. Le compilateur JIT ne parvient pas à résoudre la référence à LoaderLibrary.LoaderLibrary.Test(); et entraîne l’erreur Could not load file or assembly or one of its dependencies

La solution proposée par Hans Passant consiste à charger vos assemblys dans un bloc de code compilant JIT plus tôt que n’importe quel bloc de code faisant référence à ces assemblys.

En ajoutant [MethodImpl (MethodImplOptions.NoInlining)] aux méthodes qui référencent les assemblys chargés de manière dynamic, cela empêchera l’optimiseur d’essayer de mettre en ligne le code de la méthode.