C# 将 DLL 字节数组加载到不同的 AppDomain 引发 System.IO.FileNotFoundException

C# Loading a DLL byte array into a different AppDomain throws System.IO.FileNotFoundException

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

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

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 本身。

我试过 "AssemblyResolve" 事件,就像那里的每个人都建议的那样,但它没有做任何事情。

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

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

有趣的发现:

如果我将 DLL 文件添加到 .exe 的文件位置,它将加载这些文件并锁定它们并成功加载到新域中。为什么当我给它一个字节数组而不是文件位置时它会这样工作?我真的必须获取字节数组并写入临时文件吗?我可以做到这一点,并在完成后删除它们,但这似乎是在浪费时间,没有理由不能从内存中全部完成。

解决方案:

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

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

[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 模块:

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

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

现在一切正常,甚至加载依赖项。 GUIModule 在编译时引用了 Windows.Forms。