本文介绍了C# 将 DLL 字节数组加载到不同的 AppDomain 中会引发 System.IO.FileNotFoundException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将已复制到字节数组中的 DLL 文件加载到新的 AppDomain 中.

I am trying to load a DLL file that has been copied into a byte array into a new AppDomain.

DLL 确实包含对 Windows.Forms 和其他 dll 等内容的引用.那些是加载失败的吗?如果是这样,您如何为该特定域预加载它们?

The DLL does contain references to things like Windows.Forms and other dlls. Are those the ones failing to load? If so how do you pre-load them for that specific domain?

AppDomainSetup Setup = new AppDomainSetup();
Setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
Setup.ApplicationName = "Plugin_" + DLLName + "" + PluginManager.PluginList.Count;
AppDomain Domain = AppDomain.CreateDomain("Domain_" + DLLName + "" + PluginManager.PluginList.Count, null, Setup);
Assembly Assembly = Domain.Load(buffer);

然而变化

Assembly Assembly = Domain.Load(buffer);

Assembly = AppDomain.CurrentDomain.Load(buffer);

让它发挥作用.

我需要将它放在一个单独的域中,因为我计划卸载此 AppDomain 以卸载 DLL 本身.

I need it to be in a separate domain because I plan on unloading this AppDomain to unload the DLL itself.

我试着像所有人都建议的那样处理AssemblyResolve"事件,但它没有任何作用.

I tried playing around with the "AssemblyResolve" event like everyone out there is suggesting but it doesn't do anything.

我需要它来自字节数组的另一个原因是因为我希望能够在运行时切换 DLL 文件并将其重新加载到内存中.

Also the reason I need this to be from a byte array is because I want to be able to switch the DLL file at run-time and re-load it into memory.

DLL 文件与 .exe 文件位于不同的文件夹中.它在同一目录中,只有一个文件夹.

The DLL files are in a separate folder from the .exe file. It's in the same directory just one folder inside.

有趣的发现:

如果我将 DLL 文件添加到 .exe 的文件位置,它将加载这些文件并锁定它们并成功加载到新域中.当我向它提供一个字节数组而不是文件位置时,为什么它会以这种方式工作?我真的必须使用字节数组并写入临时文件吗?我可以这样做并在完成后删除它们,但这似乎是在浪费时间,没有理由不能从记忆中完成所有操作.

If I add the DLL files to the file location of the .exe it will load those and lock onto them and load successfully into the new domain. Why does it work this way when I feed it a byte array and not the file location? Do I actually have to take the byte array and write to a temporary file? I can do that and delete them when I'm done with them but it seems like a waste of time, there's no reason it can't do it all from memory.

推荐答案

解决方案:

无论我在哪里查看,AppDomain 都没有很好的文档记录和解释.就好像人们试图隐藏它并对公众保密.显然,AppDomains 不会像变量和其他对象引用那样在彼此之间共享数据.您需要在它们之间设置 SetData/GetData 和 DoCallBack.含糊其辞地提到了这一点,但没有人给出真正的解决方案.

AppDomains are poorly documented and poorly explained no matter where I look. It's like people are trying to hide it and keep it a secret from the public mass. Apparently AppDomains do not share data between each other like variables and other object references. You need to SetData/GetData and DoCallBack between them. This was vaguely mentioned but no real solution was given by anyone.

所以我做了这个简单的插件加载器,使用LoadFrom"而不将它加载到字节数组中并且文件没有被锁定,它将它读入一个新的 AppDomain 到内存中并立即解锁文件,但这在任何地方都没有提到并且已经是奇怪的行为,因为在主 AppDomain 中,它像癌症一样锁定文件.

So I did this simple plugin loader, using "LoadFrom" without loading it into a byte array and the file does not get locked, it reads it into a new AppDomain into memory and unlocks the file immediately but this is not mentioned anywhere and is weird behavior already because in the main AppDomain it locks onto the file like a cancer.

[Serializable] //This is important
public class TPlugin
{
    public bool InitializeImmediately { get; set; }
    public AppDomainSetup AppSetup { get; set; }
    public Assembly Assembly { get; set; }
    public AppDomain AppDomain { get; set; }
    public string FilePath { get; set; }
    public object ClassInstance { get; set; }
    public Type ClassType { get; set; }

    public TPlugin(string path, bool Initialize = false)
    {
        FilePath = path;
        InitializeImmediately = Initialize;

        AppSetup = new AppDomainSetup();
        AppSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
        AppDomain = AppDomain.CreateDomain(FilePath, null, AppSetup);
        AppDomain.SetData("Plugin", this);
        AppDomain.DoCallBack(new CrossAppDomainDelegate(() =>
        {
            //We are now inside the new AppDomain, every other variable is now invalid since this AppDomain cannot see into the main one
            TPlugin plugin = AppDomain.CurrentDomain.GetData("Plugin") as TPlugin;
            if (plugin != null)
            {
                plugin.Assembly = Assembly.LoadFrom(plugin.FilePath);
                if(InitializeImmediately) //You cannot use the "Initialize" parameter here, it goes out of scope for this AppDomain
                {
                    plugin.ClassType = plugin.Assembly.GetExportedTypes()[0];
                    if (plugin.ClassType != null && plugin.ClassType.IsClass)
                    {
                        plugin.ClassInstance = Activator.CreateInstance(plugin.ClassType);
                        MethodInfo info = plugin.ClassType.GetMethod("Initializer");
                        info.Invoke(plugin.ClassInstance, null);
                    }
                }
            }
        }));
    }

    public object Execute(string FunctionName, params object[] args)
    {
        AppDomain.SetData("FunctionName", FunctionName);
        AppDomain.SetData("FunctionArguments", args);
        AppDomain.DoCallBack(CallBack);
        return AppDomain.GetData("FunctionReturn");
    }

    public void CallBack()
    {
        TPlugin plugin = AppDomain.CurrentDomain.GetData("Plugin") as TPlugin;

        if (plugin != null)
        {
            MethodInfo info = plugin.ClassType.GetMethod(AppDomain.CurrentDomain.GetData("FunctionName") as string);
            info.Invoke(plugin.ClassInstance, AppDomain.CurrentDomain.GetData("FunctionArgs") as object[]);
        }

        //This is how to return back since DoCallBack does not support returns.
        AppDomain.CurrentDomain.SetData("FunctionReturn", null);
    }
}

这是我的 DLL 模块:

And this is my DLL module:

public class GUIModule
{
    public bool Initializer()
    {
        Console.WriteLine("Initialized!");
        return true;
    }

    public bool Deinitializer()
    {
        Console.WriteLine("Deinitialized");
        return true;
    }
}

现在一切正常,甚至加载依赖项.GUIModule 在编译时有一个对 Windows.Forms 的引用.

All works fine now, even loads the dependencies. GUIModule has a reference to Windows.Forms when compiled.

这篇关于C# 将 DLL 字节数组加载到不同的 AppDomain 中会引发 System.IO.FileNotFoundException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-16 11:15