我正在尝试读取二进制序列化对象,我没有它的对象定义/来源。我在文件中找到了一个峰值并看到了属性名称,因此我手动重新创建了对象(让我们称之为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; }
}
现在我可以像这样反序列化它:
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);
首先可疑的是,只有2个字符串(SessionId
& UserEmail
)在反序列化的数据对象中具有值。其他属性为null或只是0.这可能是有意的,但我仍然怀疑在反序列化过程中出现了问题。
第二个可疑的事情是如果我重新序列化这个对象,我最终会得到不同的文件大小。原始(695字节)。重新序列化的对象是698个字节。所以有3bytes的区别。我应该获得与原始文件相同的文件大小。
查看原始文件和新的(重新编目的)文件:
最初序列化的文件:(zoom) 重新编目的文件:(zoom)
如您所见,在标题部分之后,数据显示的顺序不同。例如,您可以看到电子邮件和sessionID不在同一个地方。
UPDATE:会警告我“PublicKeyToken = null”之后的字节也不同。 (03< - > 05)
感谢任何帮助。
答案 0 :(得分:8)
为什么两个文件中的值的顺序不同?
这是因为会员订单不是基于声明订购。 http://msdn.microsoft.com/en-us/library/424c79hc.aspx
GetMembers方法不会按特定顺序返回成员,例如按字母顺序或声明顺序。您的代码不得依赖于返回成员的顺序,因为该顺序会有所不同。
与2个序列化对象相比,为什么还有3个字节?
首先,TypeFormat的“TypesWhenNeeded”实际上应该是'TypesAlways'。这就是为什么会有这么多差异的原因。例如,'= null'变为03之后的05就是由此产生的。
其次,你没有正确的类型。查看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; }
}
我错过了什么?我怎么能这样做?
我没有看到使用给定BinaryFormatter的方法。你可以反编译/反转BinaryFormatter的工作方式。
答案 1 :(得分:5)
因为对于我决定做这篇文章的人来说可能有兴趣关于序列化.NET对象的二进制格式是什么样子以及我们如何正确解释它?
我的所有研究都基于.NET Remoting: Binary Format Data Structure规范。
示例类:
为了有一个工作示例,我创建了一个名为A
的简单类,它包含2个属性,一个字符串和一个整数值,它们被称为SomeString
和SomeValue
。
班级A
如下所示:
[Serializable()]
public class A
{
public string SomeString
{
get;
set;
}
public int SomeValue
{
get;
set;
}
}
对于序列化,我当然使用BinaryFormatter
:
BinaryFormatter bf = new BinaryFormatter();
StreamWriter sw = new StreamWriter("test.txt");
bf.Serialize(sw.BaseStream, new A() { SomeString = "abc", SomeValue = 123 });
sw.Close();
可以看出,我传递了一个包含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
的{{1}}的长度信息。
在我们的例子中,LibraryName
(十进制66)的长度信息告诉我们,我们需要读取接下来的66个字节并将它们解释为42
。
如前所述,字符串是LibraryName
编码的,因此上面字节的结果类似于:UTF-8
的 ClassWithMembersAndTypes:强>
同样,记录已完成,因此我们解释了下一个的_WorkSpace_, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
:
RecordTypeEnumeration
标识05
条记录。第ClassWithMembersAndTypes
节说明:
ClassWithMembersAndTypes记录是类记录中最详细的。它包含有关成员的元数据,包括成员的名称和远程处理类型。它还包含一个引用类的库名称的库ID。
它包括:
的的ClassInfo:强>
如2.3.2.1 ClassWithMembersAndTypes
所述,记录包括:
2.3.1.1 ClassInfo
))LengthPrefixedString
' s的序列,其中项目数必须等于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
。如上所述,在我们的示例中,字符串的长度定义为1个字节,因此第一个字节LengthPrefixedString
指定必须使用UTF-8读取和解码15个字节。结果如下所示:0F
- 显然我使用StackOverFlow.A
作为命名空间的名称。
StackOverFlow
代表02 00 00 00
,它告诉我们2位成员,均由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
再次是字符串的长度,长度为27个字节,结果如下:1B
。
第二个成员的名字:
<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
指定字符串长度为26个字节。结果如下:1A
。
的 MemberTypeInfo:强>
<SomeValue>k__BackingField
ClassInfo
之后。
第MemberTypeInfo
节声明该结构包含:
一系列BinaryTypeEnumeration值,表示正在传输的成员类型。数组必须:
与ClassInfo结构的MemberNames字段具有相同数量的项目。
按顺序排列,使BinaryTypeEnumeration对应ClassInfo结构的MemberNames字段中的成员名称。
2.3.1.2 - MemberTypeInfo
其他信息可能存在也可能不存在。
BinaryTpeEnum
| BinaryTypeEnum | AdditionalInfos |
|----------------+--------------------------|
| Primitive | PrimitiveTypeEnumeration |
因此考虑到这一点,我们几乎就在那里......
我们预计会有2 | String | None |
个值(因为BinaryTypeEnumeration
中有2个成员)。
再次,回到完整MemberNames
记录的原始数据:
MemberTypeInfo
代表第一个成员的01
,根据BinaryTypeEnumeration
我们可以预期2.1.2.2 BinaryTypeEnumeration
,并使用String
表示。
LengthPrefixedString
代表第二个成员的00
,同样,根据规范,它是BinaryTypeEnumeration
。如上所述,Primitive
之后是其他信息,在本例中为Primitive
。这就是为什么我们需要读取PrimitiveTypeEnumeration
的下一个字节,使其与08
中所述的表格相匹配,并惊讶地发现我们可以期待2.1.2.3 PrimitiveTypeEnumeration
由4个字节表示,如关于基本数据类型的其他文档中所述。
的库Id:强>
Int32
跟随MemerTypeInfo
之后,它由4个字节表示:
LibraryId
代表02 00 00 00
,即2。
值:
如LibraryId
中所述:
类的成员的值必须序列化为遵循此记录的记录,如2.7节所述。记录的顺序必须与ClassInfo(第2.3.1.1节)结构中指定的MemberNames的顺序相匹配。
这就是为什么我们现在可以期待成员的价值观。
让我们看看最后几个字节:
2.3 Class Records
标识06
。它代表我们BinaryObjectString
属性的值(确切地说SomeString
。)
根据<SomeString>k__BackingField
,它包含:
2.5.7 BinaryObjectString
)
所以知道这一点,我们可以清楚地确定
LengthPrefixedString
代表03 00 00 00
。
ObjectId
代表03 61 62 63
,其中Value
是字符串本身的长度,03
是转换为61 62 63
的内容字节。
希望你能记得有第二个成员abc
。知道使用4个字节表示Int32
,我们可以得出结论,
必须是我们第二个成员的Int32
。 Value
十六进制等于7B
十进制,似乎符合我们的示例代码。
所以这是完整的123
记录:
的 MessageEnd:强>
最后,最后一个字节ClassWithMembersAndTypes
代表0B
记录。
答案 2 :(得分:3)
如果我没弄错,二进制序列化程序会转储有关对象类型名称和命名空间的一些信息。如果这些值与原始类类型和新的“SomeDataFormat”不同,则可以解释大小差异。
您是否尝试过使用十六进制编辑器比较这两个文件?
答案 3 :(得分:2)
当你进行反序列化时,有些东西会升级就好了。例如
public class SomeClass()
{
public short SomeProperty {get;set;}
}
将反序列化为
public class SomeClass()
{
public long SomeProperty {get;set;}
}
但是如果序列化第二个SomeClass(即带有long的那个),它将导致不同的大小,SomeClass的序列化为short。在这种特殊情况下为6个字节。
更新
反序列化为通用对象,然后使用反射来获取类型。您可能需要对复杂对象进行递归和特殊处理。
using (var fileStream = new FileStream("TestFormatter.dat", FileMode.Open))
{
var binaryFormatter = new BinaryFormatter();
var myObject = binaryFormatter.Deserialize(fileStream);
var objectProperties = myObject.GetType().GetProperties();
foreach (var property in objectProperties)
{
var propertyTypeName = property.PropertyType.Name; //This will tell you the property Type Name. I.e. string, int64 (long)
}
}
答案 4 :(得分:1)
剩下的差异可能来自您班级缺少的属性。试试这个:
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class SomeDataFormat // 16 field
{
...