简体   繁体   中英

Interesting error with AppDomain.Load()

I am trying to find a way to compile assemblies at runtime and load them. Basic intention is to store them in a database not on the disc. So I wrote some code but saw an interesting situation. Here is my code:

//SumLib
namespace SumLib
{
    public class SumClass
    {
        public static int Sum(int a, int b)
        {
            return a + b;
        }
    }
}


// Console app
class Program
{

    public static void AssemblyLoadEvent(object sender, AssemblyLoadEventArgs args)
    {

        object[] tt = { 3, 6 };
        Type typ = args.LoadedAssembly.GetType("SumLib.SumClass");
        MethodInfo minfo = typ.GetMethod("Sum");
        int x = (int)minfo.Invoke(null, tt);
        Console.WriteLine(x);
    }

    static void Main(string[] args)
    {

        AppDomain apd = AppDomain.CreateDomain("newdomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
        apd.AssemblyLoad += new AssemblyLoadEventHandler(AssemblyLoadEvent);

        FileStream fs = new FileStream("Sumlib.dll", FileMode.Open);
        byte[] asbyte = new byte[fs.Length];
        fs.Read(asbyte, 0, asbyte.Length);
        fs.Close();
        fs.Dispose();

//      File.Delete("Sumlib.dll");

        apd.Load(asbyte);

        Console.ReadLine();
    }
}

The code runs perfectly with the delete line is commented out, If I uncomment it, the application domain loads the assembly, AssemblyLoadEvent() method runs, I see a number 9 on the console, but when the method is over apd.Load() throws an error : "Could not load file or assembly." which is perfectly reasonable.

The question is: how can AssemblyLoadEvent() method run without the assembly file on the disc?

If the method somehow runs with the help of raw binary data then is there any way that appdomain finishes the Load() method succesfully?

It loads the assembly fine into the "newdomain" and invokes the event handler still in the newdomain (you can verify this if you print the current domain in the event handler). Finally, it creates the return value to be passed back. You ignore that return value in your sample code, but it is still created. The exception happens during the cross-domain marshalling, because the deserialization wants to load the assembly into the default domain as well.

Here is the exception call stack from mono:

  at System.AppDomain.Load (System.String assemblyString, System.Security.Policy.Evidence assemblySecurity, Boolean refonly) [0x00000] in <filename unknown>:0
  at System.AppDomain.Load (System.String assemblyString) [0x00000] in <filename unknown>:0
  at (wrapper remoting-invoke-with-check) System.AppDomain:Load (string)
  at System.Reflection.Assembly.Load (System.String assemblyString) [0x00000] in <filename unknown>:0
  at System.UnitySerializationHolder.GetRealObject (StreamingContext context) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.ObjectRecord.LoadData (System.Runtime.Serialization.ObjectManager manager, ISurrogateSelector selector, StreamingContext context) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.ObjectManager.DoFixups () [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (System.IO.BinaryReader reader) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) [0x00000] in <filename unknown>:0
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) [0x00000] in <filename unknown>:0
  at System.Runtime.Remoting.RemotingServices.DeserializeCallData (System.Byte[] array) [0x00000] in <filename unknown>:0
  at (wrapper xdomain-invoke) System.AppDomain:Load (byte[])
  at (wrapper remoting-invoke-with-check) System.AppDomain:Load (byte[])
  at Program.Main (System.String[] args) [0x00000] in <filename unknown>:0

Edit: here is confirmation from MSDN :

An attempt to call Load on a target application domain that is not the current application domain will result in a successful load of the assembly in the target application domain. Since an Assembly is not MarshalByRefObject, when this method attempts to return the Assembly for the loaded assembly to the current application domain, the common language runtime will try to load the assembly into the current application domain and the load might fail. The assembly that is loaded into the current application domain might be different from the assembly that was loaded first if the path settings for the two application domains are different.

So you are trying to load an assembly from a byte[] and call a method. I do not recommend the way you did (working with the AssemblyLoad event), since it will be called for every dependencies.

@Jester is right about loading an assembly using Load() from a parent domain. To correct this, I suggest to use a wrapper class like this:

// Console app 
class Program 
{  
    public class AssemblyLoader : MarshalByRefObject
    {
        public void LoadAndCall(byte[] binary)
        {
            Assembly loadedAssembly = AppDomain.CurrentDomain.Load(binary);
            object[] tt = { 3, 6 };
            Type typ = loadedAssembly.GetType("SumLib.SumClass");
            MethodInfo minfo = typ.GetMethod("Sum", BindingFlags.Static | BindingFlags.Public);
            int x = (int)minfo.Invoke(null, tt);
            Console.WriteLine(x);
        }
    }

    static void Main()
    {
        AppDomain apd = AppDomain.CreateDomain("newdomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
        FileStream fs = new FileStream("Sumlib.dll", FileMode.Open);
        byte[] asbyte = new byte[fs.Length];
        fs.Read(asbyte, 0, asbyte.Length);
        fs.Close();
        fs.Dispose();
        File.Delete("Sumlib.dll");    

        AssemblyLoader loader = (AssemblyLoader)apd.CreateInstanceAndUnwrap(typeof(AssemblyLoader).Assembly.FullName, typeof(AssemblyLoader).FullName);
        loader.LoadAndCall(asbyte);
        Console.ReadLine();
      }
}  

Why don't you use Shadow Copy parameter? It might help you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM