C#将DLL字节数组加载到另一个AppDomain会引发System.IO.FileNotFoundException

时间:2018-06-09 16:35:23

标签: c# dll appdomain

我正在尝试将已复制到字节数组中的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的文件位置,它将加载它们并锁定它们并成功加载到新域中。当我提供一个字节数组而不是文件位置时,为什么它会以这种方式工作?我实际上必须采取字节数组并写入临时文件?我可以这样做,并在我完成它们时删除它们,但这似乎是浪费时间,没有理由不能从内存中完成所有这些。

1 个答案:

答案 0 :(得分:0)

<强> SOLUTION:

AppDomains的文档记录很差,无论我在哪里都很难解释。这就像人们试图隐藏它并让它成为公众群众的秘密。显然,AppDomains不会像变量和其他对象引用那样在彼此之间共享数据。您需要在它们之间使用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。