2013-05-03 108 views
7

我試圖將我的插件DLL加載到單獨的AppDomain中,但Load()方法因FileNotFoundException失敗。此外,似乎設置AppDomainSetup的PrivateBinPath屬性沒有效果,因爲在日誌中我看到「Initial PrivatePath = NULL」。所有插件都有強名。通常每個插件存儲在[應用程序startp路徑] \ postplugins \ [plugindir]。如果我把插件子目錄下[應用程序startp路徑]目錄,一切正常。我也嘗試手動更改AppBase屬性,但它不會更改。
下面是代碼:AppDomain.Load()失敗,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); 
     } 
    } 

而且這裏是日誌:

'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. 

回答

14

當你加載程序集到這樣的AppDomain中,它是用於在當前的AppDomain的PrivateBinPath找到裝配。

對於你的榜樣,當我增加了以下我的App.config它優良跑:

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

這雖然是不是對你非常有用的。

我所做的卻是創建一個包含IPostPlugin和IPluginsHost接口一個新的組件,並且也是一類稱爲裝載機,看上去像這樣:

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

我一直在應用程序根新的裝配,並且它不需要存在於插件目錄中(它可以但不會被使用,因爲應用程序根將被首先搜索)。

然後在主AppDomain中我這樣做,而不是:

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

所以我創建了衆所周知的裝載機類型的實例,然後得到那個從插件的AppDomain創建插件實例。這樣PrivateBinPaths就像你希望的那樣被使用。

另一件事,私人斌路徑可以是相對的,而不是添加d.FullName你可以添加pluginsDir + Path.DirectorySeparatorChar + d.Name保持最後的路徑列表的簡短。雖然這只是我個人的偏好!希望這可以幫助。

+0

詹姆斯,非常感謝你!最後我看到關於使用MarshalByRefObject的清晰解釋。不幸的是,我沒有足夠的聲望來標記答案是有用的(至少需要15):( – dedpichto 2013-05-04 20:06:20

+0

沒問題!很高興幫助:) – 2013-05-06 07:13:30

+0

當我嘗試這個,雖然我做LoadPlugins方法通用,我得到一個SerializationException。任何建議,爲什麼這可能是?我可以看到程序集已加載到沙盒應用程序域中。 – 2013-10-30 08:26:01

0

非常感謝DedPicto和James Thurley;我能夠實現一個完整的解決方案,我發佈在this post

我遇到了與Emil Badh相同的問題:如果您嘗試從「Loader」類返回一個表示當前AppDomain中未知的具體類的接口,則會顯示「Serialization Exception」。

這是因爲具體類型試圖反序列化。 解決方案:我能夠從「Loader」類返回一個「自定義代理」的具體類型,它的工作原理。請參閱參考後的詳細信息:

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

} 

此代理可以返回並使用,就好像它是真正的具體類型!