6

我正在尝试读取二进制序列化对象,我没有它的对象定义/源。我在文件中看到了一个高峰并看到了属性名称,所以我手动重新创建了该对象(我们称之为它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 字节。所以有3个字节的差异。我应该得到与原始文件大小相同的文件大小。

查看原始文件和新的(重新序列化的)文件:

原始序列化文件:(zoom) 在此处输入图像描述重新序列化文件 :(zoom) 在此处输入图像描述

如您所见,在标题部分之后,数据似乎以不同的顺序排列。例如,您可以看到电子邮件和 sessionID 不在同一个地方。

更新:Will 警告我,“PublicKeyToken=null”之后的字节也不同。(03 <-> 05)

  • Q1:为什么两个文件中的值顺序不同?
  • Q2:为什么两个序列化对象相比多了 3 个字节?
  • Q3:我错过了什么?我怎么能这样做?

任何帮助表示赞赏。


相关问题类型: 1 2 3

4

5 回答 5

8

为什么两个文件中的值顺序不同?

那是因为成员顺序不是基于声明顺序的。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; }
}

我错过了什么?我怎么能这样做?

我看不到使用给定的 BinaryFormatter 的方法。您可以反编译/反转 BinaryFormatter 的工作方式。

于 2013-08-09T14:43:10.910 回答
5

因为我决定写这篇文章的人可能会感兴趣,关于序列化 .NET 对象的二进制格式是什么样的以及我们如何正确解释它?

我的所有研究都基于.NET Remoting: Binary Format Data Structure规范。



示例类:

为了有一个工作示例,我创建了一个名为的简单类A,它包含 2 个属性、一个字符串和一个整数值,它们被称为SomeStringand 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包含abc123作为值的类的新实例。



示例结果数据:

如果我们在十六进制编辑器中查看序列化结果,我们会得到如下内容:

示例结果数据



让我们解释一下示例结果数据:

根据上述规范(这里是 PDF 的直接链接:[MS-NRBF].pdf),流中的每条记录都由RecordTypeEnumeration. 部分2.1.2.1 RecordTypeNumeration指出:

此枚举标识记录的类型。每条记录(MemberPrimitiveUnTyped 除外)都以记录类型枚举开头。枚举的大小是一个字节。



序列化标头记录:

所以如果我们回顾我们得到的数据,我们可以开始解释第一个字节:

SerializationHeaderRecord_RecordTypeEnumeration

2.1.2.1 RecordTypeEnumeration值 中所述,0标识在SerializationHeaderRecord中指定2.6.1 SerializationHeaderRecord

SerializationHeaderRecord 记录必须是二进制序列化中的第一条记录。该记录具有格式的主要版本和次要版本以及顶部对象和标题的 ID。

它包括:

  • RecordTypeEnum (1 字节)
  • RootId(4 字节)
  • HeaderId(4 字节)
  • 主要版本(4 字节)
  • 次要版本(4 个字节)



有了这些知识,我们可以解释包含 17 个字节的记录:

SerializationHeaderRecord_Complete

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. 当最后一个记录完成时,我们必须假设一个新的记录开始了。

让我们解释下一个字节:

BinaryLibraryRecord_RecordTypeEnumeration

如我们所见,在我们的示例中,SerializationHeaderRecord它后面是BinaryLibrary记录:

BinaryLibrary 记录将 INT32 ID(如 [MS-DTYP] 第 2.2.22 节中指定)与库名称相关联。这允许其他记录通过使用 ID 来引用图书馆名称。当有多个记录引用同一个库名称时,这种方法会减小连线大小。

它包括:

  • RecordTypeEnum (1 字节)
  • 库 ID(4 个字节)
  • LibraryName(可变字节数(即 a LengthPrefixedString))



2.1.1.6 LengthPrefixedString...中所述

LengthPrefixedString 表示一个字符串值。该字符串以 UTF-8 编码字符串的长度为前缀(以字节为单位)。长度编码在一个可变长度字段中,最小为 1 个字节,最大为 5 个字节。为了最小化线路尺寸,长度被编码为可变长度字段。

在我们的简单示例中,长度始终使用 编码1 byte。有了这些知识,我们可以继续解释流中的字节:

BinaryLibraryRecord_RecordTypeEnumeration_LibraryId

0C表示RecordTypeEnumeration标识BinaryLibrary记录的。

02 00 00 00代表我们的LibraryId情况2



现在LengthPrefixedString如下:

BinaryLibraryRecord_RecordTypeEnumeration_LibraryId_LibraryName

42LengthPrefixedString表示包含 的的长度信息LibraryName

在我们的例子中,(十进制 66)的长度信息42告诉我们,我们需要读取接下来的 66 个字节并将它们解释为LibraryName.

如前所述,字符串是经过UTF-8编码的,因此上面字节的结果将类似于:_WorkSpace_, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null



ClassWithMembersAndTypes:

同样,记录是完整的,所以我们解释RecordTypeEnumeration下一个:

ClassWithMembersAndTypesRecord_RecordTypeEnumeration

05标识一条ClassWithMembersAndTypes记录。部分2.3.2.1 ClassWithMembersAndTypes指出:

ClassWithMembersAndTypes 记录是最详细的 Class 记录。它包含有关成员的元数据,包括成员的名称和远程处理类型。它还包含一个引用类的库名称的库 ID。

它包括:

  • RecordTypeEnum (1 字节)
  • ClassInfo(可变字节数)
  • MemberTypeInfo(可变字节数)
  • 库 ID(4 个字节)



类信息:

2.3.1.1 ClassInfo记录所述,包括:

  • 对象 ID(4 个字节)
  • 名称(可变字节数(又是 a LengthPrefixedString))
  • 成员计数(4 字节)
  • MemberNames(这是一个LengthPrefixedString's 的序列,其中项目的数量必须等于MemberCount字段中指定的值。)



回到原始数据,一步一步:

ClassWithMembersAndTypesRecord_RecordTypeEnumeration_ClassInfo_ObjectId

01 00 00 00代表ObjectId. 我们已经看到了这个,它被指定RootIdSerializationHeaderRecord.

ClassWithMembersAndTypesRecord_RecordTypeEnumeration_ClassInfo_ObjectId_Name

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

ClassWithMembersAndTypesRecord_RecordTypeEnumeration_ClassInfo_ObjectId_Name_MemberCount

02 00 00 00代表MemberCount,它告诉我们 2 个成员,都用LengthPrefixedString's 代表将跟随。

第一位成员姓名: ClassWithMembersAndTypesRecord_MemberNameOne

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代表第一个MemberName1B再次是字符串的长度,长度为 27 个字节,结果如下<SomeString>k__BackingField

第二位成员姓名: ClassWithMembersAndTypesRecord_MemberNameTwo

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表示第二个MemberName1A指定字符串的长度为 26 个字节。结果是这样的:<SomeValue>k__BackingField.



成员类型信息:

之后ClassInfo如下MemberTypeInfo

部分2.3.1.2 - MemberTypeInfo指出,该结构包含:

  • BinaryTypeEnums(长度可变)

表示正在传输的成员类型的 BinaryTypeEnumeration 值序列。阵列必须:

  • 具有与 ClassInfo 结构的 MemberNames 字段相同数量的项目。

  • 进行排序,使 BinaryTypeEnumeration 对应于 ClassInfo 结构的 MemberNames 字段中的成员名称。

  • AdditionalInfos(长度可变),取决于BinaryTpeEnum附加信息可能存在也可能不存在。

| BinaryTypeEnum | AdditionalInfos |
|----------------+--------------------------|
| Primitive | PrimitiveTypeEnumeration |
| String | None |

所以考虑到这一点,我们几乎就在那里......我们期望 2 个BinaryTypeEnumeration值(因为我们在 2 个成员中有 2 个成员MemberNames)。



再次,回到完整MemberTypeInfo记录的原始数据:

ClassWithMembersAndTypesRecord_MemberTypeInfo

01表示BinaryTypeEnumeration第一个成员的 ,根据2.1.2.2 BinaryTypeEnumeration我们可以预期的 a String,它使用 a 表示LengthPrefixedString

00表示BinaryTypeEnumeration第二个成员的 ,同样,根据规范,它是Primitive. 如上所述,Primitive's 后面是附加信息,在本例中为 a PrimitiveTypeEnumeration。这就是为什么我们需要读取下一个字节,也就是08将其与表中所述的表相匹配,2.1.2.3 PrimitiveTypeEnumeration并惊讶地发现我们可以预期一个Int32由 4 个字节表示的字节,如其他一些关于基本数据类型的文档中所述。



图书馆编号:

以下之后MemerTypeInfoLibraryId用4个字节表示:

ClassWithMembersAndTypesRecord_LibraryId

02 00 00 00表示LibraryId哪个是 2。



价值:

如中所述2.3 Class Records

类成员的值必须序列化为该记录之后的记录,如第 2.7 节所述。记录的顺序必须与 ClassInfo(第 2.3.1.1 节)结构中指定的 MemberNames 的顺序相匹配。

这就是为什么我们现在可以期待成员的价值观。

让我们看看最后几个字节:

BinaryObjectStringRecord_RecordTypeEnumeration

06标识一个BinaryObjectString. 它代表我们SomeString财产的价值(<SomeString>k__BackingField准确地说)。

2.5.7 BinaryObjectString它包含:

  • RecordTypeEnum (1 字节)
  • 对象 ID(4 个字节)
  • 值(可变长度,表示为 a LengthPrefixedString



所以知道这一点,我们可以清楚地识别出

BinaryObjectStringRecord_RecordTypeEnumeration_ObjectId_MemberOneValue

03 00 00 00代表ObjectId.

03 61 62 63表示Value其中03是字符串本身的长度,61 62 63是转换为的内容字节abc

希望您能记得还有第二个成员,一个Int32. 知道Int32是用 4 个字节表示的,我们可以得出结论,

BinaryObjectStringRecord_RecordTypeEnumeration_ObjectId_MemberOneValue_MemberTwoValue

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

所以这里是完整的ClassWithMembersAndTypes记录: ClassWithMembersAndTypesRecord_Complete



留言结束:

MessageEnd_RecordTypeEnumeration

最后一个字节0B代表MessageEnd记录。

于 2016-11-04T10:08:23.817 回答
3

如果我没记错的话,二进制序列化程序会转储一些有关对象类型名称和命名空间的信息。如果这些值与原始类类型和您的新“SomeDataFormat”不同,它可能会解释大小差异。

您是否尝试过将这两个文件与十六进制编辑器进行比较?

于 2013-08-01T14:41:57.097 回答
2

当您进行反序列化时,有些事情会很好。例如

public class SomeClass()
{
   public short SomeProperty {get;set;}
}

将反序列化为

public class SomeClass()
{
   public long SomeProperty {get;set;}
}

但是,如果您序列化第二个 SomeClass(即具有 long 的那个),它将导致与带有 short 的 SomeClass 的序列化不同的大小。在这种特殊情况下为 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)
            }                
        }
于 2013-08-01T15:01:52.883 回答
1

其余的差异可能是由于您的班级缺少属性。试试这个:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public class SomeDataFormat // 16 field
{
   ...
于 2013-08-12T06:41:39.427 回答