绑定到 LoadFrom 上下文中的程序集时如何重现 InvalidCastException [英] How to reproduce InvalidCastException when binding to an Assembly in the LoadFrom Context

查看:27
本文介绍了绑定到 LoadFrom 上下文中的程序集时如何重现 InvalidCastException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Suzanne Cook 的 .NET CLR 注释,她谈到了LoadFrom"上下文的危险.具体来说,

In Suzanne Cook's .NET CLR Notes she talks about the dangers of the "LoadFrom" context. Specifically,

  • 如果加载上下文程序集尝试按显示名称加载此程序集,默认情况下将无法找到它(例如,当 mscorlib.dll 反序列化此程序集时)
  • 更糟糕的是,可能会在探测路径上找到具有相同标识但位于不同路径的程序集,从而导致稍后出现 InvalidCastException、MissingMethodException 或意外方法行为.

如何通过反序列化重现这种行为,但又不显式加载两个不同版本的程序集?

How do you reproduce this behavior with deserialization, but without explicitly loading two different versions of the assembly?

推荐答案

我创建了一个控制台应用程序 A.exe,它从类库间接加载(通过`Assembly.LoadFrom)和调用(通过反射)代码, B.dll.

I've created a Console application, A.exe, which indirectly loads (via `Assembly.LoadFrom) and calls (via reflection) code from a class library, B.dll.

  • A.exe 没有(必然)引用 B.dll,但 B.dll 应该与 A.exe 存在于同一目录中
  • 应该将 B.dll 的副本放在另一个目录中(这里我使用了名为 LoadFrom 的子目录),这是我们将使用 Assembly.LoadFrom 的位置.

A.exe

class Program
{
    static void Main(string[] args)
    {
        // I have a post build step that copies the B.dll to this sub directory.
        // but the B.dll also lives in the main directory alongside the exe:
        // mkdir LoadFrom
        // copy B.dll LoadFrom
        //
        var loadFromAssembly = Assembly.LoadFrom(@".LoadFromB.dll");
        var mySerializableType = loadFromAssembly.GetType("B.MySerializable");

        object mySerializableObject = Activator.CreateInstance(mySerializableType);
        var copyMeBySerializationMethodInfo = mySerializableType.GetMethod("CopyMeBySerialization");

        try
        {
            copyMeBySerializationMethodInfo.Invoke(mySerializableObject, null);
        }
        catch (TargetInvocationException tie)
        {
            Console.WriteLine(tie.InnerException.ToString());
        }

        Console.ReadKey();
    }
}

B.dll

namespace B
{
    [Serializable]
    public class MySerializable
    {
        public MySerializable CopyMeBySerialization()
        {
            return DeepClone(this);
        }

        private static T DeepClone<T>(T obj)
        {
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T)formatter.Deserialize(ms);
            }
        }
    }
}

输出

System.InvalidCastException: 
  [A]B.MySerializable cannot be cast to 
  [B]B.MySerializable. 
  Type A originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' 
    in the context 'Default' at location 'c:DevinDebugB.dll'. 
  Type B originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' 
    in the context 'LoadFrom' at location 'c:DevinDebugLoadFromB.dll'.

   at B.MySerializable.DeepClone[T](T obj)
   at B.MySerializable.CopyMeBySerialization()

这是正在发生的事情:

  • 当调用 formatter.Deserialize(ms) 时,它使用存储在 MemoryStream 中的信息来确定它需要创建什么类型的对象(以及它需要哪个程序集才能创建该对象).
  • 它发现它需要 B.dll 并尝试加载它(从默认的加载"上下文).
  • 未找到当前加载的 B.dll(因为它是在LoadFrom"上下文中加载的).
  • 因此,尝试在通常的位置找到 B.dll——它在 ApplicationBase 目录中找到并被加载.
  • 此 B.dll 中的所有类型都被视为与其他 B.dll 中的类型不同.因此,表达式 (T)formatter.Deserialize(ms) 中的转换失败.
  • When the call to formatter.Deserialize(ms) is made, it uses the information stored in the MemoryStream to determine what type of object it needs to create (and which assembly it needs in order to create that object).
  • It finds that it needs B.dll and attempts to Load it (from the default "Load" context).
  • The currently loaded B.dll is not found (because it was loaded in the "LoadFrom" context).
  • Thus, an attempt is made to find B.dll in the usual locations--it is found in the ApplicationBase directory and is loaded.
  • All types in this B.dll are considered different types that those from the other B.dll. Thus the cast in the expression (T)formatter.Deserialize(ms) fails.

附加说明:

  • 如果 B.dll 不存在于 A.exe 可以使用 Assembly.Load 找到它的地方,那么将有一个 InvalidCastException 而不是 InvalidCastException>SerializationException 带有消息无法找到程序集B,版本=1.0.0.0,Culture=neutral,PublicKeyToken=null".
  • 即使签名程序集也会出现同样的问题,但更令人担忧的是签名程序集可以加载不同版本的签名程序集.也就是说,如果LoadFrom"上下文中的B.dll是1.0.0.0,但在主目录中找到的B.dll是2.0.0.0,序列化代码仍然会加载错误版本的B.dll进行反序列化.
  • 我展示的 DeepClone 代码似乎是对对象进行深度克隆的一种更流行的方法.请参阅:C# 中的深度克隆对象.
  • If the B.dll had not existed somewhere where A.exe could find it using Assembly.Load, then instead of an InvalidCastException, there would be a SerializationException with the message Unable to find assembly 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
  • The same problem occurs even with signed assemblies, but what is more alarming with signed assemblies is that it can load a different version of the signed assembly. That is, if B.dll in the "LoadFrom" context is 1.0.0.0, but the B.dll found in the main directory is 2.0.0.0, the serialization code will still load the wrong version B.dll to do deserialization.
  • The DeepClone code I've shown seems to be one of the more popular ways to do a deep clone on an object. See: Deep cloning objects in C#.

因此,从加载到LoadFrom"上下文中的任何代码中,您都无法成功使用反序列化(无需跳过额外的环节以允许程序集在默认的Load"上下文中成功加载).

So, from any code that was loaded into the "LoadFrom" context, you cannot use deserialization successfully (without jumping through additional hoops to allow the assembly to successfully load in the default "Load" context).

这篇关于绑定到 LoadFrom 上下文中的程序集时如何重现 InvalidCastException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆