AppDomain Assembly not found when loaded from byte array

前端 未结 3 499
清歌不尽
清歌不尽 2021-01-05 11:41

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

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

3条回答
  •  無奈伤痛
    2021-01-05 12:30

    This call results in System.IO.FileNotFoundException and I don't know why. I suspect the exception to occur because the assembly is searched again on disk. But why does the program want to load it again?

    The key here is understanding loader contexts, there's an excellent article on MSDN:

    Think of loader contexts as logical buckets within an application domain that hold assemblies. Depending on how the assemblies were being loaded, they fall into one of three loader contexts.

    • Load context
    • LoadFrom context
    • Neither context

    Loading from byte[] places the assembly in the Neither context.

    As for the Neither context, assemblies in this context cannot be bound to, unless the application subscribes to the AssemblyResolve event. This context should generally be avoided.

    In the code below we use the AssemblyResolve event to load the assembly in the Load context, enabling us to bind to it.

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

    Note this is merely a proof of concept, exploring the nuts and bolts of loader contexts. The advised approach is to use a proxy as described by @caesay and further commented upon by Suzanne Cook in this article.

    Here's an implementation that doesn't keep a reference to the instance (analogous to fire-and-forget).

    First, our plugin:

    Test.cs

    namespace Plugins
    {
        public class Test
        {
            public Test()
            {
                Console.WriteLine($"Hello from {AppDomain.CurrentDomain.FriendlyName}.");
            }
        }
    }
    

    Next, in a new ConsoleApp, our plugin loader:

    PluginLoader.cs

    [Serializable]
    class PluginLoader
    {
        private readonly byte[] _myBytes;
        private readonly AppDomain _newDomain;
    
        public PluginLoader(byte[] rawAssembly)
        {
            _myBytes = rawAssembly;
            _newDomain = AppDomain.CreateDomain("New Domain");
            _newDomain.AssemblyResolve += new ResolveEventHandler(MyResolver);
        }
    
        public void Test()
        {
            _newDomain.CreateInstance("plugin", "Plugins.Test");
        }
    
        private Assembly MyResolver(object sender, ResolveEventArgs args)
        {
            AppDomain domain = (AppDomain)sender;
            Assembly asm = domain.Load(_myBytes);
            return asm;
        }
    }
    

    Program.cs

    class Program
    {
        static void Main(string[] args)
        {
            byte[] rawAssembly = File.ReadAllBytes(@"D:\Projects\AppDomainTest\plugin.dll");
            PluginLoader plugin = new PluginLoader(rawAssembly);
    
            // Output: 
            // Hello from New Domain
            plugin.Test();
    
            // Output: 
            // Assembly: mscorlib
            // Assembly: ConsoleApp
            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                Console.WriteLine($"Assembly: {asm.GetName().Name}");
            }
    
            Console.ReadKey();
        }
    }
    

    The output shows CreateInstance("plugin", "Plugins.Test") is successfully called from the default app domain, although it has no knowledge of the plugin assembly.

提交回复
热议问题