jgoyvaerts
jgoyvaerts

Reputation: 43

Deserializing protobuf from C++ and reserializing in C# gives different output

I have a file with a protobuf message in it in byte format, when I read the file and deserialize the protobuf it works fine (I can read the objects fields and they are correct), however, when I reserialize it and save it back to a file, some bytes are different from the original (causing compatibility issues).

More specifically, after a string and before a bool theres the bytes '18 00' added.

I tried playing around with the DataFormat options from protobuf-net to get the exact same result as the original, but to no avail.

Does anyone know of any options in protobuf-net in regards to saving a string or a bool which could explain the extra 2 bytes?

Also, a ulong is being saved differently in bytes aswell.

I don't have control over the original file, I only know it's compiled/serialized with c++

My goal is to recreate the exact same file (bytewise) by serializing in c#, so it is identical to the file serialized by c++.

Upvotes: 1

Views: 448

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1062590

This comes down to how properties are handled. By default,

protogen -i:my.proto -o:my.cs

generates simple properties, of the form:

private bool _some_value = default(bool);
[global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"some_value",
    DataFormat = global::ProtoBuf.DataFormat.Default)]
[global::System.ComponentModel.DefaultValue(default(bool))]
public bool some_value
{
  get { return _some_value; }
  set { _some_value = value; }
}

This is fine for most scenarios, but doesn't quite support every "was a value actually specified?" scenario.

However, you can do instead:

protogen -i:my.proto -o:my.cs -p:detectMissing

which generates the much more thorough:

private bool? _some_value;
[global::ProtoBuf.ProtoMember(3, IsRequired = false, Name=@"some_value",
    DataFormat = global::ProtoBuf.DataFormat.Default)]
public bool some_value
{
  get { return _some_value?? default(bool); }
  set { _some_value = value; }
}
[global::System.Xml.Serialization.XmlIgnore]
[global::System.ComponentModel.Browsable(false)]
public bool some_valueSpecified
{
  get { return this._some_value != null; }
  set { if (value == (this._some_value== null))
      this._some_value = value ? this.some_value : (bool?)null; }
}
private bool ShouldSerializesome_value() { return some_valueSpecified; }
private void Resetsome_value() { some_valueSpecified = false; }

This has full support for tracking explicit assignment, including support for most UI-binding and serialization frameworks (not just protobuf-net).

The 2-byte difference is simply the difference between "false, not serialized due to implicit default value" and "false, known to be explicitly specified as false, and thus serialized".

So the fix is: include the -p:detectMissing when using protogen.

Upvotes: 1

Related Questions