问题描述
我目前正在尝试与先使用C#的BinaryFormatter格式化通过网络发送数据的程序进行互操作.这是一个愚蠢的想法,我讨厌它,但是我必须与它进行互操作.
I am currently trying to interoperate with a program that sends data over the network after first formatting it with C#'s BinaryFormatter. It's a dumb idea, and I hate it, but I have to interoperate with it.
我知道类型是什么样,我知道它的确切布局.但是由于种种原因,我无法在程序中添加对该特定程序集的引用.
I know what the type looks like, I know it's exact layout. But I can't add a reference to that specific assembly in my program for various reasons.
鉴于BinaryFormatter与特定类型/版本的耦合程度如何,即使知道数据结构,我似乎也找不到找到使其反序列化的方法.
Given how tightly coupled BinaryFormatter is to the specific type/version, I can't seem to find a way to get it to deserialize despite knowing the data structure.
我目前正在研究创建具有所有正确属性的假程序集并尝试将其链接(似乎很混乱),或者手动尝试遍历二进制流并提取我正在寻找的值(我正在查看有关此文档的MS文档,并且对于布局来说很明显.)
I'm currently look at either creating a fake assembly with all the right attributes and attempting to link that in (seems really messy), or manually attempting to go through the binary stream and extract the values I'm looking for (I'm looking at the MS documentation on this, and it's clear as mud as to the layout).
还有其他很棒的主意吗?或者过去有人在此方面取得过成功吗?似乎我知道我需要的所有信息,而BinaryFormatter却对此非常脆弱.
Any other great ideas, or has anyone had successes with this in the past? It seems like I know all the information that I need, and BinaryFormatter is just being notoriously fragile about it.
要回答以下问题(顺便说一句,这是个好主意),有两个原因.
To answer the question below (which is a good point, btw) there are a couple of reasons.
-
项目清洁度.向一个功能的外部.exe添加5MB引用有点麻烦.
Project cleanliness. Adding a 5MB reference to an .exe that's external for one feature is a bit off.
我正在与之交互的设备在世界各地部署了各种版本.我关心的项目的内部数据结构在所有这些项目中都是相同的,但是程序集的版本不同,从而导致BinaryFormatter损坏.我可以将二进制流转换为字符串,搜索版本号,然后加载正确的版本,但是现在我周围有十几个.exe,等待我加载正确的版本.然后,该方案并不是很适合将来(当然,整个方案并不是真正的适合未来,但是我至少想抽象一些BinaryFormatter的脆弱性,使我的工作更轻松).只需编写此响应,我就可以考虑使用emit或类似的方法动态创建自定义程序集.....但是,必须有一种更简单的方法,对吗?我实际上是在中等大小的数据结构中寻找几个笨蛋.
The pieces of equipment I'm interoperating with have various versions deployed around the world. The internal data structure for the item I care about is the same across all of them, but the versions of the assembly are different, causing breakage with BinaryFormatter. I could convert the binary stream to a string, search for the version number then load the proper version, but now I have a dozen .exe's laying around waiting for me to load the right one. Then that scheme is not very future proof (well, this whole scheme isn't really future proof, but I'd at least like to abstract away some of the fragility of BinaryFormatter to make my job easier). Just writing this response has got me thinking about using emit or similar to create a custom assembly on the fly.....but man, there has to be an easier way, right? I'm literally looking for a couple of bools in a medium sized data structure.
该对象的变量通过具有某些逻辑的get/set属性公开,并尝试进行函数调用并更新其他可能不在我这边的对象(aka,get为我提供了值我需要,但还会触发通知,这些通知会在链接的依赖项中起伏不定,并且我可以使异常冒泡到我的应用程序中.谈论代码气味!).它变成了一个依赖/实现的兔子洞.
The object's variables are exposed through get/set properties that have some logic to them, and try to make function calls and update other objects that might not be present on my side (aka, a get gets me the value I need, but also triggers notifications that ripple through the linked dependency and I can get exceptions bubbling up to my application. Talk about code smell!). It turns into a dependency/implementation rabbit hole.
edit2:制造商正在与我合作,以改善他们的系统,但是当我们宣传与X一起使用"时,我们希望它可以与X一起使用,而不需要特定的版本.特别是对于我们的一些客户系统来说,这些系统受到版本严格控制,仅更新有问题的应用程序就成为了一项重大工作.
edit2: The manufacturer is working with me to make their system better, but when we advertise "Works with X" we'd like it to just work with X, not require specific versions. Particularly with some of our customer systems that are tightly version controlled, and just updating the offending application becomes a major effort.
推荐答案
SerializationSurrogate和SerializationBinder可以帮助您解决问题.假定您只对几个属性感兴趣,则可以通过枚举传递给SerializationSurrogate的SerializationInfo来反序列化为填充的代理类.不幸的是,它只会使您陷入困境.
as you speculated in the comments to your question SerializationSurrogate and SerializationBinder could get you part of the way there. Given that you're only interested in a few properties you could deserialize to a proxy class which you'd populate by enumerating over the SerializationInfo passed to your SerializationSurrogate. Unfortunately, it will only get you part of the way there.
问题在于访问属性不会触发序列化对象中的任何副作用,因为您仅使用SerializationSurrogate访问数据-二进制代码均未执行.快速与以下肮脏的测试代码说明了该问题:
The problem is that accessing the properties is not going to trigger any side-effects in the serialized object because you're only accessing the data using the SerializationSurrogate - none of the binary code is getting executed. The quick & dirty test code below illustrates the problem:
namespace BinaryProxy
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
class TestClass
{
public bool mvalue;
public TestClass(bool value)
{
BoolValue = value;
}
public bool BoolValue
{
get
{
// won't happen
SideEffect = DateTime.Now.ToString();
return mvalue;
}
set
{
mvalue = value;
}
}
public string SideEffect { get; set; }
}
class ProxyTestClass
{
private Dictionary<string, object> data = new Dictionary<string, object>();
public Object GetData(string name)
{
if(data.ContainsKey(name))
{
return data[name];
}
return null;
}
public void SetData(string name, object value)
{
data[name] = value;
}
public IEnumerable<KeyValuePair<string, object>> Dump()
{
return data;
}
}
class SurrogateTestClassConstructor : ISerializationSurrogate
{
private ProxyTestClass mProxy;
/// <summary>
/// Populates the provided <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the object.
/// </summary>
/// <param name="obj">The object to serialize. </param>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data. </param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization. </param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Populates the object using the information in the <see cref="T:System.Runtime.Serialization.SerializationInfo"/>.
/// </summary>
/// <returns>
/// The populated deserialized object.
/// </returns>
/// <param name="obj">The object to populate. </param>
/// <param name="info">The information to populate the object. </param>
/// <param name="context">The source from which the object is deserialized. </param>
/// <param name="selector">The surrogate selector where the search for a compatible surrogate begins. </param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
if (mProxy == null) mProxy = new ProxyTestClass();
var en = info.GetEnumerator();
while (en.MoveNext())
{
mProxy.SetData(en.Current.Name, en.Current.Value);
}
return mProxy;
}
}
sealed class DeserializeBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
return typeof(ProxyTestClass);
}
}
static class Program
{
static void Main()
{
var tc = new TestClass(true);
byte[] serialized;
using (var fs = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(fs, tc);
serialized = fs.ToArray();
var surrSel = new SurrogateSelector();
surrSel.AddSurrogate(typeof(ProxyTestClass),
new StreamingContext(StreamingContextStates.All), new SurrogateTestClassConstructor());
using (var fs2 = new MemoryStream(serialized))
{
var formatter2 = new BinaryFormatter();
formatter2.Binder = new DeserializeBinder();
formatter2.SurrogateSelector = surrSel;
var deser = formatter2.Deserialize(fs2) as ProxyTestClass;
foreach (var c in deser.Dump())
{
Console.WriteLine("{0} = {1}", c.Key, c.Value);
}
}
}
}
}
}
在上面的示例中,TestClass
'SideEffect
支持字段将保持为空.
In the example above, the TestClass
' SideEffect
backing field will remain null.
如果您不需要副作用,而只想访问某些字段的值,则该方法有些可行.否则,除了遵循Jon Skeet的建议,我无法想到任何其他可行的解决方案.
If you don't need the side-effects and just want to access the value of some fields the approach is somewhat viable. Otherwise I can't think of any other viable solution other than following Jon Skeet's suggestion.
这篇关于BinaryFormatter-是否可以在没有汇编的情况下反序列化已知的类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!