Dom*_*tal 6 .net c# serialization binary-serialization deserialization
我正在尝试读取二进制序列化对象,我没有它的对象定义/源.我在文件中找到了一个峰值并看到了属性名称,因此我手动重新创建了对象(让我们称之为SomeDataFormat).
我最终得到了这个:
public class SomeDataFormat // 16 field
{
public string Name{ get; set; }
public int Country{ get; set; }
public string UserEmail{ get; set; }
public bool IsCaptchaDisplayed{ get; set; }
public bool IsForgotPasswordCaptchaDisplayed{ get; set; }
public bool IsSaveChecked{ get; set; }
public string SessionId{ get; set; }
public int SelectedLanguage{ get; set; }
public int SelectedUiCulture{ get; set; }
public int SecurityImageRefId{ get; set; }
public int LogOnId{ get; set; }
public bool BetaLogOn{ get; set; }
public int Amount{ get; set; }
public int CurrencyTo{ get; set; }
public int Delivery{ get; set; }
public bool displaySSN{ get; set; }
}
Run Code Online (Sandbox Code Playgroud)
现在我可以像这样反序列化它:
BinaryFormatter formatter = new BinaryFormatter();
formatter.AssemblyFormat = FormatterAssemblyStyle.Full; // original uses this
formatter.TypeFormat = FormatterTypeStyle.TypesWhenNeeded; // this reduces size
FileStream readStream = new FileStream("data.dat", FileMode.Open);
SomeDataFormat data = (SomeDataFormat) formatter.Deserialize(readStream);
Run Code Online (Sandbox Code Playgroud)
首先可疑的是,只有2个字符串(SessionId&UserEmail)在反序列化的数据对象中具有值.其他属性为null或只是0.这可能是有意的,但我仍然怀疑在反序列化过程中出现了问题.
第二个可疑的事情是,如果我重新序列化这个对象,我最终会得到不同的文件大小.原始(695字节).重新序列化的对象是698个字节.所以有3bytes的区别.我应该获得与原始文件相同的文件大小.
看看原始文件和新的(重新编目的)文件:
如您所见,在标题部分之后,数据显示的顺序不同.例如,您可以看到电子邮件和sessionID不在同一个地方.
更新:将警告我,"PublicKeyToken = null"之后的字节也不同.(03 < - > 05)
任何帮助表示赞赏.
为什么两个文件中的值的顺序不同?
这是因为会员订单不是基于声明订购.http://msdn.microsoft.com/en-us/library/424c79hc.aspx
GetMembers方法不按特定顺序返回成员,例如按字母顺序或声明顺序.您的代码不得依赖于返回成员的顺序,因为该顺序会有所不同.
.
与2个序列化对象相比,为什么还有3个字节?
首先,TypeFormat'TypesWhenNeeded'实际上应该是'TypesAlways'.这就是为什么会有这么多差异的原因.例如,'= null'之后的05变为03是由于那个原因.
其次,你没有正确的类型.查看ILSpy中的BinaryFormatter和十六进制转储显示标记为"int"的成员实际上是"字符串".
public class SomeDataFormat // 16 field
{
public string Name { get; set; }
public string Country { get; set; }
public string UserEmail{ get; set; }
public bool IsCaptchaDisplayed{ get; set; }
public bool IsForgotPasswordCaptchaDisplayed{ get; set; }
public bool IsSaveChecked{ get; set; }
public string SessionId{ get; set; }
public string SelectedLanguage{ get; set; }
public string SelectedUiCulture{ get; set; }
public string SecurityImageRefId{ get; set; }
public string LogOnId{ get; set; }
public bool BetaLogOn{ get; set; }
public string Amount{ get; set; }
public string CurrencyTo{ get; set; }
public string Delivery{ get; set; }
public bool displaySSN{ get; set; }
}
Run Code Online (Sandbox Code Playgroud)
我错过了什么?我怎么能这样做?
我没有看到使用给定的BinaryFormatter做到这一点的方法.你可以反编译/反转BinaryFormatter的工作方式.
因为某人可能对我感兴趣,所以我决定写这篇有关以下内容的文章:序列化.NET对象的二进制格式是什么样的,我们如何正确地解释它?
我的所有研究都基于.NET Remoting:二进制格式数据结构规范。
示例类:
为了提供一个可行的示例,我创建了一个简单的类,该类A包含2个属性,一个字符串和一个整数值,它们分别称为SomeString和SomeValue。
类A看起来像这样:
[Serializable()]
public class A
{
public string SomeString
{
get;
set;
}
public int SomeValue
{
get;
set;
}
}
Run Code Online (Sandbox Code Playgroud)
对于序列化,我BinaryFormatter当然使用了:
BinaryFormatter bf = new BinaryFormatter();
StreamWriter sw = new StreamWriter("test.txt");
bf.Serialize(sw.BaseStream, new A() { SomeString = "abc", SomeValue = 123 });
sw.Close();
Run Code Online (Sandbox Code Playgroud)
可以看出,我传递了一个A包含abc和123作为值的类的新实例。
结果数据示例:
如果我们在十六进制编辑器中查看序列化的结果,则会得到以下内容:

让我们解释示例结果数据:
根据上述规范(此处是PDF的直接链接:[MS-NRBF] .pdf),流中的每个记录都由标识RecordTypeEnumeration。本节2.1.2.1 RecordTypeNumeration规定:
该枚举标识记录的类型。每个记录(MemberPrimitiveUnTyped除外)都以记录类型枚举开头。枚举的大小为一个BYTE。
SerializationHeaderRecord:
因此,如果我们回顾一下获得的数据,就可以开始解释第一个字节:

如标识中2.1.2.1 RecordTypeEnumeration所述的值,该值0在SerializationHeaderRecord中指定2.6.1 SerializationHeaderRecord:
SerializationHeaderRecord记录必须是二进制序列化中的第一条记录。该记录具有格式的主要版本和次要版本,以及顶部对象和标头的ID。
它包括:
有了这些知识,我们可以解释包含17个字节的记录:

00表示RecordTypeEnumeration这是SerializationHeaderRecord在我们的例子。
01 00 00 00 代表 RootId
如果序列化流中既没有BinaryMethodCall也没有BinaryMethodReturn记录,则此字段的值务必包含序列化流中包含的Class,Array或BinaryObjectString记录的ObjectId。
因此,在我们的例子中,它应该是的ObjectId值1(因为数据是使用little-endian序列化的),我们希望可以再次看到它;-)
FF FF FF FF 代表 HeaderId
01 00 00 00 代表 MajorVersion
00 00 00 00表示MinorVersion
BinaryLibrary:
按照指定,每个记录必须以开头RecordTypeEnumeration。最后一条记录完成后,我们必须假设新的记录开始了。
让我们解释下一个字节:

如我们所见,在我们的示例中,SerializationHeaderRecord它后面是BinaryLibrary记录:
BinaryLibrary记录将INT32 ID(在[MS-DTYP] 2.2.22节中指定)与库名相关联。这允许其他记录通过使用ID来引用库名称。当有多个引用相同库名称的记录时,此方法可减小导线尺寸。
它包括:
LengthPrefixedString))
如2.1.1.6 LengthPrefixedString...所述
LengthPrefixedString表示一个字符串值。该字符串的前缀是UTF-8编码的字符串的长度(以字节为单位)。长度在可变长度字段中编码,最小为1个字节,最大为5个字节。为了最大程度地减小导线尺寸,将长度编码为可变长度字段。
在我们的简单示例中,长度始终使用进行编码1 byte。有了这些知识,我们就可以继续解释流中的字节:

0C代表RecordTypeEnumeration标识BinaryLibrary记录的。
02 00 00 00表示LibraryId这是2在我们的例子。
现在LengthPrefixedString如下:

42代表的长度信息,LengthPrefixedString其中包含LibraryName。
在我们的情况下,42(十进制66)的长度信息告诉我们,我们需要读取接下来的66个字节并将其解释为LibraryName。
如前所述,字符串是经过UTF-8编码的,因此上述字节的结果将类似于:_WorkSpace_, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
ClassWithMembersAndTypes:
同样,记录已完成,因此我们解释RecordTypeEnumeration下一个:

05标识一条ClassWithMembersAndTypes记录。本节2.3.2.1 ClassWithMembersAndTypes规定:
ClassWithMembersAndTypes记录是Class记录中最详细的记录。它包含有关成员的元数据,包括成员的名称和远程处理类型。它还包含引用类的库名称的库ID。
它包括:
ClassInfo:
2.3.1.1 ClassInfo记录中所述包括:
LengthPrefixedString))LengthPrefixedString其中项目数必须等于MemberCount字段中指定的值。)
返回原始数据,分步进行:

01 00 00 00代表ObjectId。我们已经看到了这一个,它被指定为RootId中SerializationHeaderRecord。

0F 53 74 61 63 6B 4F 76 65 72 46 6C 6F 77 2E 41表示使用表示的Name类的LengthPrefixedString。如前所述,在我们的示例中,字符串的长度定义为1个字节,因此第一个字节0F指定必须使用UTF-8读取和解码15个字节。结果看起来像这样:StackOverFlow.A-很明显,我用它StackOverFlow作为名称空间的名称。

02 00 00 00代表MemberCount,它告诉我们接下来将有两个成员(均以表示)LengthPrefixedString。
第一位成员的姓名:

1B 3C 53 6F 6D 65 53 74 72 69 6E 67 3E 6B 5F 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64表示第一MemberName,1B再次是它是27个字节的长度的结果是这样的字符串的长度:<SomeString>k__BackingField。
第二名成员的姓名:

1A 3C 53 6F 6D 65 56 61 6C 75 65 3E 6B 5F 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64表示第二个MemberName,1A指定字符串为26个字节长。结果如下:<SomeValue>k__BackingField。
MemberTypeInfo:
之后ClassInfo的MemberTypeInfo如下。
本节2.3.1.2 - MemberTypeInfo指出,该结构包含:
BinaryTypeEnumeration值的序列,表示要传输的成员类型。数组必须:
与ClassInfo结构的MemberNames字段具有相同数量的项目。
进行排序,以使BinaryTypeEnumeration对应于ClassInfo结构的MemberNames字段中的Member名称。
BinaryTpeEnum其他信息。
| BinaryTypeEnum | AdditionalInfos |
|----------------+--------------------------|
| Primitive | PrimitiveTypeEnumeration |
| String | None |
因此,考虑到这一点,我们几乎已经到了……我们期望有2个BinaryTypeEnumeration值(因为中有2个成员MemberNames)。
再次回到完整MemberTypeInfo记录的原始数据:

01表示BinaryTypeEnumeration第一个成员的,根据2.1.2.2 BinaryTypeEnumeration我们的预期String,它使用表示LengthPrefixedString。
00代表BinaryTypeEnumeration第二个成员的,根据说明,它也是一个Primitive。如上所述,Primitive后面是附加信息,在本例中为PrimitiveTypeEnumeration。这就是为什么我们需要读取下一个字节,即08,将其与表中所述的表进行匹配,2.1.2.3 PrimitiveTypeEnumeration并惊讶地注意到Int32,正如其他有关基本数据类型的文档中所述,我们可以期望它由4个字节表示。
LibraryId:
在后MemerTypeInfo的LibraryId如下,它由4个字节表示:

02 00 00 00代表的LibraryId是2。
价值:
如2.3 Class Records:
类别成员的值必须按2.7节中的规定序列化为该记录之后的记录。记录的顺序必须与ClassInfo(第2.3.1.1节)结构中指定的MemberNames的顺序匹配。
这就是为什么我们现在可以期望成员的价值。
让我们看一下最后几个字节:

06标识一个BinaryObjectString。它代表了我们SomeString财产的价值(<SomeString>k__BackingField准确地说是)。
据2.5.7 BinaryObjectString其包含:
LengthPrefixedString)
因此,我们可以清楚地识别出

03 00 00 00代表ObjectId。
03 61 62 63表示Valuewhere 03是字符串本身的长度,61 62 63是转换为的内容字节abc。
希望您能记得还有一个成员Int32。知道Int32使用4个字节表示时,我们可以得出以下结论:

必须是Value我们第二个成员的。7B十六进制等于123十进制,这似乎适合我们的示例代码。
因此,这是完整的ClassWithMembersAndTypes记录:

MessageEnd:

最后,最后一个字节0B代表MessageEnd记录。
| 归档时间: |
|
| 查看次数: |
5033 次 |
| 最近记录: |