【问题标题】:Serialized .NET class to PHP将 .NET 类序列化为 PHP
【发布时间】:2015-05-27 01:12:21
【问题描述】:

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

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

我意识到这实际上是二进制而不是可打印的文本。我只是用这个作为我在 catting 文件时看到的一个例子。

【问题讨论】:

  • 你能分享你的尝试吗? this 有帮助吗?
  • 将源代码更改为仅使用标准化格式不会比尝试找出that 序列化更简单吗?除非你具体化上下文或研究,否则有人会用“使用 unpack()”来回答这个问题。
  • @mario,是的,标准化格式会更好,但我无法控制。是的,我认为 unpack 将是要走的路,但我不知道序列化的二进制字符串格式开始走这条路。我也用谷歌搜索过,但没有找到任何有意义的东西。
  • @JayBlanchard,我在谷歌上一无所获。好吧,没有什么是显而易见的。但这不是我要说的。它是一个 .NET 或 C# 序列化对象类。有点像 PHP 的 serialize() 函数,因为它对数组或对象做同样的事情。
  • 我建议编写一个 .net 应用程序来处理转换为更可用的格式(例如 json),并通过 exec 从 php 调用它

标签: c# php .net type-conversion converter


【解决方案1】:

简答:

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





长答案:

但是,如果这是不可能的,当然有办法......

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

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



示例类:

为了有一个工作示例,我创建了一个名为 A 的简单类,其中包含 2 个属性,一个字符串和一个整数值,它们分别称为 SomeStringSomeValue

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 除外)都以记录类型枚举开头。枚举的大小为 1 BYTE。



SerializationHeaderRecord:

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

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

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

它包括:

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



有了这些知识,我们就可以解释包含 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



二进制库:

按照规定,每条记录必须以RecordTypeEnumeration 开头。当最后一个记录完成时,我们必须假设一个新的记录开始了。

让我们解释下一个字节:

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

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

它包括:

  • RecordTypeEnum(1 字节)
  • LibraryId(4 字节)
  • LibraryName(可变字节数(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的长度信息,其中包含LibraryName

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

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



ClassWithMembersAndTypes:

再一次,记录是完整的,所以我们解释下一个的RecordTypeEnumeration

05 标识ClassWithMembersAndTypes 记录。 2.3.2.1 ClassWithMembersAndTypes 部分声明:

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

它包括:

  • RecordTypeEnum(1 字节)
  • ClassInfo(可变字节数)
  • MemberTypeInfo(可变字节数)
  • LibraryId(4 字节)



类信息:

2.3.1.1 ClassInfo 中所述,记录包括:

  • ObjectId(4 字节)
  • 名称(可变字节数(同样是LengthPrefixedString))
  • 成员计数(4 字节)
  • MemberNames(这是LengthPrefixedString 的序列,其中项目数必须等于MemberCount 字段中指定的值。)



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

01 00 00 00 代表ObjectId。我们已经看到了这个,它在SerializationHeaderRecord 中被指定为RootId

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

02 00 00 00 代表MemberCount,它告诉我们有 2 个成员,都以 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 代表第一个 MemberName1B 又是字符串的长度,长度为 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代表第二个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 值(因为我们在 MemberNames 中有 2 个成员)。



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

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

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



图书馆 ID:

MemerTypeInfo之后跟LibraryId,用4个字节表示:

02 00 00 00 代表LibraryId,即 2。



价值观:

2.3 Class Records中指定:

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

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

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

06 标识一个BinaryObjectString。它代表我们SomeString 属性的值(准确地说是<SomeString>k__BackingField)。

根据2.5.7 BinaryObjectString,它包含:

  • RecordTypeEnum(1 字节)
  • ObjectId(4 字节)
  • 值(可变长度,表示为LengthPrefixedString



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

03 00 00 00 代表ObjectId

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

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

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

这是完整的ClassWithMembersAndTypes 记录:



消息结束:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多