2016-02-18 1 views
2

Je m'excuse d'avance si le titre n'a pas de sens. Je suis très novice dans le domaine des applications et du chargement d'assemblages et je ne sais pas vraiment comment dire ce que j'essaie de demander.Pourquoi Assembly.Load ne semble-t-il pas affecter le thread en cours lors de la résolution de références (pas par réflexion)?

J'ai essayé de charger des DLL incorporées dans une application pendant 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 actuel, tous les objets/threads créés après cela seront capables de résoudre les références sur la bibliothèque nouvellement chargée, mais les objets dans le contexte d'origine ne seront pas résolus. bibliothèque nouvellement chargée.

Voici mon exemple de bibliothèque qui sera chargée à partir comme une ressource intégrée lors de l'exécution (nécessite une référence à la WPF PresentationFramework.dll pour MessageBox):

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

Dans mon application console .csproj I ajouter manuellement les éléments suivants ressource incorporée pour ce projet et comprennent une référence de projet à LoaderLibrary ainsi:

<ItemGroup> 
    <EmbeddedResource Include="..\LoaderLibrary\bin\$(Configuration)\LoaderLibrary.dll"> 
     <LogicalName>EmbeddedResource.LoaderLibrary.dll</LogicalName> 
    </EmbeddedResource> 
    </ItemGroup> 

Voici la co de mon application console qui charge cette bibliothèque (nécessite une référence de projet au LoaderLibrary csproj) AUSSI: Nécessité de mettre CopyLocal à faux pour référence LoaderLibrary:

Ce code est en mesure pour charger et exécuter la fonction LoaderLibrary.LoaderLibrary.Test().

Ma question est pourquoi ce qui suit ne fonctionne pas?

static void Main(string[] 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(string[] 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 
} 
+4

Les assemblys sont chargés par le compilateur juste-à-temps. Il doit convertir votre méthode Main() en code machine avant de pouvoir démarrer. Cela arrive bien sûr * avant * qu'il ne fonctionne, donc AssemblyResolve ne peut rien faire pour aider à trouver l'assembly. Plus mal dans la version Release, l'optimiseur regarde vers l'avenir pour trouver des opportunités pour les méthodes en ligne. Vous avez besoin d'une autre méthode, par exemple RealMain(), déplacez l'appel Test() dans cette méthode. Et donnez-lui l'attribut [MethodImpl (MethodImplOptions.Nolinling)] pour ralentir l'optimiseur. –

+0

Donc, fondamentalement, il prend n'importe quelle fonction est le point d'entrée et le convertit en IL avant d'essayer d'exécuter du code? Et puisque l'assemblage n'a pas été techniquement ajouté à l'AppDomain, il en résulte une résolution de dépendance échouée? –

+0

Ok, je suis presque sûr de le comprendre maintenant. Fondamentalement, lors de la compilation JIT du point d'entrée, il ne parvient pas à résoudre la dépendance car il ne peut évidemment exécuter aucun code avant de le compiler, ce qui entraîne l'erreur que je voyais. Déplacer les appels vers les assemblys chargés dynamiquement dans une portée de fonction différente et marquer les méthodes qui les appellent avec [MethodImpl (MethodImplOptions.Nolinling)] permet d'empêcher l'optimiseur de déplacer le code qui référence les assemblys chargés dynamiquement dans une portée cela serait compilé avant le chargement de l'assemblage. –

Répondre

1

Un grand merci à Hans et dthorpe pour Passant expliquer ce qui se passait.

Je trouve dthorpe est une grande explication sur la façon dont le compilateur JIT fonctionne ici: C# JIT compiling and .NET

Pour citer dthorpe ici:

Oui, le code JIT'ing IL consiste à traduire l'IL dans la machine native instructions.

Oui, le moteur d'exécution .NET interagit avec le code machine natif JIT'ed, dans le sens où l'exécution possède les blocs de mémoire occupés par le code machine natif , le moteur d'exécution appelle dans le code machine natif, etc.

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

Ce qui arrive est lorsque l'exécution atteint un bloc fonctionnel ou un code (comme, une clause else d'un si bloc) qui n'a pas encore été JIT compilé dans code machine natif, le JIT'r est invoqué pour compiler ce bloc de 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 atteint une fonction appel à une fonction qui n'a pas encore été compilée en code machine, le JIT'r est invoqué pour compiler cette fonction "juste à temps". Etc.

Le JIT'r ne compile pas nécessairement toute la logique d'un corps de fonction en code machine à la fois. Si la fonction a des instructions if, les blocs d'instructions des clauses if ou else peuvent ne pas être compilés JIT jusqu'à ce que l'exécution passe effectivement par ce bloc. Les chemins de code que n'ont pas exécutés restent sous forme d'IL jusqu'à ce qu'ils s'exécutent.

Le code machine natif compilé est conservé en mémoire afin qu'il puisse être utilisé à nouveau la prochaine fois que cette section de code s'exécutera. La seconde fois que vous appelez une fonction, elle fonctionnera plus vite que la première fois que vous appelez car aucune étape JIT n'est nécessaire la deuxième fois.

Dans le bureau .NET, le code machine natif est conservé en mémoire pour la durée de vie du domaine d'application. Dans .NET CF, le code machine natif peut être si l'application est à court de mémoire. Il sera JIT compilé à partir du code IL d'origine la prochaine fois que l'exécution passe à travers ce code.

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

  1. Les tentatives du compilateur JIT pour convertir l'ensemble du code de point d'entrée bloc (dans ce cas ma fonction Main()) en code natif. Ce exige qu'il résolve toutes les références.
  2. L'assembly incorporé LoaderLibrary.dll n'a PAS encore été chargé dans le AppDomain car le code qui le fait est défini dans la fonction Main() (et ne peut pas exécuter du code qui n'a pas été compilé).
  3. Les tentatives du compilateur JIT pour résoudre la référence à LoaderLibrary.dll en recherchant l'AppDomain, Global Assembly Cache, App.config/web.config et sondage (environnement PATH, actuel répertoire de travail, etc.) Plus d'info sur ce qui peut être trouvé dans l'article MSDN ici: How the Runtime Locates Assemblies
  4. le compilateur JIT ne parvient pas à résoudre la référence à LoaderLibrary.LoaderLibrary.Test(); et les résultats dans l'erreur Could not load file or assembly or one of its dependencies

la façon de contourner cela comme suggéré par Hans Passant consiste à charger vos assemblys dans un bloc de code qui obtient JIT compilé plus tôt que n'importe quel bloc de code qui fait référence à ces assemblys. En ajoutant le [MethodImpl (MethodImplOptions.NoInlining)] pour les méthodes qui référencent les assemblys chargés dynamiquement, cela empêchera l'optimiseur d'essayer d'aligner le code de la méthode.