2013-05-03 13 views
7

Sto provando a caricare la mia dll plugin in AppDomain separato, ma il metodo Load() non riesce con FileNotFoundException. Inoltre, sembra che l'impostazione della proprietà PrivateBinPath di AppDomainSetup non abbia alcun effetto, perché nel registro vedo "Initial PrivatePath = NULL". Tutti i plugin hanno un nome sicuro. Normalmente ogni plugin è memorizzato in [percorso di avvio dell'applicazione] \ postplugins \ [plugindir]. Se inserisco sottodirectory di plug-in sotto [Percorso avvio applicazione] directory, tutto funziona. Ho anche provato a modificare manualmente la proprietà AppBase ma non cambia.
ecco il codice:AppDomain.Load() non riesce con FileNotFoundException

public void LoadPostPlugins(IPluginsHost host, string pluginsDir) 
    { 
     _Host = host; 
     var privatePath = ""; 
     var paths = new List<string>(); 
     //build PrivateBinPath 
     var dirs = new DirectoryInfo(pluginsDir).GetDirectories(); 
     foreach (var d in dirs) 
     { 
      privatePath += d.FullName; 
      privatePath += ";"; 
     } 
     if (privatePath.Length > 1) privatePath = privatePath.Substring(0, privatePath.Length - 1); 
     //create new domain 
     var appDomainSetup = new AppDomainSetup { PrivateBinPath = privatePath }; 
     Evidence evidence = AppDomain.CurrentDomain.Evidence; 
     var sandbox = AppDomain.CreateDomain("sandbox_" + Guid.NewGuid(), evidence, appDomainSetup); 
     try 
     { 
      foreach (var d in dirs) 
      { 
       var files = d.GetFiles("*.dll"); 
       foreach (var f in files) 
       { 
        try 
        { 
         //try to load dll - here I get FileNotFoundException 
         var ass = sandbox.Load(AssemblyName.GetAssemblyName(f.FullName)); 
         var f1 = f; 
         paths.AddRange(from type in ass.GetTypes() 
             select type.GetInterface("PluginsCore.IPostPlugin") 
             into iface 
             where iface != null 
             select f1.FullName); 
        } 
        catch (FileNotFoundException ex) 
        { 
         Debug.WriteLine(ex); 
        } 
       } 
      } 
     } 
     finally 
     { 
      AppDomain.Unload(sandbox); 
     } 
     foreach (var plugin in from p in paths 
           select Assembly.LoadFrom(p) 
            into ass 
            select 
             ass.GetTypes().FirstOrDefault(t => t.GetInterface("PluginsCore.IPostPlugin") != null) 
             into type 
             where type != null 
             select (IPostPlugin)Activator.CreateInstance(type)) 
     { 
      plugin.Init(host); 
      plugin.GotPostsPartial += plugin_GotPostsPartial; 
      plugin.GotPostsFull += plugin_GotPostsFull; 
      plugin.PostPerformed += plugin_PostPerformed; 
      _PostPlugins.Add(plugin); 
     } 
    } 

E qui è il log:

'FBTest.vshost.exe' (Managed (v4.0.30319)): Loaded 'D:\VS2010Projects\PNotes - NET\pnfacebook\FBTest\bin\Debug\postplugins\pnfacebook\pnfacebook.dll', Symbols loaded. 
A first chance exception of type 'System.IO.FileNotFoundException' occurred in FBTest.exe 
System.IO.FileNotFoundException: Could not load file or assembly 'pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7' or one of its dependencies. The system cannot find the file specified. 
File name: 'pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7' 
    at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) 
    at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) 
    at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) 
    at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean forIntrospection) 
    at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) 
    at System.Reflection.Assembly.Load(String assemblyString) 
    at System.UnitySerializationHolder.GetRealObject(StreamingContext context) 

    at System.AppDomain.Load(AssemblyName assemblyRef) 
    at PNotes.NET.PNPlugins.LoadPostPlugins(IPluginsHost host, String pluginsDir) in D:\VS2010Projects\PNotes - NET\pnfacebook\FBTest\PNPlugins.cs:line 71 


=== Pre-bind state information === 
LOG: User = ANDREYHP\Andrey 
LOG: DisplayName = pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7 
(Fully-specified) 
LOG: Appbase = file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/ 
LOG: Initial PrivatePath = NULL 
Calling assembly : (Unknown). 
=== 
LOG: This bind starts in default load context. 
LOG: No application configuration file found. 
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config. 
LOG: Post-policy reference: pnfacebook, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9e2a2192d22aadc7 
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook.DLL. 
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook/pnfacebook.DLL. 
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook.EXE. 
LOG: Attempting download of new URL file:///D:/VS2010Projects/PNotes - NET/pnfacebook/FBTest/bin/Debug/pnfacebook/pnfacebook.EXE. 

risposta

14

quando si carica un assembly in AppDomain in questo modo, è l'attuale PrivateBinPath del dominio di applicazione che viene utilizzato per trovare l'assemblea.

Per esempio, quando ho aggiunto il seguente al mio App.config ha funzionato bene:

<runtime> 
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> 
    <probing privatePath="[PATH_TO_PLUGIN]"/> 
    </assemblyBinding> 
</runtime> 

Questo non è molto utile a voi però.

Quello che ho fatto, invece è stato quello di creare una nuova assemblea che conteneva le interfacce IPostPlugin e IPluginsHost, e anche una classe chiamata Loader che si presentava così:

public class Loader : MarshalByRefObject 
{ 
    public IPostPlugin[] LoadPlugins(string assemblyName) 
    { 
     var assemb = Assembly.Load(assemblyName); 

     var types = from type in assemb.GetTypes() 
       where typeof(IPostPlugin).IsAssignableFrom(type) 
       select type; 

     var instances = types.Select(
      v => (IPostPlugin)Activator.CreateInstance(v)).ToArray(); 

     return instances; 
    } 
} 

tengo che nuova assemblea nella radice di applicazione, e non ha bisogno di esistere nelle directory dei plugin (può ma non sarà usato poiché la radice dell'applicazione verrà cercata per prima).

Poi, nel dominio di applicazione principale per cui ho fatto questo, invece:

sandbox.Load(typeof(Loader).Assembly.FullName); 

Loader loader = (Loader)Activator.CreateInstance(
    sandbox, 
    typeof(Loader).Assembly.FullName, 
    typeof(Loader).FullName, 
    false, 
    BindingFlags.Public | BindingFlags.Instance, 
    null, 
    null, 
    null, 
    null).Unwrap(); 

var plugins = loader.LoadPlugins(AssemblyName.GetAssemblyName(f.FullName).FullName); 

foreach (var p in plugins) 
{ 
    p.Init(this); 
} 

_PostPlugins.AddRange(plugins); 

Così ho creare un'istanza di tipo Loader noto, e quindi ottenere che per creare le istanze di plugin da all'interno il dominio di applicazione plug-in . In questo modo i PrivateBinPath vengono utilizzati come vuoi che siano.

Un'altra cosa, i percorsi del cestino privato possono essere relativi, quindi piuttosto che aggiungere d.FullName è possibile aggiungere pluginsDir + Path.DirectorySeparatorChar + d.Name per mantenere breve l'elenco dei percorsi finali. Questa è solo la mia preferenza personale però! Spero che questo ti aiuti.

+0

James, grazie mille! Finalmente vedo la spiegazione chiara sull'uso di MarshalByRefObject. Purtroppo non ho abbastanza reputazione per contrassegnare una risposta come utile (è necessario almeno 15) :( – dedpichto

+0

Nessun problema! Felice ha aiutato :) –

+0

Quando provo questo, anche se ho reso il metodo LoadPlugins generico, ottengo un SerializationException. Qualche suggerimento sul perché possa essere? Posso vedere che l'assembly è stato caricato nel dominio dell'app sandbox. –

0

Grazie mille a DedPicto e James Thurley; Sono stato in grado di implementare una soluzione completa, ho postato in this post.

Ho avuto lo stesso problema di Emil Badh: se si tenta di restituire dalla classe "Loader" un'interfaccia che rappresenta una classe concreta che è sconosciuta nell'AppDomain corrente, si ottiene una "eccezione di serializzazione".

È perché il tipo concreto tenta di essere deserializzato. La soluzione: sono riuscito a restituire dalla classe "Loader" un tipo concreto di "proxy personalizzato" e funziona. Vedi post di riferimento per i dettagli:

// Our CUSTOM PROXY: the concrete type which will be known from main App 
[Serializable] 
public class ServerBaseProxy : MarshalByRefObject, IServerBase 
{ 
    private IServerBase _hostedServer; 

    /// <summary> 
    /// cstor with no parameters for deserialization 
    /// </summary> 
    public ServerBaseProxy() 
    { 

    } 

    /// <summary> 
    /// Internal constructor to use when you write "new ServerBaseProxy" 
    /// </summary> 
    /// <param name="name"></param> 
    public ServerBaseProxy(IServerBase hostedServer) 
    { 
     _hostedServer = hostedServer; 
    }  

    public string Execute(Query q) 
    { 
     return(_hostedServer.Execute(q)); 
    } 

} 

Questa delega potrebbe essere restituito e utilizzarlo come se fosse il tipo concreto reale!