Ładowanie bibliotek DLL w czasie wykonywania w języku C #

91

Próbuję dowiedzieć się, jak można przejść do importowania i używania .dll w czasie wykonywania wewnątrz aplikacji C #. Używając Assembly.LoadFile () udało mi się zmusić mój program do załadowania biblioteki dll (ta część zdecydowanie działa, ponieważ jestem w stanie uzyskać nazwę klasy za pomocą ToString ()), jednak nie mogę użyć opcji „Wyjście” metoda z wnętrza mojej aplikacji konsoli. Kompiluję plik .dll, a następnie przenoszę go do projektu mojej konsoli. Czy istnieje dodatkowy krok między CreateInstance a możliwością korzystania z metod?

To jest klasa w mojej bibliotece DLL:

namespace DLL
{
    using System;

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

a oto aplikacja, którą chcę załadować bibliotekę 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
źródło

Odpowiedzi:

128

Elementy członkowskie muszą być rozpoznawalne w czasie kompilacji, aby były wywoływane bezpośrednio z C #. W przeciwnym razie musisz użyć odbić lub obiektów dynamicznych.

Odbicie

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();
        }
    }
}

Dynamiczny (.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();
        }
    }
}
Dark Falcon
źródło
12
Zauważ, że to próbuje wywołać Outputkażdy typ w assemblerze, który prawdopodobnie wyrzuci, zanim "właściwa" klasa zostanie znaleziona ...
Reed Copsey
1
@ReedCopsey, zgadzam się, ale dla jego prostego przykładu jego typ jest widoczny. „Jedynymi typami widocznymi poza zestawem są typy publiczne i typy publiczne zagnieżdżone w innych typach publicznych”. Dla nietrywialnego przykładu, oczywiście będzie to problem ...
Dark Falcon
1
Zgrabnie z dwoma przykładami! :)
Niels Abildgaard
22
Dlatego często używane są interfejsy i można wykrywać funkcje, na przykład IDog dog = someInstance as IDog;i sprawdzać, czy nie są puste. Umieść swoje interfejsy we wspólnej bibliotece DLL współdzielonej przez klientów, a każda wtyczka, która zostanie załadowana dynamicznie, musi implementować ten interfejs. Pozwoli to następnie zakodować klienta w interfejsie IDog i mieć intelisense + silne sprawdzanie typów w czasie kompilacji zamiast używania dynamicznego.
AaronLS
1
@ Tarek.Mh: To wymagałoby zależności czasu kompilacji od Class1. W tym momencie możesz po prostu użyć new Class1(). Pytający jawnie określił zależność środowiska wykonawczego. dynamicpozwala programowi w ogóle nie wymagać zależności od czasu kompilacji Class1.
Dark Falcon
39

W tej chwili tworzysz wystąpienie każdego typu zdefiniowanego w zestawie . Class1Aby wywołać metodę, wystarczy utworzyć jedną instancję :

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
źródło
19

Musisz utworzyć instancję typu, który uwidacznia Outputmetodę:

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
źródło
Dziękuję bardzo - właśnie tego szukam. Nie mogę uwierzyć, że nie jest to wyżej ocenione niż inne odpowiedzi, ponieważ pokazuje użycie dynamicznego słowa kluczowego.
skiphoppy
Ach, teraz widzę, że było to również w odpowiedzi DarkFalcon. Twój był krótszy i łatwiejszy do zobaczenia. :)
skiphoppy
0

Activator.CreateInstance() zwraca obiekt, który nie ma metody Output.

Wygląda na to, że pochodzisz z dynamicznych języków programowania? C # na pewno nie jest tym, a to, co próbujesz zrobić, będzie trudne.

Ponieważ ładujesz konkretną bibliotekę dll z określonej lokalizacji, może chcesz po prostu dodać ją jako odniesienie do aplikacji konsoli?

Jeśli absolutnie chcesz załadować zestaw przez Assembly.Load, będziesz musiał przejść przez refleksję, aby wezwać dowolnych członkówc

Coś takiego type.GetMethod("Output").Invoke(c, null);powinno to zrobić.

Fredrik
źródło
0
foreach (var f in Directory.GetFiles(".", "*.dll"))
            Assembly.LoadFrom(f);

To ładuje wszystkie biblioteki DLL obecne w folderze twojego pliku wykonywalnego.

W moim przypadku próbowałem Reflectionznaleźć wszystkie podklasy klasy, nawet w innych bibliotekach DLL. To zadziałało, ale nie jestem pewien, czy to najlepszy sposób.

EDYCJA: ustaliłem czas i wydaje się, że ładuję je tylko za pierwszym razem.

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);
}

Wyjście: 34 0 0 0

Tak więc, na wszelki wypadek, można by potencjalnie uruchomić ten kod przed jakimikolwiek przeszukiwaniem Reflection.

Samuel Cabrera
źródło
-1

To nie jest takie trudne.

Możesz sprawdzić dostępne funkcje załadowanego obiektu, a jeśli znajdziesz tę, której szukasz po nazwie, przeszukaj jej oczekiwane parametry, jeśli takie istnieją. Jeśli jest to wywołanie, które próbujesz znaleźć, wywołaj je przy użyciu metody Invoke obiektu MethodInfo.

Inną opcją jest po prostu zbudowanie zewnętrznych obiektów do interfejsu i przesłanie załadowanego obiektu do tego interfejsu. Jeśli się powiedzie, wywołaj funkcję natywnie.

To całkiem prosta sprawa.

ChrisH
źródło
Wow, nie jestem pewien, dlaczego głosy w dół. Mam aplikację produkcyjną, która robi dokładnie to przez ostatnie 12 lat. * wzruszenie ramionami * Każdy potrzebuje do tego kodu, wyślij mi wiadomość. Spakuję fragmenty mojego kodu produkcyjnego i wyślę go razem.
ChrisH,
10
Podejrzewam, że głosy negatywne miałyby związek z brakiem przykładów i skondensowanym tonem ... Wygląda jednak na to, że masz podstawy do pełnej odpowiedzi, więc nie bój się edytować bardziej szczegółowo :)
Shadow
1
Po prostu niegrzecznie jest powiedzieć „to całkiem prosta sprawa” i dlatego otrzymałeś głosy negatywne.
ABPerson
1
Nie byłam niegrzeczna ani protekcjonalna ... 6 lat temu. Ton wyraźnie nie pojawia się w tekście. To naprawdę miało być dość lekkie ... Naprawdę czuję, że przez te wszystkie lata miałem link do próbki kodu i nie mam pojęcia, gdzie to poszło (zakładając, że naprawdę tam było, tak jak pamiętam ). : \
ChrisH
Nie wiem, jak działa MethodInfo, ale wydaje się wartościowe. Powiedziałbym, że Twoja odpowiedź może być lepsza niż obecnie zaakceptowana, ale musiałaby zostać uzupełniona. Będziemy wdzięczni, jeśli kiedykolwiek się do tego podejdziesz. Jeśli tak, nie umieszczaj linków do przykładowego kodu. W przyszłości mogą się one zepsuć. Najlepiej jest dostarczyć próbkę samodzielnie, ewentualnie z linkiem do źródła lub dodatkowymi informacjami do dalszego czytania.
SpaghettiCook