2012-11-19 8 views
5

Ich habe so viele verschiedene Versionen dieser Frage gelesen wie auf Stack Overflow, sowie jeden blauen Link auf der Startseite von 3 verschiedenen Google-Suchen für Tutorials, sowie die MSDN (die eher flach ist als das Ausführen von Assemblys). Ich kann nur an meine Bemühungen denken, Tao zu einem guten Testfall zu machen, aber glauben Sie mir, ich habe es mit einer einfachen String Return, einem Double, einer Funktion mit Parametern versucht. Was auch immer mein Problem ist, es ist nicht Tao.C# Dynamisches Laden/Entladen von DLLs Redux (mit AppDomain, natürlich)

Grundsätzlich möchte ich eine testLibraryDomain.CreateInstance() meiner Draw-Klasse im GLPlugin-Namespace erstellen.

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

Ich weiß für eine Tatsache, dass:

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

in der Tat die Stiftfarbe ändert. Es funktioniert, wenn ich ihm einen static void Main(string args[]) Einstiegspunkt gebe und ich rufe testLibraryDomain.ExecuteAssembly(thePluginFilePath) an ob oder nicht ein direkter ExecuteAssembly arbeiten würde, hatte mich betroffen, da ich nicht sicher war, dass die GL Anrufe in den OpenGL Kontext "der obersten Ebene" AppDomain bilden würden. Ich kann sogar die Baugruppe überschreiben und die Stiftfarbe ein zweites Mal ändern. Leider gibt es einen ausführbaren Einstiegspunkt bedeutet, dass eine Popup-Konsole mich unterbricht und dann weggeht. Es funktioniert auch, wenn ich einfach eine Referenz im Projekt gebe und eine reguläre GLPlugin.DrawingTool tool = new GLPlugin.DrawingControl() oder sogar eine someAssembly = Assembly.LoadFrom(thePluginFilePath) erzeuge (was natürlich die Baugruppe sperrt und den Austausch/Neukompilierung verhindert).

Bei der Verwendung einer der verschiedenen Methoden, die ich versucht habe, bekomme ich immer "der angegebene Assemblyname oder seine Codebasis ist ungültig." Ich verspreche, es ist gültig. Etwas in der Art, wie ich es zu laden versuche, ist es nicht.

Eines, was ich weiß, ich bin fehlt, ist eine richtige Setup für die testLibraryDomain.CreateInstance(string assemblyName , string typeName);

Soweit ich das beurteilen kann, das assembly Argument nicht der Dateipfad zu der Baugruppendatei ist. Ist es der Namespace oder auch nur der Assemblyname, zB: GLPlugin? Wenn ja, wo referenziere ich die eigentliche Datei? Es gibt keine someAppDomain.LoadFrom (someFilename), obwohl es dang praktisch wäre, wenn es da wäre. Außerdem, was zum Teufel ist der Typ und string typeName bei diesem? Ich möchte nicht "Object" hier einfügen, da ich keinen anderen Typ als eine Instanz eines Objekts erstellt? Ich habe auch CreateInstanceAndUnwrap(... , ...) mit dem gleichen Mangel an einem grundlegenden Verständnis von AppDomain versucht. Normalerweise kann ich Tutorials durcheinander bringen und Dinge zur Arbeit bringen, obwohl ich oft das "Warum" nicht verstehe ... nicht so hier. In der Regel ist es hilfreich für mich, sechs verschiedene Tutorials nachzuschlagen ... nicht so hier, sondern weil jeder einen fundamentalen (oder scheinbar so) Ansatz verfolgt.

Also bitte ELI5 ... Ich möchte eine Instanz einer Klasse aus einer DLL in einer separaten AppDomain laden, vielleicht ein paar Funktionen ausführen und entladen. Schließlich erstellen Sie eine Liste dieser Funktionen als Liste, entfernen/aktualisieren wie nötig ... Ich würde gerne auch Argumente an sie übergeben können, aber das wird Schritt 2. Laut StackOverflow muss ich über serializable lernen was ich für einen anderen Tag verschieben werde. (Ich stelle mir vor, dass Sie in der Lage sein werden, aus meinem Beispiel herauszufinden, was ich zu tun versuche.)

Antwort

11

Ok, wir müssen einige Dinge klären. Erstens, wenn Sie in der Lage sein wollen, dlls zu verschiedenen AppDomain zu laden und zu entladen, ohne die Datei iteslf zu Sperren, vielleicht können Sie Ansatz wie folgt verwenden:

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

Auf diese Weise werden Sie die Datei nicht werden Sperren und Sie sollten später in der Lage sein, die Domäne zu entladen, die Datei neu zu kompilieren und sie später mit einer neueren Version zu laden. Aber ich bin mir nicht 100% sicher, ob das Ihre Bewerbung nicht bricht.

Und das ist wegen der zweiten Sache. Wenn Sie die Methode CreateInstanceAndUnwrap gemäß MSDN verwenden, müssen Sie die Assembly in beide Anwendungsdomänen laden - die eine, die anruft, und die, von der Sie anrufen. Und dies kann in einer Situation enden, wenn Sie zwei verschiedene DLLs in AppDomains geladen haben.

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.

Ich erinnere mich nicht, gerade jetzt, aber ich denke, das Verhalten der Objekterstellung in beiden Anwendungsdomänen anders sein wird, wenn Sie CreateInstanceAndUnwrap nennen, aber ich erinnere mich nicht die Details.

Für Ihre Plugin-Architektur möchten Sie vielleicht diesen Blogbeitrag lesen. About how to handle Dynamic Plugins using the AppDomain Class to Load and Unload Code

EDIT

Ich habe vergessen, wie das funktioniert AppDomains und ich könnte einige Verwirrung vorstellen. Ich habe ein kurzes Beispiel vorbereitet, wie die Plugin-Architektur funktionieren könnte. Es ist ziemlich ähnlich zu dem, was ich im Blog zuvor beschrieben habe, und hier ist mein Beispiel, das Schattenkopieren verwendet. Wenn Sie aus irgendeinem Grund nicht verwenden möchten, kann es sehr einfach geändert werden AppDomain.Load(byte[] bytes)

Wir haben 3 Assemblys, die erste ist Basis-Plugin-Assembly, die als Proxy funktioniert, und wird geladen werden alle AppDomains (in unserem Fall - in der Haupt-App-Domain und in der Plugin-App-Domain).

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

Hier werden wir eine weitere Baugruppe mit unserem Dummy-Plugin haben, SamplePlugin.dll und gespeichert unter „Plugins“ Ordner. Es hat PluginBaseLib.dll

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

Und letzte Einheit (einfache Konsolenanwendung), die nur PluginBaseLib.dll Referenz wird und

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

}

Schatten Kopieren scheint sehr bequem zu sein.

+1

Um Baugruppen ohne Locking zu laden, denke ich, ist es besser Schattenkopie zu verwenden, siehe http://msdn.microsoft.com/en-us/library/ms404279.aspx. – Maarten

+0

Ich bin neugierig auf dein Beispiel, wieder wahrscheinlich aufgrund eines Mangels an fundamentalem Wissen ... wo "wendet" sich die neue "loadedAssembly" auf die "apd" AppDomain und nicht die Standard-Top-Ebene? Ist es nur in der Reihenfolge impliziert, wie in Sie eine AppDomain erstellt haben, dann ist alles darunter Teil bis "Unload()"? Auch ein toller Artikel, ein wenig über meinem Kopf. Ich habe einen Teil davon gelesen, der abgerissen wurde und auf einer Ad-Site platziert wurde, aber es ist schön, den ganzen Artikel zu haben. Ich werde versuchen, im Laufe des heutigen Tages wirklich davon zu lernen. Irgendwie wünschte es, es hätte eine funktionale Quelldatei. – Adam

+0

Ich bin mir nicht sicher, ob ich Ihre Frage verstehe, aber ich werde versuchen, später mit einigen Beispielen und Kommentaren aus meiner alten Anwendung herumzuspielen. –