Protobuf-net 不会反序列化来自 Protobuf.js 的数据

问题描述 投票:0回答:2

我使用 Protobuf 通过 WebSocket 在 Web 客户端和服务器 (C#) 之间进行通信。在客户端,反/序列化是通过 Protobuf.js 完成的,在服务器上,使用 protobuf-net 完成。

问题是,当使用抽象类的聚合时,protobuf-net无法反序列化Protobuf.js发送的数据。

这是堆栈跟踪:

ProtoException: No parameterless constructor found for Base.
at ProtoBuf.Meta.TypeModel.ThrowCannotCreateInstance(Type type) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 1397
at proto_6(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.ProtoReader.ReadTypedObject(Object value, Int32 key, ProtoReader reader, Type type) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 579
at ProtoBuf.ProtoReader.ReadObject(Object value, Int32 key, ProtoReader reader) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 566
at proto_2(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader, Type type, Object value, Boolean noAutoCreate) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 700
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type, SerializationContext context) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 589
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 566
at ProtoBuf.Serializer.Deserialize[T](Stream source) na c:\Dev\protobuf-net\protobuf-net\Serializer.cs:line 77
at ProtobufPolymorphismTest.Program.Main(String[] args) na c:\Desenvolvimento\Testes\ProtobufPolymorphismTest\ProtobufPolymorphismTest\Program.cs:line 30
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

这是合同:

[ProtoContract]
[ProtoInclude(100, typeof(Child))]
abstract class Base
{
    [ProtoMember(1)]
    public int BaseProperty { get; set; }
}

[ProtoContract]
class Child : Base
{
    [ProtoMember(1)]
    public float ChildProperty { get; set; }
}

[ProtoContract]
class Request
{
    [ProtoMember(1)]
    public Base Aggregate { get; set; }
}

这是重现错误的代码。当序列化正在进行时,我仅以字节数组形式提供结果。如果有帮助,我可以提供获取序列化值所采取的步骤。

// This is the object serialized
Child child = new Child() { ChildProperty = 0.5f, BaseProperty = 10 };
Request request = new Request() { Aggregate = child };

// This is the byte representation generated by protobuf-net and Protobuf.js
byte[] protoNet = new byte[] { 10, 10, 162, 6, 5, 13, 0, 0, 0, 63, 8, 10 };
byte[] protoJS = new byte[] { 10, 10, 8, 10, 162, 6, 5, 13, 0, 0, 0, 63 };

// Try to deserialize the protobuf-net data
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(protoNet))
{
    request = Serializer.Deserialize<Request>(ms); // Success
}

// Try to deserialize the Protobuf.js data
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(protoJS))
{
    request = Serializer.Deserialize<Request>(ms); // ProtoException: No parameterless constructor found for Base.
}

如果我在基类定义上添加 SkipConstructor = true,错误将更改为“MemberAccessException:无法创建抽象类”,并显示以下堆栈跟踪。如果我从基类定义中删除抽象,它就会按预期工作。

System.MemberAccessException: Cannot create an abstract class.
at System.Runtime.Serialization.FormatterServices.nativeGetUninitializedObject(RuntimeType type)
at ProtoBuf.BclHelpers.GetUninitializedObject(Type type) na c:\Dev\protobuf-net\protobuf-net\BclHelpers.cs:line 38
at proto_6(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.ProtoReader.ReadTypedObject(Object value, Int32 key, ProtoReader reader, Type type) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 579
at ProtoBuf.ProtoReader.ReadObject(Object value, Int32 key, ProtoReader reader) na c:\Dev\protobuf-net\protobuf-net\ProtoReader.cs:line 566
at proto_2(Object , ProtoReader )
at ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Serializers\CompiledSerializer.cs:line 57
at ProtoBuf.Meta.RuntimeTypeModel.Deserialize(Int32 key, Object value, ProtoReader source) na c:\Dev\protobuf-net\protobuf-net\Meta\RuntimeTypeModel.cs:line 775
at ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader, Type type, Object value, Boolean noAutoCreate) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 700
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type, SerializationContext context) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 589
at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type) na c:\Dev\protobuf-net\protobuf-net\Meta\TypeModel.cs:line 566
at ProtoBuf.Serializer.Deserialize[T](Stream source) na c:\Dev\protobuf-net\protobuf-net\Serializer.cs:line 77
at ProtobufPolymorphismTest.Program.Main(String[] args) na c:\Desenvolvimento\Testes\ProtobufPolymorphismTest\ProtobufPolymorphismTest\Program.cs:line 30
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

我不确定为什么通过 protobuf-net 和 Protobuf.js 生成的二进制表示不同,但它们似乎都是有效的,因为如果基类不是抽象的,它就可以工作。

关于为什么会发生这种情况或在不从基类中删除抽象的情况下解决问题的方法有什么想法吗?

提前致谢!

更新

这是我用来通过 Protobuf.js 生成字节序列化的代码:

<script src="//raw.githubusercontent.com/dcodeIO/ByteBuffer.js/master/dist/ByteBufferAB.min.js"></script>
<script src="//cdn.rawgit.com/dcodeIO/ProtoBuf.js/master/dist/ProtoBuf.js"></script>
<script type="text/javascript">
    // Proto file
    var proto = "";
    proto += "package ProtobufPolymorphismTest;\r\n\r\n";
    proto += "message Base {\r\n";
    proto += "    optional int32 BaseProperty = 1 [default = 0];\r\n";
    proto += "    // the following represent sub-types; at most 1 should have a value\r\n";
    proto += "    optional Child Child = 100;\r\n";
    proto += "}\r\n\r\n";
    proto += "message Child {\r\n";
    proto += "    optional float ChildProperty = 1 [default = 0];\r\n";
    proto += "}\r\n\r\n";
    proto += "message Request {\r\n";
    proto += "    optional Base Aggregate = 1;\r\n";
    proto += "}";

    // Build the entities
    var protoFile = dcodeIO.ProtoBuf.loadProto(proto);
    var requestClass = protoFile.build("ProtobufPolymorphismTest.Request");
    var baseClass = protoFile.build("ProtobufPolymorphismTest.Base");
    var childClass = protoFile.build("ProtobufPolymorphismTest.Child");

    // Build the request
    var base = new baseClass();
    base.BaseProperty = 10;
    base.Child = new childClass();
    base.Child.ChildProperty = 0.5;
    var request = new requestClass();
    request.Aggregate = base;

    // Serialize
    var bytes = new Uint8Array(request.toArrayBuffer());
    var str = "new byte[] { " + bytes.join(", ") + " };";
    console.log(str);
</script>

解决方法

正如 Marc 所解释的,当字段顺序反转时,protobuf-net 不支持多态性。作为特定于 Protobuf.js 的解决方法,您可以更改 .proto 文件中字段的顺序,以使其按正确的顺序序列化。

就我而言,将 .proto 文件更改为以下内容解决了问题:

package ProtobufPolymorphismTest;

message Base {
   // the following represent sub-types; at most 1 should have a value
   optional Child Child = 100;
   optional int32 BaseProperty = 1 [default = 0];
}
message Child {
   optional float ChildProperty = 1 [default = 0];
}
message Request {
   optional Base Aggregate = 1;
}

(注意

optional Child Child = 100;
之前
BaseProperty

javascript .net protocol-buffers protobuf-net
2个回答
1
投票

总而言之,protobuf-net 的多态性支持期望子类型是消息中的“first”(或者更具体地说:对于任何对象,类型将在提供数据之前固定)。在 js 输出中,BaseProperty 的字段数据出现在

first
- 也许是完全合理的。但由于没有继承应该如何表现的总体协议定义,protobuf-net 的实现实际上只是为了与自身一起工作。就字节而言,这实际上归结为字段标记“162, 6”(以及相关的长度/数据“5, 13, 0, 0, 0, 63”)出现的位置。 该库

可能

可能会被重新设计以允许多态性的任何字段顺序,但是:这需要一些努力。我知道“通常”预计会以任何顺序处理字段,但由于这已经超出了规范,因此我没有关注这一点。所有其他数据字段都以任何顺序接受 - 只有多态性才以这种方式工作。 在一般情况下:因为多态性不是规范的一部分,我强烈建议在库之间工作时避免多态性。

注意:您可以通过确保多态性字段低于(数字)比数据字段来强制其工作。

对于遇到同样问题的人,请查看此 npm 包

https://www.npmjs.com/package/bcl-guid-proto

0
投票

它基本上提供了使用 protonul-net 序列化的 .NET GUID 和使用 protobufjs 序列化的 GUID 之间的互操作,也可能是其他库。

© www.soinside.com 2019 - 2024. All rights reserved.