为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
红颜莎娜 2021-05-18 15:15:20
请多多包涵,我花了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中创建实例?
查看完整描述

3 回答

?
慕标5832272

TA贡献1966条经验 获得超4个赞

这里的主要问题是,您可以在执行主appdomain中的代码时实例化插件。


您需要做的是创建一个代理类型,该类型在已加载的程序集中定义,但在新的appdomain中实例化。在两个应用程序域中都未加载类型的程序集的情况下,您无法跨应用程序域边界传递类型。 例如,如果您想像上面那样枚举类型并打印到控制台,则应从在新应用程序域中执行的代码执行,而不要从在当前应用程序域中执行的代码执行。


所以,让我们创造我们的插件的代理,这样会存在于你的主要组件,并负责执行所有插件相关的代码:


// Mark as MarshalByRefObject allows method calls to be proxied across app-domain boundaries

public 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实际上是被序列化为字符串并传递给新应用程序要执行的域。这不是正常的函数调用,您需要非常小心传入/传出这些方法的内容。


查看完整回答
反对 回复 2021-05-23
?
哆啦的时光机

TA贡献1779条经验 获得超6个赞

此调用导致System.IO.FileNotFoundException,但我不知道为什么。我怀疑会发生异常,因为再次在磁盘上搜索了程序集。但是,为什么程序要再次加载它?


这里的关键是了解加载程序上下文,有关MSDN的一篇很好的文章:


将加载程序上下文视为保存程序集的应用程序域中的逻辑存储区。根据程序集的加载方式,它们属于三种加载程序上下文之一。


加载上下文

LoadFrom上下文

两种情况都没有

从装byte[]配件将装配件放置在这两个上下文中。


至于这两个上下文,除非应用程序预订AssemblyResolve事件,否则不能绑定该上下文中的程序集。通常应避免这种情况。


在下面的代码中,我们使用AssemblyResolve事件在Load上下文中加载程序集,从而使我们能够绑定到该程序集。


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


请注意,这仅仅是概念的证明,它是探索装载程序上下文的基本要素。建议的方法是使用@caesay描述的代理,并在本文中由Suzanne Cook进一步评论。


这是一个不保留对实例的引用的实现(类似于即发即弃)。


首先,我们的插件:


Test.cs


namespace 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.cs


class 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")尽管不了解插件程序集,但显示的输出已成功从默认应用程序域中成功调用。


查看完整回答
反对 回复 2021-05-23
  • 3 回答
  • 0 关注
  • 192 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信