绑定到LoadFrom上下文中的程序集时如何重现InvalidCastException
作者:互联网
在Suzanne Cook’s .NET CLR Notes中,她谈到了“ LoadFrom”上下文的危险.特别,
- If a Load context assembly tries to load this assembly by display name, it will fail to be found by default (e.g., when mscorlib.dll deserializes this assembly)
- Worse, an assembly with the same identity but at a different path could be found on the probing path, causing an InvalidCastException, MissingMethodException, or unexpected method behavior later on.
如何在不显式加载程序集的两个不同版本的情况下通过反序列化来重现此行为?
解决方法:
我创建了一个控制台应用程序A.exe,该应用程序通过类库B.dll间接加载(通过`Assembly.LoadFrom)并调用(通过反射)代码.
> A.exe没有(不必要)对B.dll的引用,但是B.dll应该与A.exe位于同一目录中
>应该将B.dll的副本放置到另一个目录中(这里我使用了名为LoadFrom的子目录),这是我们将在Assembly.LoadFrom上使用的位置.
可执行文件
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(@".\LoadFrom\B.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:\Dev\bin\Debug\B.dll'.
Type B originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'LoadFrom' at location 'c:\Dev\bin\Debug\LoadFrom\B.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)的转换失败.
补充笔记:
>如果B.dll在A.exe可以使用Assembly.Load找到的地方不存在,则将出现SerializationException并显示消息“无法找到程序集’B,版本= 1.0.0.0,区域性”,而不是InvalidCastException. =中立,PublicKeyToken =空”.
>即使对于已签名的程序集,也会出现相同的问题,但是对于已签名的程序集,更令人担忧的是它可以加载已签名程序集的不同版本.也就是说,如果“ LoadFrom”上下文中的B.dll是1.0.0.0,但是在主目录中找到的B.dll是2.0.0.0,则序列化代码仍将加载错误的版本B.dll进行反序列化.
>我展示的DeepClone代码似乎是对对象进行深度克隆的较流行的方法之一.参见:Deep cloning objects in C#.
因此,对于从任何已加载到“ LoadFrom”上下文中的代码,您都无法成功使用反序列化(必须跳过附加的循环才能使程序集在默认的“ Load”上下文中成功加载).
标签:serialization,net-assembly,c 来源: https://codeday.me/bug/20191029/1961424.html