2012-11-19 13 views
5

Ho letto tante versioni diverse di questa domanda come sono su Stack Overflow, così come ogni link blu sulla prima pagina di 3 diverse ricerche Google per le esercitazioni, così come MSDN (che è piuttosto superficiale oltre a eseguire gli assembly). Posso solo pensare ai miei sforzi per far funzionare Tao come un buon test, ma credetemi, ho provato con un semplice ritorno di stringa, un double, una funzione con parametri. Qualunque sia il mio problema, non è Tao.C# Caricamento/scaricamento dinamico di DLL Redux (utilizzando AppDomain, ovviamente)

Fondamentalmente voglio creare un testLibraryDomain.CreateInstance() della mia classe Draw nello spazio dei nomi 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); 

So per certo che:

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

cambia infatti il ​​colore della penna. Funziona quando gli do un punto di ingresso static void Main(string args[]) e chiamo testLibraryDomain.ExecuteAssembly(thePluginFilePath) Se un ExecuteAssembly diretto avrebbe funzionato o meno mi riguardava, poiché non ero sicuro che GL Calls sarebbe entrato nel contesto OpenGL di "livello superiore" di AppDomain. Mi consente persino di sovrascrivere il gruppo e cambiare il colore della penna una seconda volta. Sfortunatamente dargli un punto di accesso eseguibile significa che una console popup mi interrompe e poi se ne va. Funziona anche quando mi limito a dargli un riferimento nel Progetto e creo un normale GLPlugin.DrawingTool tool = new GLPlugin.DrawingControl(), o anche creando un someAssembly = Assembly.LoadFrom(thePluginFilePath) (che ovviamente e, purtroppo, blocca l'assieme, impedendo la sostituzione/ricompilazione).

Quando si utilizza uno dei vari metodi che ho provato, ottengo sempre "il nome dell'assembly specificato o il suo codice base non è valido." Lo prometto, è valido Qualcosa nel modo in cui sto cercando di caricarlo non lo è.

Una cosa che so mi manca è una corretta messa a punto per la testLibraryDomain.CreateInstance(string assemblyName , string typeName);

Per quanto posso dire, l'argomento AssemblyName non è il percorso del file per il file di assieme. È lo spazio dei nomi, o anche solo il nome dell'assembly, ovvero: GLPlugin? In tal caso, dove faccio riferimento al file attuale? Non ci sono alcuni AppDomain.LoadFrom (someFilename), anche se sarebbe pericoloso se ce ne fossero. Inoltre, che diamine è il Type e string typeName? Non voglio inserire "Object" qui, perché non sta creando un tipo diverso da un'istanza di un oggetto? Ho anche provato CreateInstanceAndUnwrap(... , ...) con la stessa mancanza di una comprensione fondamentale di AppDomain. Di solito riesco a cavarmela attraverso le esercitazioni e a far funzionare le cose, anche se spesso non capisco il "Perché?" ... non è così. Di solito è utile per me cercare sei diversi tutorial ... non di nuovo qui, ma perché ognuno prende un approccio fondamentalmente (o ciò che sembra essere così).

Quindi, per favore ELI5 ... Voglio caricare un'istanza di una classe da una DLL in un AppDomain separato, magari eseguire alcune funzioni e scaricarla. Alla fine creiamo un elenco di queste funzioni come Elenco, rimuovendo/aggiornando come necessario ... Mi piacerebbe essere in grado di passare argomenti anche a loro, ma quello sarà il passaggio 2. Secondo StackOverflow, devo imparare su serializable che rimanderò per un altro giorno. (Immagino che sarai in grado di capire dal mio esempio cosa sto cercando di fare.)

risposta

11

Ok, dobbiamo chiarire alcune cose. In primo luogo, se si vuole essere in grado di caricare e scaricare DLL a diverso dominio di applicazione senza bloccare il file iteslf, forse è possibile utilizzare l'approccio in questo modo:

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

In questo modo, non sarà il blocco del file e dovresti essere in grado di scaricare in seguito il dominio, ricompilare il file e caricarlo successivamente con la versione più recente. Ma non sono sicuro al 100% se questo non rompere la tua domanda.

E questo a causa della seconda cosa. Se si utilizza il metodo CreateInstanceAndUnwrap, in base a MSDN, è necessario caricare l'assembly in entrambi i domini, quello che sta chiamando e quello da cui si sta chiamando. E questo può finire in una situazione, quando hai due dll differenti caricate in AppDomain.

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.

non mi ricordo in questo momento, ma penso che il comportamento di creazione di oggetti in entrambi i domini app sarà diverso quando si chiamerà CreateInstanceAndUnwrap, ma io non ricordo i dettagli.

Per l'architettura dei plug-in, si consiglia di leggere questo post del blog. About how to handle Dynamic Plugins using the AppDomain Class to Load and Unload Code

EDIT

ho dimenticato come questo AppDomain funziona e potrebbe introdurre una certa confusione. Ho preparato un breve esempio su come l'architettura 'plugin' potrebbe funzionare. È abbastanza simile a quello descritto nel blog che ho pubblicato in precedenza, ed ecco il mio esempio che usa Shadow Copy. Se per qualche motivo non si desidera utilizzarlo, è possibile modificarlo in modo semplice per utilizzare AppDomain.Load(byte[] bytes)

Abbiamo 3 assiemi, il primo è un assembly di base plug-in, che funzionerà come proxy e verrà caricato in tutti gli AppDomain (nel nostro caso - nel dominio dell'app principale e nel dominio dell'app plugin).

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

Qui avremo un'altra assemblea con il nostro plugin fittizio, chiamato SamplePlugin.dll e conservato nella cartella "Plugins". Ha PluginBaseLib.dll riferimento

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

E per ultimo assemblaggio (consolle semplice applicazione), che farà riferimento solo PluginBaseLib.dll e

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

}

Ombra copia sembra essere molto conveniente.

+1

Per caricare assiemi senza blocco Penso che sia preferibile utilizzare Copia shadow, vedere http://msdn.microsoft.com/en-us/library/ms404279.aspx. – Maarten

+0

Sono curioso del tuo esempio, ancora probabilmente a causa di una mancanza di conoscenze fondamentali ... dove si "applica" il nuovo 'loadedAssembly' all'appDomain' apd' piuttosto che al primo livello predefinito? È solo implicito nell'ordine, come in hai creato un AppDomain, quindi tutto ciò che è sotto di esso è parte di esso fino a 'Unload()'? Inoltre, ottimo articolo, un po 'sopra la mia testa però. Ho letto una parte di esso che è stata strappata e inserita in un sito di annunci, ma è bello avere l'articolo completo. Cercherò di imparare davvero da esso attraverso il corso di oggi. Tipo di desiderio che avesse un file sorgente funzionale. – Adam

+0

Non sono sicuro che capisco la tua domanda, ma proverò a giocare con alcuni esempi e commenti della mia vecchia applicazione più tardi oggi. –