【问题标题】:Interesting error with AppDomain.Load()AppDomain.Load() 的有趣错误
【发布时间】:2011-01-21 16:42:53
【问题描述】:

我正在尝试找到一种在运行时编译程序集并加载它们的方法。基本意图是将它们存储在不在磁盘上的数据库中。所以我写了一些代码,但看到了一个有趣的情况。这是我的代码:

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

代码运行完美,delete 行被注释掉,如果我取消注释,应用程序域加载程序集,AssemblyLoadEvent() 方法运行,我在控制台上看到数字 9,但是当方法结束时apd.Load() 抛出错误:“无法加载文件或程序集。”这是完全合理的。

问题是:如果没有光盘上的程序集文件,AssemblyLoadEvent() 方法如何运行?

如果该方法在原始二进制数据的帮助下以某种方式运行,那么 appdomain 有什么方法可以成功完成 Load() 方法?

【问题讨论】:

  • 您正在尝试从字节 [] 加载程序集。对吗?

标签: c# .net load appdomain assembly-loading


【解决方案1】:

它将程序集加载到“新域”并调用事件处理程序仍在新域中(如果您在事件处理程序中打印当前域,您可以验证这一点)。最后,它创建要传回的返回值。您在示例代码中忽略了该返回值,但仍会创建它。该异常发生在跨域编组期间,因为反序列化也希望将程序集加载到默认域中。

这是来自单声道的异常调用堆栈:

  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

编辑:这是来自MSDN的确认:

尝试在不是当前应用程序域的目标应用程序域上调用 Load 将导致在目标应用程序域中成功加载程序集。由于Assembly 不是MarshalByRefObject,当此方法尝试将已加载程序集的Assembly 返回到当前应用程序域时,公共语言运行时将尝试将程序集加载到当前应用程序域中,并且加载可能会失败。如果两个应用程序域的路径设置不同,则加载到当前应用程序域的程序集可能与首先加载的程序集不同。

【讨论】:

    【解决方案2】:

    因此,您尝试从 byte[] 加载程序集并调用方法。我不推荐您使用的方式(使用 AssemblyLoad 事件),因为它将为每个依赖项调用。

    @Jester 关于使用 Load() 从父域加载程序集是正确的。为了纠正这个问题,我建议使用这样的包装类:

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

    【讨论】:

    • 嗯,很有趣。所以卸载 appdomain 也会从内存中删除程序集,对吧?
    • @sad_man:由于您是通过反射调用目标程序集,我建议在 LoadAndCall 中放置一个 try-catch 块来检测反射问题。
    • 什么类型的问题?你能给我一些阅读材料吗?
    • @sad_man:反思本身就是一个大话题。在解决“Sum”方法时,目标程序集的更改可能会导致问题(例如:添加另一个采用不同参数的 Sum 方法。在这种情况下,您必须通过它的参数来识别该方法)。你可以看看 MSDN (msdn.microsoft.com/en-us/library/f7ykdhsy.aspx)
    【解决方案3】:

    为什么不使用Shadow Copy 参数?它可能会对你有所帮助。

    【讨论】:

    • 主要是因为它要求将被复制的程序集存储在ApplicationBase指定的应用程序目录或其子目录中。这样我什至可以将程序集存储在数据库中,并在必要时加载它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多