Partage
  • Partager sur Facebook
  • Partager sur Twitter

[c#] Chargement Assemblies dans un autre AppDomain

    26 décembre 2018 à 22:37:25

    Bonjour, tout est dans le titre. Je cherche à charger des assemblies ( .dll ) dans un AppDomain different du principal. Jusque là ça semble simple sauf que et bien j'ai toujours la même erreur qui me revient en boucle malgrè avoir essayé de dizaines de façons différentes... Voici un code exemple qui imite mon problème : 

    La méthode que j'utilise pour charger une dll :

            private static bool LoadDLL(AppDomain appDomain, string path)
            {
                try
                {
                    using (var fs = new FileStream(path, FileMode.Open))
                    {
                        var bytes = new byte[fs.Length];
                        fs.Read(bytes, 0, bytes.Length);
                        appDomain.Load(bytes);
                    }
                    return true;
                }
                catch (Exception ex)
                {
                    LogWarning(ex.GetType() + " -> " + ex.Message);
                    return false;
                }
            }

    Voici le code qui créé le nouveau AppDomain et appel la méthode :

            private static bool PrepareStivenoCore()
            {
                AppDomainSetup appDomainSetup = AppDomain.CurrentDomain.SetupInformation;
                Evidence evidence = AppDomain.CurrentDomain.Evidence;
                AppDomain MonSecondAD = AppDomain.CreateDomain("MonSecondAD", evidence, appDomainSetup);
                string MonAssembly = "chemin/d'accès/valide";
                return LoadDLL(MonSecondAD, MonAssembly);
            }

    Voilà et donc c'est la ligne "appDomain.Load(bytes);" qui lève "System.IO.FileNotFoundException -> Impossible de charger le fichier ou l'assembly 'StivenoModulesInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' ou une de ses dépendances. Le fichier spécifié est introuvable."

    (StivenoModulesInterfaces) étant l'assembly que j'essaye de charger. J'ai bien vérifier le fichier existe bien, le chemin d'accès est valide. J'ai tenté d'utiliser l'événement 'AssemblyResolve' pour voir s'il manquant un assembly mais apparemment non. Et ce qui m'étonne c'est que si je fait EXACTEMENT la même chose mais pour l'AppDomain principal tout fonctionne correctement. Hors je dois absolument le charger dans un AppDomain secondaire pour pouvoir le décharger avec AppDomain.unload();

    Je pense que pour résoudre mon problème je dois utiliser MarshalByRefObject mais je comprend absolument pas comment ça marche. La doc Microsoft est très superficiel https://docs.microsoft.com/fr-fr/dotnet/api/system.marshalbyrefobject?view=netframework-4.7.2 et les réponses des forums style StackOverFlow ne m'avance pas plus. J'ai parcourus des dizaines et des dizaines de topics anglais mais toujours pas avancé. Pourriez vous-donc me dire ce qui cloche dans mon code. Dois-je utiliser cette histoire de proxy avec MarshalByRefObject et si oui comment ? Je tourne en rond depuis 2 jours j'ai vraiment besoin qu'on m'aide je suis dans un flou total.


    Merci d'avance


    • Partager sur Facebook
    • Partager sur Twitter
      2 janvier 2019 à 20:05:11

      A la base, c'est laquelle des 7 versions de la méthode "Load" d'AppDomain, vous essayez d'utiliser ??? :

      https://docs.microsoft.com/fr-fr/dotnet/api/system.appdomain.load?view=netframework-4.7.2

      Chaque AppDomain a ses propres configurations, de sécurité en particulier, donc c'est pas parce que cela fonctionne avec un AppDomain que cela fonctionne avec tous les AppDomain.

      Si c'est un problème de chemin, l'outil ProcessMonitor vous indiquera où le loader .NET cherche les assemblies qu'il ne trouve pas.

      https://docs.microsoft.com/en-us/sysinternals/downloads/procmon

      • Partager sur Facebook
      • Partager sur Twitter
      Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
        8 janvier 2019 à 19:23:37

        Bonsoir et MERCI BEAUCOUP pour ta réponse tu m'as fait connaitre un logiciel TRÈS intéressante qui m'a en plus permis de comprendre quel était mon problème. Voici un screen de ProcessMonitor : 

        Ce que je comprend en lisant ça c'est que lorsque dans mon code j'ai une ligne qui fait appel à des types venant de l'assembly que j'ai en référence, le runtime cherche mon assembly dans les différents chemin que l'on peut voir et que en effet il ne les trouves pas. Ce qui est étonnant c'est que à ce stade d'execution les assembly qu'il cherche ont déjà été chargé via la méthode appDomain.Load et je fais même une vérification avec "AppDomain.CurrentDomain.GetAssemblies()". J'avais déjà remarqué que au lancement d'un executable, le runtime charge automatiquement les assemblies référencés depuis le dossier de travail courant ( où se trouve mon .exe ). J'ai remarqué aussi que si les assembly ne se trouve pas dans ce fameux dossier ce n'est pas grave le programme s'execute quand même mais évidement dès qu'il rencontre du code faisant référence à un des assemblies référencé il plante ce qui est normal. Ce que je veux donc faire c'est pouvoir charger mes assemblies manuellement, depuis n'importe quel chemin d'accès et que le runtime comprenne que les assemblies que j'ai chargé sont les référence qu'il n'a pas pu chargé automatiquement au début. Merci de votre aide :)

        • Partager sur Facebook
        • Partager sur Twitter
          9 janvier 2019 à 10:52:35

          >je fais même une vérification avec "AppDomain.CurrentDomain.GetAssemblies()"

          Vous connaissez les vérifications en questions ?

          Comme le Runtime .NET "JIT", je suis pas sûr qu'il fasse beaucoup de vérification à la volée.

          Il existe beaucoup (trop?) de moyen de configurer les recherches des assemblies dans les systèmes de fichier.

          Il faut donc configurer le Runtime .NET à vos besoins.

          Le dossier de travail courant n'est pas forcément le répertoire contenant le fichier de l'exécutable, c'est même la source de bien des erreurs de programmation dans ce domaine.

          >c'est pouvoir charger mes assemblies manuellement, depuis n'importe quel chemin d'accès

          Le GAC ou l'usage de chemin absolue ou une approche rigoureuse de la configuration du Runtime (et donc ne pas confondre Working Directory et Le répertoire contenant le fichier image du programme) devrait suffire, non ?

          • Partager sur Facebook
          • Partager sur Twitter
          Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
            11 janvier 2019 à 22:44:01

            Bonsoir, merci de votre réponse en effet mon vocabulaire n'est pas très pro. Enfaîte j'arrive à charger des assemblies depuis n'importe quel chemin absolue, mais je n'arrive pas à les charger de tel sorte que le runtime comprenne qu'il s'agisse des références qui n'ont pas pu être chargés plus tôt. Je sais que je pourrais utiliser l’événement AppDomain.CurrentDomain.AssemblyResolve mais j'aimerais réussir à faire sans. Peu être que si je vous donne un projet exemple vous comprendriez mieux ce que j'essaye de faire. Voici un lien vers un projet que je viens de faire pour illustrer la situation et ainsi pourriez vous peu être me dire comment faire : https://www.sendspace.com/file/hmeibx
            • Partager sur Facebook
            • Partager sur Twitter
              18 janvier 2019 à 11:03:33

              Je ne comprends pas trop votre exemple car vous vous servez des types des assemblies directement dans le main, donc à charger dans l'AppDomain principal d_s le lancement du programme.

              Votre besoin initial, c'est quoi concrètement ?

              Du DelayLoading de d'assembly comme du DelayLoading de Dll ?

              Une gestion de plug-Ins static au démarrage ?

              Une gestion de plug-Ins dynamique ?

              etc ...

              • Partager sur Facebook
              • Partager sur Twitter
              Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                18 janvier 2019 à 18:49:13

                Bonjour merci de votre réponse. Je ne comprend pas ce que signifie un plug-ins static ou dynamique. Je sais qu'un plug-ins est un "bout de code" externe que l'on ajoute à son programme mais quel différence entre static et dynamique ?

                Je viens de trouver un article très intéressant qui explique la manière dont le runtime charge les dll :

                0
                <button class="js-vote-down-btn grid--cell s-btn s-btn__unset c-pointer" style="box-sizing: inherit; font: inherit; position: relative; padding: 0px; border-width: initial; border-style: none; border-color: initial; border-radius: 3px; background-image: none; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial; outline: none; box-shadow: none; margin: 2px;" title="This answer is not useful" data-selected-classes="fc-theme-primary" />

                This article explains in detail how it works in .NET. Summary of key points:

                There are a number of different ways that assemblies are loaded in .NET. When you create a typical project, assemblies usually come from:

                • The Assembly reference list of the top level 'executable' project

                • The Assembly references of referenced projects

                • Dynamically loaded assemblies, using runtime loading via AppDomain or Reflection loading

                and

                .NET automatically loads mscorlib (most of the System namespace) as part of the .NET runtime hosting process that hoists up the .NET runtime in EXE apps, or some other kind of runtime hosting environment (runtime hosting in servers like IIS, SQL Server or COM Interop).

                and

                • Dependent Assembly References are not pre-loaded when an application starts (by default)

                • Dependent Assemblies that are not referenced by executing code are never loaded

                • Dependent Assemblies are just in time loaded when first referenced in code

                • Once Assemblies are loaded they can never be unloaded, unless the AppDomain that host them is unloaded.

                La partie qui m’intéresse et celle que j'ai mise en gras. On apprend donc pour simplifier la chose que les Dependent Assemblies ne sont chargé que lorsque le runtime rencontre la première ligne de code qui fait reférence à un Dependent Assembly. Le soucis c'est que lorsque le runtime rencontre une ligne de code qui fait reference à un Dependent Assembly, il va chercher l'assembly en question dans le GAC ou bien le working path ( il me semble ). Le soucis c'est que les Dependent Asemblies qu'il cherche ne se trouveront dans aucun de ces 2 dossier dans la version finale de mon programme mais dans un dossier bien appart et par forcément le même selon les Dependent Assemblies. C'est pour cela que j'aimerais pouvoir charger les Dependent Assemblies manuellement au début de mon code car de ce fait je pourrais les charger depuis le chemin d'accès que je veux. Le problème c'est que ce que je veux faire marche en partie, mais le runtime de c# 'croit' que les assemblies que j'ai chargés manuellement au début ne sont pas les Dependent Assemblies dont il a besoin donc forcément c'est comme si je n'avais rien chargé ... Que pouvez vous donc me proposer comme solution. J'espère avoir été plus claire et je m'en excuse si mon problème est très confus, encore une fois je débute en C# et je n'ai que 16 ans ce qui explique cela. Merci :)

                -
                Edité par Nutchaxo 18 janvier 2019 à 19:03:12

                • Partager sur Facebook
                • Partager sur Twitter
                  22 janvier 2019 à 18:25:07

                  >mais quel différence entre static et dynamique ?

                  Est statique quand le plug-Ins est chargé au début du programme et déchargé à la fin du programme.

                  Dynamique, c'est quand on peut charger et décharger le Plug-Ins a n'importe quel moment.

                  >il va chercher l'assembly en question dans le GAC ou bien le working path

                  Ça dépend, c'est fonction de la configuration de l'AppDomain qui charge l'assembly.

                  Exemple de modification du comportement d'un AppDomain:

                  https://docs.microsoft.com/fr-fr/dotnet/api/system.appdomain.basedirectory?view=netframework-4.7.2

                  S'il n'y a pas de collision de nom entre les assemblies :

                  https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/probing-element

                  etc...

                  Je ne comprends toujours pas pourquoi vous lutter contre le runtime .NET au lieu de l'utiliser à votre profit.

                  Si vous pouvez nous illustrer votre problème avec un petit cas simple et concret de votre problème, ça serait cool.

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                    23 janvier 2019 à 12:43:40

                    Et bien imaginons que je souhaite créer une application portable. Toute mon application ( .exe + DLL ) se trouverait dans un dossier jusque là il n'y a aucun problème puisque le runtime charge les assembly se trouvant dans le même dossier que mon .exe mais si je souhaite créer une hiérarchie du style : 

                    Et bien j'ai besoin de pouvoir charger mes Assembly depuis le dossier librairies et le runtime n'ira pas de lui même par défaut. Et je préfère éviter de passer par les private_path de l'AppDomain. J'aimerais vraiment pouvoir les charger depuis où je veux et que le runtime 'remarque' que ce que j'ai charger c'est les même assembly que j'ai référencé. Pour ce qui est du statique et dynamique. J'ai besoin des 2. Certains Assemblies sont des références je veux donc les charger Statiquement donc dans l'appdomain principal en revanche par la suite j'aurais des assemblies ( plug-ins ) que je chargerais dynamiquement dans un second appdomain mais là n'est pas le problème.

                    • Partager sur Facebook
                    • Partager sur Twitter
                      23 janvier 2019 à 14:52:33

                      >une application portable

                      Définissez plus précisément "portable", SVP ?

                      >le runtime n'ira pas de lui même par défaut.

                      C'est pile-poil le rôle de l'élément XML "probing" du fichier de configuration.

                      >par les private_path de l'AppDomain

                      De quoi parlez-vous précisément, SVP ?

                      Et aussi, pourquoi supprimer une possibilité si elle convient au besoin ???

                      >depuis où je veux

                      C'est le rôle de l'évènement "AssemblyResolve".

                      > j'ai charger c'est les même assembly que j'ai référencé

                      Je ne comprends pas, soit c'est des assemblies référencés soit des assemblies chargées dynamiquement.

                      Si elles sont référencées, pourquoi les recharger "dynamiquement" ???

                      Si vous pouvez nous illustrer votre problème avec un petit cas simple et concret de votre problème, ça serait cool.(BIS)

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                        25 janvier 2019 à 23:05:19

                        Le soucis de l'élément XML "probing" c'est que si je ne dis pas de bêtise c'est une valeur constante. Ensuite ce que j'entend pas portable c'est que je puisse l’exécuter depuis une clé USB par exemple et d'un ordinateur à un autre sans avoir à l'installer sur l'ordinateur en question. Dans les AppDomain il y a une propriété PrivatePath et bien je souhaiterais ne pas passer par là, de plus PrivatePath est deprecated d'après la Visual Studio. Ensuite j'ai connaissance de l'évènement AssemblyResolve qui en soit résous parfaitement mon problème mais j'aimerais vraiment pouvoir au lancement dans la méthode 'Main' de mon application pouvoir dire où sont chaque Assemblies que j'ai en références.

                        Je sais bien que je me complique probablement la vie mais pour ce que je souhaite faire je dois passer par là j'ai déjà essayé les autres options et ça ne donne pas exactement ce que je cherche.

                        --

                        Merci de prendre le temps de m'aider :)

                        • Partager sur Facebook
                        • Partager sur Twitter
                          26 janvier 2019 à 11:28:36

                          Tu donnes pas d'exemples concrets de ce que tu «cherches» à faire (et bacelar l'a demandé 2 fois déjà) ; faut pas t'etonner derrière…

                          Sinon, j'ai survolé le sujet, mais le managed extensibility framework (MEF) ça peut pas aider ?

                          • Partager sur Facebook
                          • Partager sur Twitter
                          Censément, quelqu'un de sensé est censé s'exprimer sensément.
                            1 février 2019 à 20:25:12

                            Effectivement après quelques recherches @Sehnsucht, le MEF semble correspondre à ce que je cherche. Je vais donc poursuivre mes recherches et vous tiendrais au courant si mon problème est résolut. Je ne donne pas d'exemple concret pour la simple et bonne raison que je n'en ai pas spécialement qui corresponde à la situation.
                            • Partager sur Facebook
                            • Partager sur Twitter

                            [c#] Chargement Assemblies dans un autre AppDomain

                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                            • Editeur
                            • Markdown