我所有的研究都基于.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
记录。