Protobuf-net与官方谷歌Protobuf for C++(消息编码)不兼容

myW*_*SON 8 .net c# c++ protocol-buffers protobuf-net

我们在.NET中有一些(很多)类.我们使用protobuf-net来标记它们,并通过谷歌原始库为C++代码端生成.proto包装器.

所以我有一条消息(C++ DebugString()在一些EventBase类上(在.NET中EventCharacterMoved 继承,EventBase而在C++中我只是写入可选属性)):

UserId: -2792
EventCharacterMoved {
  Coordinates {
    Position {
      X: 196.41913
      Y: 130
      Z: 213
    }
    Rotation {
      X: 207
      Y: 130
      Z: 213
    }
  }
  OldCoordinates {
    Position {
      X: 196.41913
      Y: 130
      Z: 213
    }
    Rotation {
      X: 207
      Y: 130
      Z: 213
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

(来自这样的.proto文件)

message Coordinates {
   optional TreeFloat Position = 1;
   optional TreeFloat Rotation = 2;
}
message EventBase {
   optional int32 UserId = 10 [default = 0];
   // the following represent sub-types; at most 1 should have a value
   optional EventCharacterMoved EventCharacterMoved = 15;
}
message EventCharacterMoved {
   optional Coordinates Coordinates = 100;
   optional Coordinates OldCoordinates = 101;
}
message TreeFloat {
   optional float X = 1 [default = 0];
   optional float Y = 2 [default = 0];
   optional float Z = 3 [default = 0];
}
Run Code Online (Sandbox Code Playgroud)

在C++中,我发送了这个,我们从.NET发送相同的消息内容.

C++代码可以解析C++编码的消息以及.NET编码的消息..NET代码只能解析.NET消息.

通过线路,我们得到87字节飞行(相同大小来自.Net文件C++文件)但内容不同:

在此输入图像描述

你可以看到它的相似但不一样.由于这种差异,CPP代码可以读取.NET C#消息,而.NET无法读取CPP消息.

在反序列化的代码中,我们得到:

TestProto.exe中发生了未处理的"System.InvalidCastException"类型异常

附加信息:无法将"TestProto.EventBase"类型的对象强制转换为"TestProto.EventCharacterMoved".

在代码中:

using (var inputStream = File.Open(@"./cpp_in.bin", FileMode.Open, FileAccess.Read)) {
    var ecm = Serializer.Deserialize<EventCharacterMoved>(inputStream);
}
Run Code Online (Sandbox Code Playgroud)

让我们来看看(正如jpa在他的评论中提到的)protoc --decode_raw选项:

在此输入图像描述

这可能与我的CPP包装使用最新的google protobuf版本有关,而protobuf-net可能使用一些较旧的编码格式或类似的东西......

所以我想知道如何让.NET protobuf读取C++消息(使得能够解码相同的东西)?

或者至少如何使原始谷歌protobuf编码与.NET protobuf相同?

对于那些真正感兴趣并希望通过简化示例(包含C++和C#代码的VS 2010解决方案)进入拉链包的人

Mar*_*ell 2

编辑; 这应该在 r616 及更高版本中修复。


我终于有机会看看这个(对延迟表示歉意,但社交季节性假期要求介入)。我明白现在发生了什么。

基本上,数据在理论上是相同的;这实际上归结为字段排序。从技术上讲,字段通常按升序编写,但可以按任何顺序编写。关于 protobuf-net;对于不涉及继承的类型,无论顺序如何,它都可以正常工作。protobuf 规范没有定义继承,因此 protobuf-net在规范之外添加了对此的支持(由于不断的需求)。作为一个实现特性,它首先写入子类信息(即,子类型字段 15 写在字段 10 之前)。目前,在反序列化期间,它还首先期望子类型信息。这很少对任何人产生影响,因为由于 protobuf-net 是唯一使用这种继承的实现,因此继承功能的使用大多只在 protobuf-net 到 protobuf-net 的使用中看到。

就您而言,您正在使用 .proto 与 CPP 进行互操作;这意味着 CPP 代码将能够使用 protobuf-net 数据,但它可能会出现相反的类型转换异常(基本上,它在获取第一个数据字段时开始构造具体类型)。

尽管很少成为问题,但这是需要解决的问题。我可以尝试在今天晚些时候或明天看看这个。

选项:

  • 确保子类型字段始终低于任何数据字段
  • 如果您知道它需要子类型,请使用 Merge API 并传入所需类型的现有新对象 - 这将正确填充现有对象
  • 等待一两天(希望如此!)使用版本 r616 或更高版本进行正确修复
  • 使用互操作时避免继承(以及其他特定于实现的功能)
    • 请注意,您可以通过封装对相同的数据进行建模,而无需继承 - 并且它会正常工作;具体来说,具体类型的创建就是这里的问题
  • 在从 CPP 站点构建数据时,通过将其分成两部分,达到不合理的长度(意思是:我不认为这是一个实际的解决方案):
    • 首先只EventBase写入数据,然后序列化;现在在一个单独的模型中编写一个包含数据的模型,然后序列化;这将模拟按所需顺序写入它们(protobuf 流是可附加的)- 不太漂亮EventCharacterMovedEventBaseTreeFloat