2

I have done some researches on this topic and found some solutions with the help of

  1. MemoryStream and BinaryFormatter classes
  2. Marshal class

But neither of these methods work for my class because my classes have an array.

Here is the test class I am working with:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public class ByteArrayInClass
{
    private byte _option;
    private ushort _nElements;
    private byte[] arrayElements;

    public ByteArrayInClass(byte option, ushort nElements)
    {
        this._option = option;
        this._nElements = nElements;
        arrayElements = new byte[nElements];
        for (int i = 0; i < arrayElements.Length; i++)
        {
            arrayElements[i] = (byte)i;
        }
    }

    public static byte[] ObjectToBytes(byteArrayInClass value)
    {
    }

    public static byteArrayInClass BytesToObject(byte[] bytes)
    {
    }
}

In my main:

testObject1 = new ByteArrayInClass(3, 10);
byte[] testBytes1 = ByteArrayInClass.ObjectToBytes(testObject1);

byte[] testBytes2 = { 3, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
ByteArrayInClass testObject2 = ByteArrayInClass.BytesToObjectbyte(testBytes2);

I am starting to think that I need to convert the members of the class to bytes one by one, and vice versa to convert bytes to object. Could someone point me to the right direction?

Edit: I was not clear enough on what I am trying to do. I am working on a program which communicates with a server. It involves receiving data and sending data. The data are sent and received in bytes, and when I receive bytes of data, I need to construct a class with the received bytes, so I understand what is being sent to me. When I send data to the server, I first construct a class with the appropriate values, then convert the object to bytes, so I can send the data to the server. Hopefully this explain a little better on what I am trying to do.

It seems like there isn't a easy way to convert a class to bytes, so I am converting each class members to bytes myself. So the following is what I come up with. Please feel free to let me know if this is a dumb way to accomplish the task. I would like to learn a smarter way to do it.

public static int GetObjectSize(ByteArrayInClass value) 
{
    return Marshal.SizeOf(value.Option) + Marshal.SizeOf(value.ElementCount) + (value.ElementCount * 1);
}

public static byte[] ObjectToBytes(ByteArrayInClass value)
{
    int copyIndex = 0;
    byte[] resultBytes = new byte[GetObjectSize(value)];

    resultBytes[copyIndex] = value.Option;
    copyIndex += 1;

    byte[] elementCountBytes = BitConverter.GetBytes(value.ElementCount);
    elementCountBytes.CopyTo(resultBytes, copyIndex);
    copyIndex += elementCountBytes.Length;

    value.ElementArray.CopyTo(resultBytes, copyIndex);
    return resultBytes;
}


public static ByteArrayInClass BytesTObject(byte[] bytes)
{
    int convertIndex = 0;
    byte option = bytes[convertIndex];
    convertIndex += 1;
    ushort elementCount = BitConverter.ToUInt16(bytes, convertIndex);
    convertIndex += 2;

    ByteArrayInClass resultObj = new ByteArrayInClass(option, elementCount); 
    byte[] elementArray = new byte[elementCount];
    int j = 0;
    for (int i = convertIndex; i < (convertIndex + elementCount); i++)
    {
        elementArray[j++] = bytes[i];
    }
    resultObj.ElementArray = elementArray;
    return resultObj;
}
6
  • 5
    The byte array shouldn't be a problem. Why are you using the StructLayout attribute and have you tried serializing and des-erializing without it? Normally objects like this are marked with the [Serializable] attribute. See here: msdn.microsoft.com/en-us/library/4abbf6k0(v=vs.110).aspx Commented Dec 27, 2014 at 1:58
  • I'm have trouble trying to understand your goal, are you trying to convert bytes to your class and vice versa? Commented Dec 27, 2014 at 2:01
  • The code that you claim "neither of these methods work for my class" somehow is missing from the post. Please make sure to add that part from your code to the sample. It is hard to reason why not present code does not work. Commented Dec 27, 2014 at 2:02
  • Is the "Im my main" section, the intended usage? Commented Dec 27, 2014 at 2:03
  • Unlike forum sites, we don't use "Thanks", or "Any help appreciated", or signatures on Stack Overflow. See "Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?. Commented Dec 27, 2014 at 3:09

4 Answers 4

4

Ok this maybe what your looking for

The assumptions gleaned from your comments and updates are as follows

  • You want a generic way to serialize your classes (messages)
  • You want to be able to use this over the wire (sockets, not wcf)
  • It conforms to a socket regime
  • You want some basic protection against corruption
  • you want it easy to use

Note : there maybe many areas here which can be improved, however this is just to give you some ideas

If your using something like webservices this entire answer is null and void, this is really only a adhock method of serialization suitable for sockets

Overview

The Package class has some methods for the following

  • Able to serialize objects to bytes
  • Able to deserialize bytes to objects
  • Can create packages with header information
  • Header contains your message type (Option)
  • The length of the entire package
  • Some basic check summing and markers, in case of corruptions you can validate a package

So given the following

Message Enum

// Some predefined messages
public enum MessageType : byte
{
   MyClass,
   MyOtherClass,
   Message3,
   Message4,

}

Some class you want to send over the wire

// a serilaizable class
// Make this as you like
[Serializable]
public class MyClass
{

   public byte[] SomeArbitaryBytes { get; set; }
   public string SomeArbitaryString { get; set; }
   public int SomeArbitaryInt { get; set; }
   public double SomeArbitaryDouble { get; set; }

   public MyClass()
   {

      SomeArbitaryString = "hello";
      SomeArbitaryInt = 7;
      SomeArbitaryDouble = 98.1;
      SomeArbitaryBytes = new byte[10];
      for (var i = 0; i < SomeArbitaryBytes.Length; i++)
      {
         SomeArbitaryBytes[i] = (byte)i;
      }
   }
}

Package Class

public static class Package
{
   // Method for basic checksum
   private static byte GetBasicChecksum(this byte[] data)
   {
      byte sum = 0;
      unchecked // Let overflow occur without exceptions
      {
         foreach (byte b in data)
         {
            sum += b;
         }
      }
      return sum;
   }

   // Serialize to bytes (BinaryFormatter)
   public static byte[] SerializeToBytes<T>(this T source)
   {
      using (var stream = new MemoryStream())
      {
         var formatter = new BinaryFormatter();
         formatter.Serialize(stream, source);
         return stream.ToArray();
      }
   }

   // Deerialize from bytes (BinaryFormatter)
   public static T DeserializeFromBytes<T>(this byte[] source)
   {
      using (var stream = new MemoryStream(source))
      {
         var formatter = new BinaryFormatter();
         stream.Seek(0, SeekOrigin.Begin);
         return (T)formatter.Deserialize(stream);
      }
   }

   // Check if we have enough data
   // will throw if it detects a corruption (basic)
   // return false if there isnt enough data to determine
   // return true and length of the package if sucessfull
   public static bool HasValidPackage(this Stream stream, out Int32 messageSize)
   {
      messageSize = -1;

      if (stream.Length - stream.Position < sizeof(byte) * 2 + sizeof(Int32))
         return false;


      var stx = stream.ReadByte();

      if (stx != 2)
         throw new InvalidDataException("Invalid Package : STX Failed");

      var packageLength = new byte[sizeof(Int32)];
      stream.Read(packageLength, 0, sizeof(Int32));
      messageSize = BitConverter.ToInt32(packageLength, 0) - sizeof(byte) * 3;
      var checkSum = stream.ReadByte();

      if (checkSum != packageLength.GetBasicChecksum())
         throw new InvalidDataException("Invalid Package : CheckSum Failed");

      return stream.Length >= messageSize;

   }

   // Pack the message
   public static byte[] PackMessage<T>(this T source, MessageType messageType)
   {
      var buffer = source.SerializeToBytes();
      var packageLength = BitConverter.GetBytes(buffer.Length + sizeof(byte) * 3);
      using (var stream = new MemoryStream())
      {
         stream.WriteByte(2);
         stream.Write(packageLength, 0, sizeof(Int32));
         stream.WriteByte(packageLength.GetBasicChecksum());
         stream.WriteByte((byte)messageType);
         stream.Write(buffer, 0, buffer.Length);
         stream.WriteByte(3);
         return stream.ToArray();
      }
   }

   // Unpack the message
   public static MessageType UnPackMessage(this Stream stream, Int32 messageSize, out byte[] buffer)
   {

      var messageType = (MessageType)stream.ReadByte();
      buffer = new byte[messageSize];
      stream.Read(buffer, 0, buffer.Length);

      var etx = stream.ReadByte();

      if (etx != 3)
         throw new InvalidDataException("Invalid Package : ETX Failed");

      return messageType;
   }

}

Client Side Code

// create your class 
var myClass = new MyClass();

// Create a package out of it
var bytes = myClass.PackMessage(MessageType.MyClass);

Server Side Code

// this is server side
using (var stream = new MemoryStream(bytes))
{
   Int32 messageSize;

   // if we have a valid package do stuff
   // this loops until there isnt enough data for a package or empty
   while (stream.HasValidPackage(out messageSize))
   {

      byte[] buffer;

      switch (stream.UnPackMessage(messageSize, out buffer))
      {
         case MessageType.MyClass:
            var myClassCopy = buffer.DeserializeFromBytes<MyClass>();
            // do stuff with your class
            break;
         case MessageType.MyOtherClass:
            break;
         case MessageType.Message3:
            break;
         case MessageType.Message4:
            break;
         default:
            throw new ArgumentOutOfRangeException();
      }

   }

   // do something with the remaining bytes here, if any, i.e partial package

}
Sign up to request clarification or add additional context in comments.

Comments

1

C# is not C/C++, so you can't just use address arithmetic as you want (as far as I see).

In .NET right way to convert to/from a byte array is a serializing/deserializing.

Or, maybe you need the BitConverter if you want emulate a low-level.

Comments

1

It is hard to tell if Serialization is the end result, or simply a method used in an attempt to achieve some other goal. step to some other goal. Byte arrays serialize just fine however:

[Serializable]
public class ByteArrayClass
{
    public byte[] FirstArray {get; set;}
    public byte[] SecondArray {get; set;}           
}

Then to do a round trip test:

    ByteArrayClass myFoo = new ByteArrayClass();
    myFoo.FirstArray = new byte[] { 3, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    myFoo.SecondArray = new byte[] { 3, 11, 5, 1, 21, 23, 4, 5, 4, 7, 8, 9, 10 };

    using (FileStream fs = new FileStream(@"C:\Temp\Arry.Bin", 
                          FileMode.Create, FileAccess.Write))
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(fs, myFoo);
    }

    ByteArrayInClass newFoo;
    using (FileStream fs = new FileStream(@"C:\Temp\Arry.Bin", 
                        FileMode.Open, FileAccess.Read))
    {
        BinaryFormatter bf = new BinaryFormatter();
        newFoo = (ByteArrayClass) bf.Deserialize(fs);
    }

I would be remiss if I did not add that ProtoBuf-Net is a much faster, better serializer than the BinaryFormatter. It is also better suited for data transfer - a class serialized by one assembly can be deserialized by another, and it produces smaller output.

Comments

1

It's hard to tell what you actually want to achieve, however I'll give it a shot.

Is this in line with your intended goal?

public class ByteArrayInClass
{
   public byte Option { get; set; }
   public ushort Elements { get; set; }
   public byte[] Bytes { get; set; }

   public ByteArrayInClass(byte option, ushort nElements)
   {
      this.Option = option;
      this.Elements = nElements;
      this.Bytes = new byte[nElements];
      for (var i = 0; i < nElements; i++)
      {
         this.Bytes[i] = (byte)i;
      }
   }
   public ByteArrayInClass(byte[] array)
   {
      this.Elements = (ushort)array.Length;
      this.Bytes = new byte[this.Elements];
      array.CopyTo(this.Bytes, 0);
   }
   public static byte[] ObjectToBytes(ByteArrayInClass value)
   {
      var result = new byte[value.Elements];
      value.Bytes.CopyTo(result, 0);
      return result;
   }

   public static ByteArrayInClass BytesToObject(byte[] bytes)
   {
      return new ByteArrayInClass(bytes);
   }
}

6 Comments

I think this is close to what I need. When I convert the class to bytes, I need to convert Opition, Elements and Bytes all into bytes. So I think the only way to do that is to convert each class member to bytes and return all the bytes as the resulting bytes. Does that sound right? I was trying to avoid doing that because I have a lot of classes with lots of class members.
Yeah this can be done, and as you say it can be done manually, however it can also be done automatically with serialization, is there any reason why you don't want to use serialization?, if its size i could probably give you an example using proto-buf which is mighty fast and effeicent
I would like to learn to do this with serialization instead of converting each class member manually. Please show an example without using proto-buf, I am trying to use just .NET framework without having to install third party components. The sample object I created has 13 bytes, but when I try the serialization method described by Plutonix, I get a MemoryStream with size 208 for some reasons.
I'm guessing your option flag and length are so you can send this over the wire (ie TCP sockets), and recompose it on the other side?
Yes, the option flag is for the other side or me to understand what type of message it is. The length describes the length of the content.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.