Chargement des DLL au moment de l'exécution en C #

91

J'essaie de comprendre comment vous pourriez importer et utiliser un .dll au moment de l'exécution dans une application C #. En utilisant Assembly.LoadFile (), j'ai réussi à obtenir mon programme pour charger la dll (cette partie fonctionne certainement car je suis capable d'obtenir le nom de la classe avec ToString ()), mais je ne peux pas utiliser le 'Output' méthode depuis l'intérieur de mon application console. Je compile le .dll puis je le déplace dans le projet de ma console. Y a-t-il une étape supplémentaire entre CreateInstance et la possibilité d'utiliser les méthodes?

Voici la classe dans ma DLL:

namespace DLL
{
    using System;

    public class Class1
    {
        public void Output(string s)
        {
            Console.WriteLine(s);
        }
    }
}

et voici l'application que je souhaite charger la DLL

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}
danbroooks
la source

Réponses:

128

Les membres doivent pouvoir être résolus au moment de la compilation pour être appelés directement à partir de C #. Sinon, vous devez utiliser la réflexion ou des objets dynamiques.

Réflexion

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                type.InvokeMember("Output", BindingFlags.InvokeMethod, null, c, new object[] {@"Hello"});
            }

            Console.ReadLine();
        }
    }
}

Dynamique (.NET 4.0)

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                dynamic c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}
Faucon noir
la source
12
Notez que cela essaie d'appeler Outputtous les types de l'assemblage, ce qui sera probablement lancé avant que la classe «correcte» ne soit trouvée ...
Reed Copsey
1
@ReedCopsey, d'accord, mais pour son exemple simple, son type est le seul visible. "Les seuls types visibles en dehors d'un assembly sont les types publics et les types publics imbriqués dans d'autres types publics." Pour un exemple non trivial, ce sera évidemment un problème ...
Dark Falcon
1
Neat avec les deux exemples! :)
Niels Abildgaard
22
C'est pourquoi les interfaces sont souvent utilisées et vous pouvez effectuer une détection de fonctionnalités telles que IDog dog = someInstance as IDog;et tester si elle n'est pas nulle. Placez vos interfaces dans une DLL commune partagée par les clients, et tout plugin qui sera chargé dynamiquement doit implémenter cette interface. Cela vous permettra ensuite de coder votre client avec l'interface IDog et d'avoir une vérification de type intellisense + strong au moment de la compilation plutôt que d'utiliser Dynamic.
AaronLS
1
@ Tarek.Mh: Cela nécessiterait une dépendance à la compilation sur Class1. À ce stade, vous pouvez simplement utiliser new Class1(). Le demandeur a explicitement spécifié une dépendance d'exécution. dynamicpermet au programme de ne pas exiger du tout une dépendance à la compilation sur Class1.
Dark Falcon
39

À l'heure actuelle, vous créez une instance de chaque type défini dans l'assemblage . Il vous suffit de créer une seule instance de Class1pour appeler la méthode:

class Program
{
    static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var theType = DLL.GetType("DLL.Class1");
        var c = Activator.CreateInstance(theType);
        var method = theType.GetMethod("Output");
        method.Invoke(c, new object[]{@"Hello"});

        Console.ReadLine();
    }
}
Reed Copsey
la source
19

Vous devez créer une instance du type qui expose la Outputméthode:

static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var class1Type = DLL.GetType("DLL.Class1");

        //Now you can use reflection or dynamic to call the method. I will show you the dynamic way

        dynamic c = Activator.CreateInstance(class1Type);
        c.Output(@"Hello");

        Console.ReadLine();
     }
Alberto
la source
Merci beaucoup - c'est exactement ce que je recherche. Je ne peux pas croire que ce ne soit pas mieux noté que les autres réponses, car cela montre l'utilisation du mot-clé dynamique.
skiphoppy
Ah, maintenant je vois que c'était aussi dans la réponse de DarkFalcon. Le vôtre était plus court et permettait de voir plus facilement. :)
skiphoppy
0

Activator.CreateInstance() renvoie un objet qui n'a pas de méthode Output.

On dirait que vous venez de langages de programmation dynamiques? C # n'est certainement pas cela, et ce que vous essayez de faire sera difficile.

Puisque vous chargez une dll spécifique à partir d'un emplacement spécifique, vous souhaitez peut-être simplement l'ajouter comme référence à votre application console?

Si vous souhaitez absolument charger l'assembly via Assembly.Load, vous devrez passer par la réflexion pour appeler les membres surc

Quelque chose comme type.GetMethod("Output").Invoke(c, null);ça devrait le faire.

Fredrik
la source
0
foreach (var f in Directory.GetFiles(".", "*.dll"))
            Assembly.LoadFrom(f);

Cela charge toutes les DLL présentes dans le dossier de votre exécutable.

Dans mon cas, j'essayais d'utiliser Reflectionpour trouver toutes les sous-classes d'une classe, même dans d'autres DLL. Cela a fonctionné, mais je ne suis pas sûr que ce soit la meilleure façon de le faire.

EDIT: Je l'ai chronométré, et il ne semble les charger que la première fois.

Stopwatch stopwatch = new Stopwatch();
for (int i = 0; i < 4; i++)
{
    stopwatch.Restart();
    foreach (var f in Directory.GetFiles(".", "*.dll"))
        Assembly.LoadFrom(f);
    stopwatch.Stop();
    Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

Sortie: 34 0 0 0

On pourrait donc potentiellement exécuter ce code avant toute recherche Reflection au cas où.

Samuel Cabrera
la source
-1

Ce n'est pas si difficile.

Vous pouvez inspecter les fonctions disponibles de l'objet chargé, et si vous trouvez celle que vous recherchez par son nom, fouillez ses paramètres attendus, le cas échéant. Si c'est l'appel que vous essayez de trouver, appelez-le à l'aide de la méthode Invoke de l'objet MethodInfo.

Une autre option consiste simplement à créer vos objets externes dans une interface et à convertir l'objet chargé dans cette interface. En cas de succès, appelez la fonction de manière native.

Ce sont des choses assez simples.

ChrisH
la source
Wow, je ne sais pas pourquoi les votes négatifs. J'ai une application de production qui fait exactement cela depuis les 12 dernières années. * hausser les épaules * Tout le monde a besoin de code pour faire ça, envoyez-moi un message. Je vais emballer des parties de mon code de production et l'envoyer avec moi.
ChrisH
10
Je soupçonne que les votes négatifs auraient à voir avec le manque d'exemples et le ton condensé ... On dirait que vous avez la base d'une réponse complète, alors n'ayez pas peur de modifier plus en détail :)
Shadow
1
C'est juste un peu impoli de dire "ce sont des choses assez simples", et c'est pourquoi vous avez obtenu des votes négatifs.
ABPerson
1
Je n'étais pas impoli ou condescendant ... il y a 6 ans. Le ton n'apparaît pas clairement dans le texte. C'était vraiment censé être assez léger ... J'ai aussi vraiment l' impression d'avoir un lien vers un échantillon de code là-dedans toutes ces années, et je n'ai aucune idée d'où il est allé (en supposant qu'il était vraiment là comme je me souviens ). : \
ChrisH
Je ne sais pas comment fonctionne MethodInfo mais cela semble précieux. Je dirais que votre réponse a le potentiel d'être meilleure que celle actuellement acceptée, mais elle devrait être complétée. Si jamais vous y arriviez, ce serait apprécié. Si tel est le cas, veuillez ne pas créer de lien vers un exemple de code. Ceux-ci peuvent se briser à l'avenir. Il est préférable de fournir l'échantillon vous-même, avec éventuellement un lien vers une source ou des informations supplémentaires pour continuer à lire.
SpaghettiCook