从字节数组加载时找不到AppDomain程序集

请多多包涵,我花了30多个小时来尝试完成这项工作,但没有成功。


在程序开始时,我将程序集(dll)加载到字节数组中,然后将其删除。


_myBytes = File.ReadAllBytes(@"D:\Projects\AppDomainTest\plugin.dll");

稍后在程序中,我创建一个新的Appdomain,加载字节数组并枚举类型。


var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);


domain.Load(_myBytes);


foreach (var ass in domain.GetAssemblies())

{

    Console.WriteLine($"ass.FullName: {ass.FullName}");

    Console.WriteLine(string.Join(Environment.NewLine, ass.GetTypes().ToList()));

}

类型正确列出:


ass.FullName:插件,版本= 1.0.0.0,文化=中性,PublicKeyToken =空


...


插件测试


...


现在,我想在新的AppDomain中创建该类型的实例


domain.CreateInstance("plugin", "Plugins.Test");

导致此呼叫System.IO.FileNotFoundException,但我不知道为什么。


当我在下面的ProcessExplorer中.NET Assemblies -> Appdomain: plugintest查看时,我看到该程序集已正确加载到新的appdomain中。


我怀疑会发生异常,因为再次在磁盘上搜索了程序集。但是,为什么程序要再次加载它?


如何使用从字节数组加载的程序集在新的appdomain中创建实例?


红颜莎娜
浏览 193回答 3
3回答

慕标5832272

这里的主要问题是,您可以在执行主appdomain中的代码时实例化插件。您需要做的是创建一个代理类型,该类型在已加载的程序集中定义,但在新的appdomain中实例化。在两个应用程序域中都未加载类型的程序集的情况下,您无法跨应用程序域边界传递类型。 例如,如果您想像上面那样枚举类型并打印到控制台,则应从在新应用程序域中执行的代码执行,而不要从在当前应用程序域中执行的代码执行。所以,让我们创造我们的插件的代理,这样会存在于你的主要组件,并负责执行所有插件相关的代码:// Mark as MarshalByRefObject allows method calls to be proxied across app-domain boundariespublic class PluginRunner : MarshalByRefObject{    // make sure that we're loading the assembly into the correct app domain.    public void LoadAssembly(byte[] byteArr)    {        Assembly.Load(byteArr);    }    // be careful here, only types from currently loaded assemblies can be passed as parameters / return value.    // also, all parameters / return values from this object must be marked [Serializable]    public string CreateAndExecutePluginResult(string assemblyQualifiedTypeName)    {        var domain = AppDomain.CurrentDomain;        // we use this overload of GetType which allows us to pass in a custom AssemblyResolve function        // this allows us to get a Type reference without searching the disk for an assembly.        var pluginType = Type.GetType(            assemblyQualifiedTypeName,            (name) => domain.GetAssemblies().Where(a => a.FullName == name.FullName).FirstOrDefault(),            null,            true);        dynamic plugin = Activator.CreateInstance(pluginType);        // do whatever you want here with the instantiated plugin        string result = plugin.RunTest();        // remember, you can only return types which are already loaded in the primary app domain and can be serialized.        return result;    }}在这里,我将在上面的评论中重申一些要点:您必须继承自MarshalByRefObject,这意味着可以使用远程处理跨应用程序域边界代理对此对象的调用。将数据传递到代理类或从代理类传递数据时,必须对数据进行标记,[Serializable]并且还必须采用当前加载的程序集中的类型。如果您需要插件将某些特定对象返回给您,则说PluginResultModel您应该在由两个程序集/应用程序域加载的共享程序集中定义此类。必须将程序集合格的类型名传递给CreateAndExecutePluginResult其当前状态,但是可以通过自己迭代程序集和类型并删除对的调用来消除此要求Type.GetType。接下来,您需要创建域并运行代理:static void Main(string[] args){    var bytes = File.ReadAllBytes(@"...filepath...");    var domain = AppDomain.CreateDomain("plugintest", null, null, null, false);    var proxy = (PluginRunner)domain.CreateInstanceAndUnwrap(typeof(PluginRunner).Assembly.FullName, typeof(PluginRunner).FullName);    proxy.LoadAssembly(bytes);    proxy.CreateAndExecutePluginResult("TestPlugin.Class1, TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");}再说一遍,因为它非常重要,而且我很长一段时间都不了解:当您在此代理类上执行方法时,例如proxy.LoadAssembly实际上是被序列化为字符串并传递给新应用程序要执行的域。这不是正常的函数调用,您需要非常小心传入/传出这些方法的内容。

哆啦的时光机

此调用导致System.IO.FileNotFoundException,但我不知道为什么。我怀疑会发生异常,因为再次在磁盘上搜索了程序集。但是,为什么程序要再次加载它?这里的关键是了解加载程序上下文,有关MSDN的一篇很好的文章:将加载程序上下文视为保存程序集的应用程序域中的逻辑存储区。根据程序集的加载方式,它们属于三种加载程序上下文之一。加载上下文LoadFrom上下文两种情况都没有从装byte[]配件将装配件放置在这两个上下文中。至于这两个上下文,除非应用程序预订AssemblyResolve事件,否则不能绑定该上下文中的程序集。通常应避免这种情况。在下面的代码中,我们使用AssemblyResolve事件在Load上下文中加载程序集,从而使我们能够绑定到该程序集。如何使用从字节数组加载的程序集在新的appdomain中创建实例?请注意,这仅仅是概念的证明,它是探索装载程序上下文的基本要素。建议的方法是使用@caesay描述的代理,并在本文中由Suzanne Cook进一步评论。这是一个不保留对实例的引用的实现(类似于即发即弃)。首先,我们的插件:Test.csnamespace Plugins{    public class Test    {        public Test()        {            Console.WriteLine($"Hello from {AppDomain.CurrentDomain.FriendlyName}.");        }    }}接下来,在一个新ConsoleApp的插件加载器中:PluginLoader.cs[Serializable]class PluginLoader{    private readonly byte[] _myBytes;    private readonly AppDomain _newDomain;    public PluginLoader(byte[] rawAssembly)    {        _myBytes = rawAssembly;        _newDomain = AppDomain.CreateDomain("New Domain");        _newDomain.AssemblyResolve += new ResolveEventHandler(MyResolver);    }    public void Test()    {        _newDomain.CreateInstance("plugin", "Plugins.Test");    }    private Assembly MyResolver(object sender, ResolveEventArgs args)    {        AppDomain domain = (AppDomain)sender;        Assembly asm = domain.Load(_myBytes);        return asm;    }}Program.csclass Program{    static void Main(string[] args)    {        byte[] rawAssembly = File.ReadAllBytes(@"D:\Projects\AppDomainTest\plugin.dll");        PluginLoader plugin = new PluginLoader(rawAssembly);        // Output:         // Hello from New Domain        plugin.Test();        // Output:         // Assembly: mscorlib        // Assembly: ConsoleApp        foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())        {            Console.WriteLine($"Assembly: {asm.GetName().Name}");        }        Console.ReadKey();    }}CreateInstance("plugin", "Plugins.Test")尽管不了解插件程序集,但显示的输出已成功从默认应用程序域中成功调用。
打开App,查看更多内容
随时随地看视频慕课网APP