2012-11-19 21 views
5

Przeczytałem tyle różnych wersji tego pytania, co w przepełnieniu stosu, a także każdy niebieski link na pierwszej stronie 3 różnych wyszukiwań w Google do samouczków, a także do MSDN (który jest trochę płytko poza wykonywaniem złożeń). Mogę tylko myśleć o moich wysiłkach, aby tao działała jako dobry przypadek testowy, ale wierz mi, próbowałem z prostym zwracaniem ciągu, podwójnym, funkcją z parametrami. Niezależnie od mojego problemu, nie jest to Tao.C# Dynamiczne ładowanie/rozładowywanie bibliotek DLL Redux (przy użyciu AppDomain, oczywiście)

Zasadniczo chcę utworzyć testLibraryDomain.CreateInstance() z mojej klasy Draw w przestrzeni nazw GLPlugin.

 if(usePlugin) 
     { 
       AppDomain testLibraryDomain = AppDomain.CreateDomain("TestGLDomain2"); 

       //What the heck goes here so that I can simply call 
       //the default constructor and maybe a function or two? 

       AppDomain.Unload(testLibraryDomain); 
     } 
     Gl.glBegin(Gl.GL_TRIANGLES); 

wiem na pewno, że:

namespace GLPlugin 
{ 
    public class DrawingControl : MarshalByRefObject 
    { 
     public DrawingControl() 
     { 
      Gl.glColor3f(1.0f , 0.0f , 0.0f); 

      //this is a test to make sure it passes 
      //to the GL Rendering context... success 
     } 
    } 
} 

rzeczywiście zmienia kolor pisaka. Działa, gdy podaję go jako punkt wejścia i dzwonię pod numer testLibraryDomain.ExecuteAssembly(thePluginFilePath) Niezależnie od tego, czy zadziałałby bezpośredni moduł ExecuteAssembly, ponieważ nie byłem pewien, że wywołania GL staną się częścią kontekstu OpenGL AppDomain "najwyższego poziomu". Pozwala nawet na zastąpienie zespołu i zmianę koloru pisaka po raz drugi. Niestety nadanie mu wykonywalnego punktu wejścia oznacza, że ​​wyskakująca konsola przerywa mi, a następnie znika. Działa to również, gdy po prostu nadaję mu odnośnik w Projekcie i tworzę regularny GLPlugin.DrawingTool tool = new GLPlugin.DrawingControl(), lub nawet tworzę someAssembly = Assembly.LoadFrom(thePluginFilePath) (który oczywiście i niestety, blokuje złożenie, uniemożliwiając wymianę/rekompilację).

Podczas korzystania z różnych metod, które wypróbowałem, zawsze otrzymuję "podaną nazwę zestawu lub jego podstawa kodu jest nieprawidłowa." Obiecuję, to jest ważne. Coś w sposobie, w jaki próbuję to załadować, nie jest.

Jedno wiem, że brak jest poprawna konfiguracja dla testLibraryDomain.CreateInstance(string assemblyName , string typeName);

O ile mogę powiedzieć, argument AssemblyName nie jest filepath do pliku zespołu. Czy jest to przestrzeń nazw, czy nawet nazwa zestawu, tj .: GLPlugin? Jeśli tak, to gdzie mam się odwołać do rzeczywistego pliku? Nie ma nazwy someAppDomain.LoadFrom (someFilename), ale byłaby przydatna, gdyby była. Dodatkowo, co do cholery jest typ, i ciąg nazwa-typu w tym? Nie chcę wstawiać tutaj "Object", ponieważ nie tworzy typ inny niż instancja obiektu? Próbowałem też CreateInstanceAndUnwrap(... , ...) z tym samym brakiem podstawowego zrozumienia AppDomain. Zwykle mogę poradzić sobie z samouczkami i sprawić, że wszystko działa, mimo że często nie rozumiem "Dlaczego?" ... nie tutaj. Zwykle pomocne jest dla mnie wyszukanie sześciu różnych samouczków ... już nie tutaj, ale dlatego, że każdy bierze fundamentalnie (lub wydaje się, że tak jest) podejście.

Więc proszę ELI5 ... Chcę załadować wystąpienie klasy z biblioteki dll w oddzielnej AppDomain, może uruchomić kilka funkcji i zwolnić go. Ostatecznie utwórz listę tych funkcji jako List, usuwanie/aktualizowanie w razie potrzeby ... Chciałbym móc przekazywać im również argumenty, ale to będzie krok 2. Według StackOverflow muszę się nauczyć o serializable które odłożę na kolejny dzień. (Wyobrażam sobie, że będziesz w stanie wymyślić z mojego przykładu, co próbuję zrobić.)

Odpowiedz

11

Ok, musimy wyjaśnić kilka rzeczy. Po pierwsze, jeśli chcesz być w stanie załadować i rozładować DLL do różnych AppDomain bez blokowania pliku iteslf, może można użyć podejścia tak:

AppDomain apd = AppDomain.CreateDomain("newdomain"); 
using(var fs = new FileStream("myDll.dll", FileMode.Open)) 
{ 
    var bytes = new byte[fs.Length]; 
    fs.Read(bytes, 0, bytes .Length); 
    Assembly loadedAssembly = apd.Load(bytes); 
} 

ten sposób, że nie będzie blokowania pliku i powinieneś móc później wyładować domenę, ponownie skompilować plik i załadować go później w nowszej wersji. Ale nie jestem w 100% pewien, czy to nie złamie twojej aplikacji.

A to z powodu drugiej rzeczy. Jeśli będziesz używać metody CreateInstanceAndUnwrap, zgodnie z MSDN, musisz załadować zespół w obu aplikacjach - tym, który dzwoni, i tym, z którego dzwonisz. A to może skończyć się sytuacją, gdy masz dwa różne biblioteki dll załadowane do AppDomains.

The assembly that contains unwrapped class must be loaded into both application domains, but it can load other assemblies that exist only in the new application domain.

nie pamiętam teraz, ale myślę, że zachowanie tworzenia obiektów w obu domenach aplikacji będzie inaczej, kiedy będziesz dzwonić CreateInstanceAndUnwrap, ale nie pamiętam szczegółów.

Dla Twojej architektury wtyczek możesz przeczytać ten post na blogu. About how to handle Dynamic Plugins using the AppDomain Class to Load and Unload Code

EDIT

zapomniałem jak to AppDomains prace i może wprowadzić pewne zamieszanie. Przygotowałem krótki przykład, jak może działać architektura "wtyczki". Jest bardzo podobny do tego, co opisano na blogu, który wcześniej umieściłem, a oto moja próbka, która korzysta z funkcji kopiowania w tle. Jeśli z jakichś powodów nie chcesz go używać, możesz go całkiem łatwo zmienić na: AppDomain.Load(byte[] bytes)

Mamy 3 złożenia, pierwszy to podstawowy zestaw wtyczek, który będzie działać jako proxy, i zostanie załadowany wszystkie AppDomains (w naszym przypadku - w głównej domenie aplikacji oraz w domenie aplikacji wtyczki).

namespace PluginBaseLib 
{ 
    //Base class for plugins. It has to be delivered from MarshalByRefObject, 
    //cause we will want to get it's proxy in our main domain. 
    public abstract class MyPluginBase : MarshalByRefObject 
    { 
     protected MyPluginBase() 
     { } 

     public abstract void DrawingControl(); 
    } 

    //Helper class which instance will exist in destination AppDomain, and which 
    //TransparentProxy object will be used in home AppDomain 
    public class MyPluginFactory : MarshalByRefObject 
    { 
     //This method will be executed in destination AppDomain and proxy object 
     //will be returned to home AppDomain. 
     public MyPluginBase CreatePlugin(string assembly, string typeName) 
     { 
      Console.WriteLine("Current domain: {0}", AppDomain.CurrentDomain.FriendlyName); 
      return (MyPluginBase) Activator.CreateInstance(assembly, typeName).Unwrap(); 
     } 
    } 

    //Small helper class which will show how to call method in another AppDomain. 
    //But it can be easly deleted. 
    public class MyPluginsHelper 
    { 
     public static void LoadMyPlugins() 
     { 
      Console.WriteLine("----------------------"); 
      Console.WriteLine("Loading plugins in following app domain: {0}", AppDomain.CurrentDomain.FriendlyName); 
      AppDomain.CurrentDomain.Load("SamplePlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); 
      Console.WriteLine("----------------------"); 
     } 
    } 
} 

Tutaj mamy kolejny zespół z naszego manekina plugin o nazwie SamplePlugin.dll i przechowywane w folderze „Wtyczki”. Ma PluginBaseLib.dll odwołuje

namespace SamplePlugin 
{ 
    public class MySamplePlugin : MyPluginBase 
    { 
     public MySamplePlugin() 
     { } 

     public override void DrawingControl() 
     { 
      var color = Console.ForegroundColor; 
      Console.ForegroundColor = ConsoleColor.Green; 
      Console.WriteLine("----------------------"); 
      Console.WriteLine("This was called from app domian {0}", AppDomain.CurrentDomain.FriendlyName); 
      Console.WriteLine("I have following assamblies loaded:"); 
      foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 
      { 
       Console.WriteLine("\t{0}", assembly.GetName().Name); 
      } 
      Console.WriteLine("----------------------"); 
      Console.ForegroundColor = color; 
     } 
    } 
} 

i ostatni zespół (prosta aplikacja konsola), który będzie wyłącznie w celach informacyjnych PluginBaseLib.dll i

namespace ConsoleApplication1 
{ 
    //'Default implementation' which doesn't use any plugins. In this sample 
    //it just lists the assemblies loaded in AppDomain and AppDomain name itself. 
    public static void DrawControlsDefault() 
    { 
     Console.WriteLine("----------------------"); 
     Console.WriteLine("No custom plugin, default app domain {0}", AppDomain.CurrentDomain.FriendlyName); 
     Console.WriteLine("I have following assamblies loaded:"); 
     foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 
     { 
      Console.WriteLine("\t{0}", assembly.GetName().Name); 
     } 
     Console.WriteLine("----------------------"); 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      //Showing that we don't have any additional plugins loaded in app domain. 
      DrawControlsDefault(); 

      var appDir = AppDomain.CurrentDomain.BaseDirectory; 
      //We have to create AppDomain setup for shadow copying 
      var appDomainSetup = new AppDomainSetup 
           { 
            ApplicationName = "", //with MSDN: If the ApplicationName property is not set, the CachePath property is ignored and the download cache is used. No exception is thrown. 
            ShadowCopyFiles = "true",//Enabling ShadowCopy - yes, it's string value 
            ApplicationBase = Path.Combine(appDir,"Plugins"),//Base path for new app domain - our plugins folder 
            CachePath = "VSSCache"//Path, where we want to have our copied dlls store. 
           }; 
     var apd = AppDomain.CreateDomain("My new app domain", null, appDomainSetup); 

     //Loading dlls in new appdomain - when using shadow copying it can be skipped, 
     //in CreatePlugin method all required assemblies will be loaded internaly, 
     //Im using this just to show how method can be called in another app domain. 
     //but it has it limits - method cannot return any values and take any parameters. 

     //apd.DoCallBack(new CrossAppDomainDelegate(MyPluginsHelper.LoadMyPlugins)); 

     //We are creating our plugin proxy/factory which will exist in another app domain 
     //and will create for us objects and return their remote 'copies'. 
     var proxy = (MyPluginFactory) apd.CreateInstance("PluginBaseLib", "PluginBaseLib.MyPluginFactory").Unwrap(); 

     //if we would use here method (MyPluginBase) apd.CreateInstance("SamplePlugin", "SamplePlugin.MySamplePlugin").Unwrap(); 
     //we would have to load "SamplePlugin.dll" into our app domain. We may not want that, to not waste memory for example 
     //with loading endless number of types. 
     var instance = proxy.CreatePlugin("SamplePlugin", "SamplePlugin.MySamplePlugin"); 
     instance.DrawingControl(); 

     Console.WriteLine("Now we can recompile our SamplePlugin dll, replace it in Plugin directory and load in another AppDomain. Click Enter when you ready"); 
     Console.ReadKey(); 

     var apd2 = AppDomain.CreateDomain("My second domain", null, appDomainSetup); 
     var proxy2 = (MyPluginFactory)apd2.CreateInstance("PluginBaseLib", "PluginBaseLib.MyPluginFactory").Unwrap(); 
     var instance2 = proxy2.CreatePlugin("SamplePlugin", "SamplePlugin.MySamplePlugin"); 
     instance2.DrawingControl(); 

     //Now we want to prove, that this additional assembly was not loaded to prmiary app domain. 
     DrawControlsDefault(); 

     //And that we still have the old assembly loaded in previous AppDomain. 
     instance.DrawingControl(); 

     //App domain is unloaded so, we will get exception if we try to call any of this object method. 
     AppDomain.Unload(apd); 
     try 
     { 
      instance.DrawingControl(); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex); 
     } 

     Console.ReadKey(); 
    } 
} 

}

Cień kopiowanie wydaje się być bardzo wygodne.

+1

Aby załadować złożenia bez blokowania Myślę, że lepiej jest użyć funkcji kopiowania w tle, zobacz http://msdn.microsoft.com/en-us/library/ms404279.aspx. – Maarten

+0

Jestem ciekawy twojego przykładu, znowu prawdopodobnie z powodu braku podstawowej wiedzy ... gdzie to "stosuje" nowy 'loadedAssembly' do' apd' AppDomain zamiast domyślnego najwyższego poziomu? Czy jest to tylko domniemana kolejność, tak jak w przypadku tworzenia AppDomain, a następnie wszystko pod nią jest częścią do czasu 'Unload()'? Poza tym świetny artykuł, trochę ponad moją głową. Przeczytałem część tego, która została oszukana i umieszczona na stronie z reklamami, ale miło jest mieć cały artykuł. Spróbuję naprawdę się z tego uczyć przez dzisiejszy dzień. Żałował, że nie ma funkcjonalnego pliku źródłowego. – Adam

+0

Nie jestem pewien, czy rozumiem twoje pytanie, ale postaram się zagrać z pewnym przykładem i komentarzami z mojej starej aplikacji. –