【问题标题】:AppDomain.Load() fails with FileNotFoundExceptionAppDomain.Load() 失败并出现 FileNotFoundException
【发布时间】:2013-05-03 20:28:20
【问题描述】:

我正在尝试将我的插件 dll 加载到单独的 AppDomain 中,但 Load() 方法因 FileNotFoundException 而失败。此外,似乎设置 AppDomainSetup 的 PrivateBinPath 属性无效,因为在日志中我看到“初始 PrivatePath = NULL”。所有插件都有强名称。通常每个插件都存储在[Application startp path]\postplugins\[plugindir]中。如果我将插件子目录放在 [Application startp path] 目录下,一切正常。我也尝试过手动更改 AppBase 属性,但它没有改变。
代码如下:

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.

【问题讨论】:

    标签: c# plugins filenotfoundexception appdomain appdomainsetup


    【解决方案1】:

    当您以这种方式将程序集加载到 AppDomain 中时,当前 AppDomain 的 PrivateBinPath 用于查找程序集。

    对于您的示例,当我将以下内容添加到我的 App.config 时,它运行良好:

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

    不过这对你不是很有用。

    我所做的是创建一个包含 IPostPlugin 和 IPluginsHost 接口的新程序集,以及一个名为 Loader 的类,如下所示:

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

    所以我创建了一个已知 Loader 类型的实例,然后从插件 AppDomain 中创建插件实例。这样,PrivateBinPath 就可以按照您的意愿使用。

    另一件事,私有 bin 路径可以是相对的,因此您可以添加 pluginsDir + Path.DirectorySeparatorChar + d.Name 以保持最终路径列表简短,而不是添加 d.FullName。不过这只是我个人的喜好!希望这会有所帮助。

    【讨论】:

    • 詹姆斯,非常感谢!最后我看到了关于使用 MarshalByRefObject 的清晰解释。不幸的是,我没有足够的声誉将答案标记为有用(至少需要 15 个):(
    • 没问题!很高兴它有帮助:)
    • 当我尝试这个时,虽然我将 LoadPlugins 方法设为通用,但我得到了 SerializationException。关于为什么会这样的任何建议?我可以看到程序集已加载到沙盒应用程序域中。
    • @JamesThurley IPostPlugin 的实现是否需要从 MarshalByRefObject 继承才能从 main 执行沙箱域中的代码,而不是将其加载到 main 中?
    • +1 谢谢!这可以在插件接口程序集中的 Loader 内实例化插件。但是,鉴于concrete types must be known by master application 为了反序列化,插件机制并不令人满意。我现在可以执行一个插件,但我不能让它的实例保持活动状态,因为我只能将调用结果发送回主应用程序,而不是插件本身实现的接口。
    【解决方案2】:

    非常感谢 DedPicto 和 James Thurley;我能够实现一个完整的解决方案,我在this post 发帖。

    我遇到了与 Emil Badh 相同的问题:如果您尝试从“Loader”类返回表示当前 AppDomain 中未知的具体类的接口,则会收到“序列化异常”。

    这是因为具体类型试图反序列化。 解决方案:我能够从“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));
        }
    
    }
    

    这个代理可以被返回并像真正的具体类型一样使用!

    【讨论】:

      猜你喜欢
      • 2014-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-15
      • 2021-05-21
      • 1970-01-01
      • 2018-02-24
      • 1970-01-01
      相关资源
      最近更新 更多