使用自定义十进制原型合约(C#/C++ 互操作)时,Protobuf-net(反)序列化小数抛出

Posted

技术标签:

【中文标题】使用自定义十进制原型合约(C#/C++ 互操作)时,Protobuf-net(反)序列化小数抛出【英文标题】:Protobuf-net (de)serialization of decimals throws when using custom decimal proto contract (C#/C++ interop) 【发布时间】:2013-05-15 08:45:57 【问题描述】:

假设我要序列化,然后使用 protobuf-net 反序列化一个小数:

const decimal originalDecimal = 1.6641007661819458m;
using (var memoryStream = new MemoryStream())

    Serializer.Serialize(memoryStream, originalDecimal);
    memoryStream.Position = 0;
    var deserializedDecimal = Serializer.Deserialize<decimal>(memoryStream);
    Assert.AreEqual(originalDecimal, deserializedDecimal);

它工作正常。 Protobuf-net 在内部使用以下小数表示(参见Bcl.proto):

message Decimal 
  optional uint64 lo = 1; // the first 64 bits of the underlying value
  optional uint32 hi = 2; // the last 32 bis of the underlying value
  optional sint32 signScale = 3; // the number of decimal digits, and the sign

现在说我通过代码定义了一个假定等效的原型合约:

[ProtoContract]
public class MyDecimal

    [ProtoMember(1, IsRequired = false)]
    public ulong Lo;

    [ProtoMember(2, IsRequired = false)]
    public uint Hi;

    [ProtoMember(3, IsRequired = false)]
    public int SignScale;

...那么我不能序列化一个decimal 并返回一个MyDecimal,也不能序列化一个MyDecimal 并返回一个decimal

decimalMyDecimal

const decimal originalDecimal = 1.6641007661819458m;
using (var memoryStream = new MemoryStream())

    Serializer.Serialize(memoryStream, originalDecimal);
    memoryStream.Position = 0;

    // following line throws a Invalid wire-type ProtoException
    Serializer.Deserialize<MyDecimal>(memoryStream);

MyDecimaldecimal

var myDecimal = new MyDecimal

    Lo = 0x003b1ee886632642,
    Hi = 0x00000000,
    SignScale = 0x00000020,
;

using (var memoryStream = new MemoryStream())

    Serializer.Serialize(memoryStream, myDecimal);
    memoryStream.Position = 0;

    // following line throws a Invalid wire-type ProtoException
    Serializer.Deserialize<decimal>(memoryStream);

我在这里遗漏了什么吗?

我正在开发一个 C++ 应用程序,该应用程序需要通过协议缓冲区与 C# 进行通信,但不知道十进制反序列化失败的原因。

【问题讨论】:

【参考方案1】:

这是“它是一个对象?还是一个裸值?”的边缘情况。你不能只是在 protobuf 中序列化 int,你需要一个包装器对象。因此,对于裸值,它假装该值实际上是假设的包装对象的字段 1。但是,在decimal 的情况下,这有点棘手——因为decimal 实际上被编码为好像它是一个对象。所以技术上decimal可以写成一个裸值......但是:它看起来不是(它正在包装它) - 我怀疑在这个阶段纠正它是一个好主意。

基本上,如果不是序列化一个裸值,而是序列化一个 具有 值的 对象,这将更加可靠。它会更有效地工作(protobuf-net 查找它知道的类型,裸值非常适合备用方案)。例如:

[ProtoContract]
class DecimalWrapper 
    [ProtoMember(1)]
    public decimal Value  get; set; 

[ProtoContract]
class MyDecimalWrapper 
    [ProtoMember(1)]
    public MyDecimal Value  get; set; 

如果我们序列化这些,它们是 100% 可互换的:

const decimal originalDecimal = 1.6641007661819458m;
using (var memoryStream = new MemoryStream())

    var obj = new DecimalWrapper  Value = originalDecimal ;
    Serializer.Serialize(memoryStream, obj);
    // or, as it happens (see text) - this is equal to
    // Serializer.Serialize(memoryStream, originalDecimal);

    memoryStream.Position = 0;
    var obj2 = Serializer.Deserialize<MyDecimalWrapper>(memoryStream);
    Console.WriteLine("0, 1, 2",
        obj2.Value.Lo, obj2.Value.Hi, obj2.Value.SignScale);
    // ^^^ 16641007661819458, 0, 32

    memoryStream.SetLength(0);
    Serializer.Serialize(memoryStream, obj2);
    memoryStream.Position = 0;
    var obj3 = Serializer.Deserialize<DecimalWrapper>(memoryStream);

    bool eq = obj3.Value == obj.Value; // True

实际上,因为 protobuf-net 假装有一个对象,所以说Serialize&lt;decimal&gt; 将与Serialize&lt;MyDecimalWrapper&gt; 100% 兼容也是正确的,但为了您自己的理智,它可能是只是更容易坚持简单的“始终序列化 DTO 实例”方法,而不必考虑“这是 DTO?还是裸值?”


作为最后的想法:如果您使用互操作,我建议避免使用decimal,因为 protobuf 规范中没有定义,而且不同平台的“十进制”类型通常具有不同的含义。 protobuf-net 发明了一个含义,主要是为了允许 protobuf-net 往返(到自己)更广泛的 DTO,但是将这个值解析到任意平台可能会很尴尬。在跨平台工作并使用decimal 时,我建议考虑 诸如double/float 之类的东西,或者通过long/ulong 进行一些固定精度,或者甚至只是@987654335 @。

【讨论】:

感谢您的回答。这一切都是有道理的。一些 cmets: 1. 我们不发送裸小数,我们在 DTO 中使用它们。 2. 是的,我的样本并不能完全说明我遇到的问题;事实上,我发现包含decimalDecimalWrapper 与包含MyDecimalMyDecimalWrapper 之间的序列化/反序列化按预期工作。 3. 就我而言,我有一个 C++ 中的小数,可以表示为与 C# 兼容的 lo/hi/signscale; protobuf-net 十进制看起来像一个很好的合同。 4.问题似乎是由于 bcl.proto 十进制是用 sint32 SignScale 定义的,并且 protobuf-net 正在向/从流中写入/读取 uint32。如果序列化发生在 C++ 中(sint32 的写入),那么 protobuf-net 读取它的方式会产生无效值。我什至不确定如何在 C# 中处理 sint32 值。 @RomainVerdier 啊,sint32 vs uint32...该死的,这很痛苦。 zig-zag 编码很简单,但如果我让合同和代码说不同的东西,那就很烦人了 @RomainVerdier 嗯......这里的主要“修复”可能是更改 bcl.proto 以宣传 uint32。很好奇。 @RomainVerdier 啊,我觉得uint 很有道理,其实;它在这里的工作方式是将符号编码为位 0,将比例编码为位 1-16 - 所以只使用前 16 位并且它始终为正; sint32 效率低下

以上是关于使用自定义十进制原型合约(C#/C++ 互操作)时,Protobuf-net(反)序列化小数抛出的主要内容,如果未能解决你的问题,请参考以下文章

C#/C++ 回调类(非函数)互操作 - 如何?

C 和 C++ 中类型的互操作性

[转]Go与C语言的互操作

C#编程 10进制和26进制互转

智能合约重构社会契约(10)超级账本之跨链Hyperledger Lab

c语言如何读写二进制