从字节数组加载时找不到 AppDomain 程序集 [英] AppDomain Assembly not found when loaded from byte array

查看:24
本文介绍了从字节数组加载时找不到 AppDomain 程序集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请耐心等待,我花了 30 多个小时试图完成这项工作 - 但没有成功.

Please bear with me, I spent 30+ hours trying to get this work - but without success.

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

At the start of my program I load an Assembly (dll) in bytearray and delete it afterwards.

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

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

Later on in the program I create a new Appdomain, load the byte array and enumerate the types.

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,Culture=neutral,PublicKeyToken=null

ass.FullName: plugin, Version=1.0.0.0, Culture=neutral,PublicKeyToken=null

...

插件测试

...

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

Now I want to create an instance of that type in the new AppDomain

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

这个调用导致 System.IO.FileNotFoundException,我不知道为什么.

This call results in System.IO.FileNotFoundException and I don't know why.

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

When I look in ProcessExplorer under .NET Assemblies -> Appdomain: plugintest I see that the assembly is loaded correctly in the new appdomain.

我怀疑发生异常是因为在磁盘上再次搜索程序集.但是为什么程序要重新加载呢?

I suspect the exception to occur because the assembly is searched again on disk. But why does the program want to load it again?

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

How can I create an instance in a new appdomain with an assembly loaded from byte array?

推荐答案

这里的主要问题是认为您可以在主应用程序域中执行代码时实例化插件.

The main problem here is thinking that you can instantiate a plugin while executing code in your primary appdomain.

您需要做的是创建一个代理类型,它在已加载的程序集中定义,但在应用程序域中实例化.您不能跨应用域边界传递类型,除非类型的程序集在两个应用域中加载.例如,如果您想像上面那样枚举类型并打印到控制台,您应该从在新应用程序域中执行的代码中执行此操作,而不是从当前应用程序域中执行的代码中执行此操作强>.

What you need to do instead, is create a proxy type which is defined in an already loaded assembly, but instantiated in the new appdomain. You can not pass types across app domain boundaries without the type's assembly being loaded in both appdomains. For instance, if you want to enumerate the types and print to console as you do above, you should do so from code which is executing in the new app domain, not from code that is executing in the current app domain.

那么,让我们创建我们的插件代理,它将存在于您的主要程序集中,并将负责执行所有与插件相关的代码:

So, lets create our plugin proxy, this will exist in your primary assembly and will be responsible for executing all plugin related code:

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

以上评论中的几个关键点我将在这里重申:

A few key points in the comments above I will reiterate here:

  • 您必须从 MarshalByRefObject 继承,这意味着可以使用远程处理跨应用域边界代理对此对象的调用.
  • 向代理类传递数据或从代理类传递数据时,数据必须标记为[Serializable],并且还必须是当前加载的程序集中的类型.如果您需要您的插件向您返回某个特定对象,例如 PluginResultModel,那么您应该在由程序集/应用程序域加载的共享程序集中定义此类.
  • 必须将程序集限定的类型名称传递给处于当前状态的 CreateAndExecutePluginResult,但可以通过自己迭代程序集和类型并删除对 Type 的调用来删除此要求.获取类型.
  • You must inherit from MarshalByRefObject, this means that the calls to this object can be proxied across app-domain boundaries using remoting.
  • When passing data to or from the proxy class, the data must be marked [Serializable] and also must be in a type which is in the currently loaded assembly. If you need your plugin to return some specific object to you, say PluginResultModel then you should define this class in a shared assembly which is loaded by both assemblies/appdomains.
  • Must pass an assembly qualified type name to CreateAndExecutePluginResult in its current state, but it would be possible to remove this requirement by iterating the assemblies and types yourself and removing the call to Type.GetType.

接下来,您需要创建域并运行代理:

Next, you need to create the domain and run the proxy:

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这是实际上被序列化为一个字符串并被传递到新的应用程序域来执行.这不是一个普通的函数调用,您需要非常小心向/从这些方法传递的内容.

Going to say this again because it's super important and I didn't understand this for a long time: when you're executing a method on this proxy class, such as proxy.LoadAssembly this is actually being serialized into a string and being passed to the new app domain to be executed. This is not a normal function call and you need to be very careful what you pass to/from these methods.

这篇关于从字节数组加载时找不到 AppDomain 程序集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆