views:

34

answers:

1

I have a class defined in C++/CLI as such:

literal Int32 BufferLength = 4000;

Message()
{
   num1 = 0;
   num2 = 0;
   str1 = String::Empty;
   buffer = gcnew array<System::SByte>(BufferLength);
};

[ProtoMember(1)]
property double num1
{
   double get() { return _num1; }
   void set(double value) { _num1 = value; }
}

[ProtoMember(2)]
property double num2
{
   double get() { return _num2; }
   void set(double value) { _num2 = value; }
}

[ProtoMember(3)]
property String^ str1
{
   String^ get() { return _str1; }
   void set(String^ value) { _str1 = value; }
}

[ProtoMember(4)]
property array<System::SByte>^ buffer
{
   array<System::SByte>^ get() { return _buffer; }
   void set(array<System::SByte>^ value) { _buffer = value; }
}

While debugging, I can see the serializer pulling the value from the buffer property with data in tact. When the deserializer runs I see it pushing data into the buffer property, however the array is filled with 0's in lieu of the data that was there before serialize. I've tried setting IsRequired = true on the ProtoMember attribute, no luck.

I have other messages defined with sbyte arrays that are deserializing fine. However, those arrays are quite short (10 maximum). The only thing that stands out here to me is the length of this array. Help! :-)

Edit: I guess I should also mention that I'm using v1 r282.

A: 

The error here is that protobuf-net (in line with the protobuf-spec) deserializes list (etc) data by appending data. And in "v1" (the version you are using) it always runs the constructor. So when deserializing, it runs the constructor (creates an array length 4000), then processes the data and appends another 4000 items. If you check, you'll find the array is now 8000 long (you'll be glad to hear that it doesn't resize the array 4000 times...).

Fixes:

  • in v2 (not really quite released, this is for info only) you can suppress the constructor completely
  • or just remove the array creation in the ctor, and assign via the set instead (perhaps add a static factory method that does this for you, to save you some code)

The following test rig (translated to C# for my convenience, sorry) works fine:

using System;
using System.Diagnostics;
using ProtoBuf;

namespace ConsoleApplication28
{
    class Program
    {
        static void Main()
        {
            var msg = Message.Create();
            var rand = new Random();
            var buffer = msg.buffer;
            for (int i = 0; i < buffer.Length; i++)
                buffer[i] = (sbyte)rand.Next(-128, 128);
            var clone = Serializer.DeepClone(msg);
            var cloneBuffer = clone.buffer;
            Debug.Assert(!ReferenceEquals(buffer, cloneBuffer), "Should be different buffer");

            Debug.Assert(buffer.Length == cloneBuffer.Length, "Should be same length");
            for(int i = 0 ; i < buffer.Length ; i++)
                Debug.Assert(buffer[i] == cloneBuffer[i], "Should be same value at index " + i);
        }
    }

    [ProtoContract]
    public class Message
    {
        const int BufferLength = 4000;
        public static Message Create()
        {
            var msg = new Message();
            msg.buffer = new sbyte[BufferLength];
            return msg;
        }
        private Message()
        {
           num1 = 0;
           num2 = 0;
           str1 = String.Empty;
        }


        private double _num1, _num2;
        private string _str1;
        private sbyte[] _buffer;

        [ProtoMember(1)]
        public double num1
        {
            get { return _num1; }
            set { _num1 = value; }
        }

        [ProtoMember(2)]
        public double num2
        {
           get { return _num2; }
           set { _num2 = value; }
        }

        [ProtoMember(3)]
        public String str1
        {
           get { return _str1; }
           set { _str1 = value; }
        }

        [ProtoMember(4)]
        public sbyte[] buffer
        {
           get { return _buffer; }
           set { _buffer = value; }
        }    
    }
}
Marc Gravell
Marc, I knew you wouldn't let me down. :-) First post for me here but I've lurked for a long time. In most of my implementation I'm using List<T> but in a few cases using an array<T> made the consuming code easier to work with. I've just decided to move the array initialization from the default constructor to an Initialize() method that I'll call when necessary. Thanks again for your invaluable help and for protobuf-net!
harlam357