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

安稳与你 提交于 2019-12-10 12:06:27

问题


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

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);

However changing

Assembly Assembly = Domain.Load(buffer);

to

Assembly = AppDomain.CurrentDomain.Load(buffer);

Makes it work.

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

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

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.

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

Interesting discovery:

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.


回答1:


SOLUTION:

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.

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

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

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



来源:https://stackoverflow.com/questions/50776377/c-sharp-loading-a-dll-byte-array-into-a-different-appdomain-throws-system-io-fil

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!