简体   繁体   English

AppDomain.Load()因FileNotFoundException而失败

[英]AppDomain.Load() fails with FileNotFoundException

I'm trying to load my plugin dll into separate AppDomain, but Load() method fails with FileNotFoundException. 我正在尝试将我的插件dll加载到单独的AppDomain中,但Load()方法因FileNotFoundException而失败。 Moreover, it seems like setting PrivateBinPath property of AppDomainSetup has no effect, because in log I see "Initial PrivatePath = NULL". 而且,似乎设置AppDomainSetup的PrivateBinPath属性没有任何效果,因为在日志中我看到“Initial PrivatePath = NULL”。 All plugin have strong name. 所有插件都有很强的名字。 Normally each plugin is stored in [Application startp path]\\postplugins\\[plugindir] . 通常,每个插件都存储在[Application startp path] \\ postplugins \\ [plugindir]中 If I put plugins subdirectories under [Application startp path] directory, everything works. 如果我将plugins子目录放在[Application startp path]目录下,一切正常。 I also have tried to change AppBase property manually but it does not change. 我也试图手动更改AppBase属性但它没有改变。
Here is the code: 这是代码:

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

And here is the 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.

when you load an assembly into the AppDomain in that way, it is the current AppDomain's PrivateBinPath that is used to find the assembly. 当您以这种方式将程序集加载到AppDomain时,它是用于查找程序集的当前 AppDomain的PrivateBinPath。

For your example, when I added the following to my App.config it ran fine: 对于您的示例,当我将以下内容添加到我的App.config时,运行正常:

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

This is not very useful to you though. 这对你来说并不是很有用。

What I did instead was to create a new assembly that contained the IPostPlugin and IPluginsHost interfaces, and also a class called Loader that looked like this: 我所做的是创建一个包含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;
    }
}

I keep that new assembly in the application root, and it doesn't need to exist in the plugin directories (it can but won't be used as the application root will be searched first). 我将新程序集保留在应用程序根目录中,并且它不需要存在于插件目录中(它可以但不会被使用,因为将首先搜索应用程序根目录)。

Then in the main AppDomain I did this instead: 然后在主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);

So I create an instance of the known Loader type, and then get that to create the plugin instances from within the plug-in AppDomain. 所以,我创建了众所周知的装载机类型的实例,然后得到那个从插件的AppDomain 创建的插件实例。 That way the PrivateBinPaths are used as you want them to be. 这样就可以按照您的意愿使用PrivateBinPaths。

One other thing, the private bin paths can be relative so rather than adding d.FullName you could add pluginsDir + Path.DirectorySeparatorChar + d.Name to keep the final path list short. 另一件事,私有bin路径可以是相对的,而不是添加d.FullName你可以添加pluginsDir + Path.DirectorySeparatorChar + d.Name以保持最终路径列表简短。 That's just my personal preference though! 这只是我个人的偏好! Hope this helps. 希望这可以帮助。

Thanks a lot to DedPicto and James Thurley ; 非常感谢DedPicto和James Thurley; I was able to implement a complete solution, I posted in this post . 我能够实现一个完整的解决方案,我发布在这篇文章中

I had the same problem as Emil Badh : if you try to return from "Loader" class an interface that represents a concrete class that is unknown in current AppDomain, you get a "Serialization Exception". 我和Emil Badh有同样的问题:如果你试图从“Loader”类返回一个代表当前AppDomain中未知的具体类的接口,你会得到一个“序列化异常”。

It is because the concrete type tries to be deserialized. 这是因为具体类型试图反序列化。 The solution: I was able to return from "Loader" class a concrete type of a "custom proxy" and it works. 解决方案:我能够从“Loader”类返回一个具体类型的“自定义代理”并且它可以工作。 See referenced post for details : 有关详情,请参阅参考帖子

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

}

This proxy could be returned and use as if it was the real concrete type ! 可以返回此代理并使用,就像它是真正的具体类型一样!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM