我所有的研究都基于.NET Remoting: Binary Format Data Structure规范。
实例类:
为了有一个有效的例子,我创建了一个名为A它包含两个属性,一个字符串和一个整数值,它们被调用SomeString和SomeValue.
等级一如下所示:
[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();
可以看到,我传递了一个新的类实例一包含abc和123作为价值。
示例结果数据:
如果我们在十六进制编辑器中查看序列化结果,会得到如下结果:

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

如上所述2.1.2.1 RecordTypeEnumeration一个值0标识SerializationHeaderRecord在2.6.1 SerializationHeaderRecord:
序列化HeaderRecord记录必须是二进制序列化中的第一个记录。此记录具有格式的主版本和次版本,以及顶部对象和标题的ID。
它包括:
- 记录类型枚举(1字节)
- rootid(4字节)
- HeaderID(4字节)
- 主版本(4字节)
- MinorVersion(4字节)
利用这些知识,我们可以解释包含17个字节的记录:

00代表记录类型枚举哪个是序列化标题记录在我们的例子中。
01 00 00 00代表RootId
如果序列化流中既不存在BinaryMethodCall也不存在BinaryMethodReturn记录,则此字段的值必须包含序列化流中包含的类、数组或BinaryObjectString记录的ObjectId。
所以在我们的例子中,这应该是ObjectId用价值1(因为数据是用小endian序列化的),我们希望能再次看到;-)
FF FF FF FF代表HeaderId
01 00 00 00代表MajorVersion
00 00 00 00代表MinorVersion
BinaryLibrary:
按照规定,每个记录必须以记录类型枚举. 当最后一个记录完成时,我们必须假设一个新的记录开始。
让我们解释下一个字节:

如我们所见,在我们的示例中,序列化标题记录后面是BinaryLibrary记录:
binarylibrary记录将一个int32 id(如[ms-dtyp]第2.2.22节中指定的)与一个库名相关联。这允许其他记录使用ID引用库名称。当有多个记录引用同一库名称时,此方法会减小线大小。
它包括:
- 记录类型枚举(1字节)
- 库ID(4字节)
- libraryname(可变字节数
LengthPrefixedString)
如上所述2.1.1.6 LengthPrefixedString…
lengthPrefixedString表示字符串值。字符串的前缀是以字节为单位的utf-8编码字符串的长度。长度编码在可变长度字段中,最小为1字节,最大为5字节。为了最小化导线尺寸,长度被编码为可变长度字段。
在我们的简单示例中,长度总是使用1 byte. 有了这些知识,我们可以继续解释流中的字节:

0C代表记录类型枚举它可以识别二进制库记录。
02 00 00 00代表LibraryId哪个是2在我们的例子中。
现在长度前缀字符串跟随:

42表示的长度信息长度前缀字符串其中包含LibraryName.
在我们的例子中,长度信息四十二(十进制66)告诉我们,我们需要读取接下来的66个字节,并将其解释为图书馆名称.
如前所述,字符串是UTF-8已编码,因此上面字节的结果类似于:_WorkSpace_, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
用成员和类型分类:
同样,记录是完整的,所以我们解释记录类型枚举下一个:

05标识一个ClassWithMembersAndTypes记录。截面2.3.2.1 ClassWithMembersAndTypes国家:
ClassWithMembersandTypes记录是类记录中最详细的记录。它包含有关成员的元数据,包括成员的名称和远程处理类型。它还包含引用类的库名称的库ID。
它包括:
- 记录类型枚举(1字节)
- classinfo(可变字节数)
- membertypeinfo(可变字节数)
- 库ID(4字节)
ClassInfo:
如上所述2.3.1.1 ClassInfo记录包括:
- 对象ID(4字节)
- 名称(可变字节数
长度前缀字符串)
- 成员计数(4字节)
- 成员名(是
长度前缀字符串其中的项数必须等于MemberCount字段)
回到原始数据,一步一步:

01 00 00 00代表客体. 我们已经看到了这个,它被指定为腮腺在序列化标题记录.

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

02 00 00 00代表成员计数,它告诉我们,两个成员,都代表长度前缀字符串我们会跟进的。
第一个成员的名称:

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表示第二个成员姓名,1A指定字符串的长度为26个字节。结果是这样的:<SomeValue>k__BackingField.
MemberTypeInfo:
后ClassInfo这个MemberTypeInfo跟随。
截面2.3.1.2 - MemberTypeInfo声明结构包含:
表示正在传输的成员类型的BinaryTypeEnumeration值的序列。数组必须:
- 附加信息(长度可变),取决于
BinaryTpeEnum其他信息可能存在,也可能不存在。
| BinaryTypeEnum | AdditionalInfos |
|----------------+--------------------------|
| Primitive | PrimitiveTypeEnumeration |
| String | None |
考虑到这一点,我们就快到了…
我们期待2BinaryTypeEnumeration价值观(因为我们在MemberNames)
再次,回到原始数据的完整成员类型信息记录:

01代表二进制类型枚举根据2.1.2.2 BinaryTypeEnumeration我们可以期待String它用一个长度前缀字符串.
00代表二进制类型枚举对于第二个构件,根据规范,它是Primitive. 如上所述,本原后面是附加信息,在本例中是PrimitiveTypeEnumeration. 这就是为什么我们需要读取下一个字节,也就是08,将其与中所述的表匹配2.1.2.3 PrimitiveTypeEnumeration当我们发现Int32它由4个字节表示,如其他一些关于基本数据类型的文档所述。
LibraryId:
后MemerTypeInfo这个图书馆ID下面,它由4个字节表示:

02 00 00 00代表图书馆ID哪一个是2。
价值观:
如规定2.3 Class Records:
类成员的值必须序列化为该记录后面的记录,如第2.7节所述。记录的顺序必须与ClassInfo(第2.3.1.1节)结构中指定的成员名顺序匹配。
这就是为什么我们现在可以期待成员的价值观。
让我们看看最后几个字节:

06标识一个BinaryObjectString. 它代表了我们的价值弦乐财产<somestring>k_uu后退场确切地说)
根据2.5.7 BinaryObjectString它包含:
- 记录类型枚举(1字节)
- 对象ID(4字节)
- 值(可变长度,表示为
长度前缀字符串)
所以知道了这一点,我们就可以清楚地确定

03 00 00 00代表客体.
03 61 62 63代表Value哪里03是字符串本身的长度,并且61 62 63是转换为abc.
希望你能记得有第二个成员英特32. 知道英特32用4个字节表示,我们可以得出结论:

必须是价值我们的第二个成员。7B十六进制等于一百二十三十进制似乎适合我们的示例代码。
所以这是完整的与成员和类型分类记录:

MessageEnd:

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