代码之家  ›  专栏  ›  技术社区  ›  shrimpwagon

将.NET类序列化为PHP

  •  3
  • shrimpwagon  · 技术社区  · 10 年前

    我正在从源获取一些序列化的.NET类字符串数据,我只需要将其转换为PHP中可读的内容。不一定要转换为“对象”或JSON,但我需要以某种方式读取它。我认为.NET字符串只是一个具有一些设置属性的类,但它是二进制的,显然不可移植。我不想将.NET代码转换为PHP代码。以下是数据示例:

    U:?�S�@��-��v�Y��?������An�@AMAUI������
    

    我意识到这实际上是二进制的,不是可打印的文本。我只是用这个例子来说明我在传送文件时看到的情况。

    1 回复  |  直到 10 年前
        1
  •  8
  •   Community CDub    4 年前

    简短回答:

    我真的建议不要自己实现二进制表示的解释。我会使用另一种格式( JSON , XML 等等)。





    长答案:

    然而,如果这不可能,当然有一种方法。。。

    实际问题是: 序列化.NET对象的二进制格式是什么样的?我们如何正确解释它?

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



    示例类:

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



    示例结果数据:

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

    Example result data



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

    根据上述规范(以下是PDF的直接链接: [MS-NRBF].pdf )流中的每个记录都由 RecordTypeEnumeration 部分 2.1.2.1 RecordTypeNumeration 状态:

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



    SerializationHeaderRecord:

    因此,如果我们回头看我们得到的数据,我们可以开始解释第一个字节:

    SerializationHeaderRecord_RecordTypeEnumeration

    如中所述 2.1.2.1 RecordTypeEnumeration 值为 0 识别 SerializationHeaderRecord 2.6.1 SerializationHeaderRecord :

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

    它包括:

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



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

    SerializationHeaderRecord_Complete

    00 代表 记录类型枚举 这是 序列化标头记录 在我们的案例中。

    01 00 00 00 代表 RootId

    如果序列化流中既不存在BinaryMethodCall记录,也不存在Binary MethodReturn记录,则此字段的值必须包含序列化流中包含的类、数组或BinaryObjectString记录的ObjectId。

    所以在我们的案例中 ObjectId 1 (因为数据是使用little-endian序列化的),我们希望再次看到;-)

    FF FF FF FF 代表 HeaderId

    01 00 00 00 代表 MajorVersion

    00 00 00 00 代表 MinorVersion



    二进制库:

    按照规定,每条记录必须以 记录类型枚举 。最后一个记录完成后,我们必须假设新的记录开始了。

    让我们解释下一个字节:

    BinaryLibraryRecord_RecordTypeEnumeration

    如我们所见,在我们的示例中 序列化标头记录 之后是 BinaryLibrary 记录:

    BinaryLibrary记录将INT32 ID(如[MS-DTYP]第2.2.22节所述)与库名称相关联。这允许其他记录使用ID引用库名称。当有多个记录引用同一个库名称时,此方法可减小导线大小。

    它包括:

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



    如中所述 2.1.1.6 LengthPrefixedString ...

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

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

    BinaryLibraryRecord_RecordTypeEnumeration_LibraryId

    0C 代表 记录类型枚举 它标识 二进制库 记录

    02 00 00 00 代表 LibraryId 这是 2 在我们的案例中。



    现在 长度前缀字符串 跟随:

    BinaryLibraryRecord_RecordTypeEnumeration_LibraryId_LibraryName

    42 表示 长度前缀字符串 其中包含 LibraryName .

    在本例中 42 (十进制66)告诉我们,我们需要读取接下来的66个字节,并将它们解释为 库名称 .

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



    具有成员和类型的类:

    同样,记录是完整的,因此我们解释 记录类型枚举 下一个:

    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字节)
    • 名称(可变字节数(也是 长度前缀字符串 ))
    • 成员计数(4字节)
    • MemberNames(它是 长度前缀字符串 的项目数必须等于 MemberCount 字段。)



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

    ClassWithMembersAndTypesRecord_RecordTypeEnumeration_ClassInfo_ObjectId

    01 00 00 00 代表 对象ID 。我们已经看到了这个,它被指定为 根ID 序列化标头记录 .

    ClassWithMembersAndTypesRecord_RecordTypeEnumeration_ClassInfo_ObjectId_Name

    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 作为命名空间的名称。

    ClassWithMembersAndTypesRecord_RecordTypeEnumeration_ClassInfo_ObjectId_Name_MemberCount

    02 00 00 00 代表 成员计数 ,它告诉我们,2名成员,均代表 长度前缀字符串 的。

    第一位成员的姓名: 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 代表第一个 MemberName , 1B 同样是字符串的长度,长度为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 代表第二个 成员名称 , 1A 指定字符串长度为26字节。结果如下: <SomeValue>k__BackingField .



    成员类型信息:

    ClassInfo 这个 MemberTypeInfo 跟随。

    部分 2.3.1.2 - MemberTypeInfo 声明,该结构包含:

    • BinaryTypeEnum(长度可变)

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

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

    • 进行排序,使BinaryTypeEnumeration与ClassInfo结构的MemberNames字段中的Member名称相对应。

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

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

    所以考虑到这一点,我们就快到了。。。 我们期望2 BinaryTypeEnumeration 值(因为我们在 MemberNames ).



    再次,回到完整的原始数据 成员类型信息 记录:

    ClassWithMembersAndTypesRecord_MemberTypeInfo

    01 代表 BinaryType枚举 根据 2.1.2.2 BinaryTypeEnumeration 我们可以期待 String 并且使用 长度前缀字符串 .

    00 代表 BinaryType枚举 再次,根据规范,它是 Primitive 如上所述, 原始的 的后面是附加信息,在本例中是 PrimitiveTypeEnumeration 。这就是为什么我们需要读取下一个字节,即 08 ,将其与 2.1.2.3 PrimitiveTypeEnumeration 并惊讶地注意到,我们可以期待 Int32 如关于基本数据类型的一些其他文档中所述。



    库ID:

    MemerTypeInfo 这个 库ID 下面,它由4个字节表示:

    ClassWithMembersAndTypesRecord_LibraryId

    02 00 00 00 代表 库ID 其为2。



    值:

    2.3 Class Records :

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

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

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

    BinaryObjectStringRecord_RecordTypeEnumeration

    06 识别 BinaryObjectString 。它代表了我们的价值 SomeString 属性( <SomeString>k_Backing字段 准确地说)。

    根据 2.5.7 BinaryObjectString 它包含:

    • RecordTypeEnum(1字节)
    • 对象ID(4字节)
    • 值(可变长度,表示为 长度前缀字符串 )



    因此,我们可以清楚地识别

    BinaryObjectStringRecord_RecordTypeEnumeration_ObjectId_MemberOneValue

    03 00 00 00 代表 对象ID .

    03 61 62 63 代表 Value 哪里 03 是字符串本身的长度 61 62 63 是转换为的内容字节 abc .

    希望你能记得有第二个成员 国际32 。知道 国际32 通过使用4个字节表示,我们可以得出如下结论:

    BinaryObjectStringRecord_RecordTypeEnumeration_ObjectId_MemberOneValue_MemberTwoValue

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

    所以这里是完整的 具有成员和类型的类 记录: ClassWithMembersAndTypesRecord_Complete



    消息结束:

    MessageEnd_RecordTypeEnumeration

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