Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fixed Object to Byte Array

Tags:

c#

bytearray

I have a simple object that looks like this:

public class Foo
{
   public UInt32 One { get; set; }
   public UInt32 Two { get; set; }
   public UInt32 Three { get; set; }
   public UInt32 Four { get; set; }
}

I tried this code I found somewhere on the net:

public byte[] ObjectToByteArray(Object obj)
{
    MemoryStream fs = new MemoryStream();
    BinaryFormatter formatter = new BinaryFormatter();
    formatter.Serialize(fs, obj);
    byte[] rval = fs.ToArray();
    fs.Close();
    return rval;
}

But somehow the returning byte array has a size of 248 bytes.
I would expect it to be 4 bytes x 4 fields = 16 bytes.

QUESTION:
What's the cleanest way to convert a fixed object into a byte array?
And should the resulting array be 16 bytes in size in this case?

like image 227
SF Developer Avatar asked Jun 20 '14 04:06

SF Developer


4 Answers

BinaryFormatter saves a lot of type information to be able to deserialize properly. If you want compact serialization or to comunicate via some strict protocol, you will have to do it explictly like this:

public byte[] ToByteArray()
{
    List<byte> result = new List<byte>();

    result.AddRange(BitConverter.GetBytes(One));
    result.AddRange(BitConverter.GetBytes(Two));
    result.AddRange(BitConverter.GetBytes(Three));
    result.AddRange(BitConverter.GetBytes(Four));

    return result.ToArray();
}

here I convert each of your UInt32 into byte array and store it in resulting array.

Edit
Turns out there is another way by using struct and Marshal First you make struct and mark it with attributes like that:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MyStruct
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    public string StringField;

    public int IntField;
}

Here LayoutKind.Sequential tells clr to keep fields in memory in same order as declaration. Without Pack = 1 structures can take more memory than required. Like struct with one short field and one byte needs only 3 bytes, but by default it's size will most likely be 4 (processor has instructions for manipulating single byte, 2 bytes and 4 bytes, clr sacrifices one byte per instance of struct to reduce amount of instructions of machine code by half). Now you can use Marshal to copy bytes:

public static byte[] GetBytes<T>(T str)
{
    int size = Marshal.SizeOf(str);
    var bytes = new byte[size];
    IntPtr ptr = Marshal.AllocHGlobal(size);

    try 
    {
         Marshal.StructureToPtr(str, ptr, true);
         Marshal.Copy(ptr, bytes, 0, size);
         return bytes;
    }
    finally 
    {
         Marshal.FreeHGlobal(ptr);
    }
}

Everything works fine with simple types. For complex types such as string you'll have to use MarshalAs attribute and it's using is a bit more complicated (In example I told clr to marshal string as array of fixed 50 bytes size).

like image 51
Atomosk Avatar answered Nov 07 '22 09:11

Atomosk


Here's another way...which way is best is a matter of opinion probably. I like Atomosk's answer. Combine that answer with cast operator overloads and you've got a pretty elegant solution.

class Foo
{
    public UInt32 One { get; set; }
    public UInt32 Two { get; set; }
    public UInt32 Three { get; set; }
    public UInt32 Four { get; set; }

    static public implicit operator byte[](Foo f)
    {
        MemoryStream m = new MemoryStream(16);
        BinaryWriter w = new BinaryWriter(m);

        w.Write(f.One);
        w.Write(f.Two);
        w.Write(f.Three);
        w.Write(f.Four);
        w.Close();
        m.Close();

        return m.ToArray();
    }

    static public explicit operator Foo(byte[] b)
    {
        Foo f = new Foo();
        MemoryStream m = new MemoryStream(b);
        BinaryReader r = new BinaryReader(m);

        f.One = r.ReadUInt32();
        f.Two = r.ReadUInt32();
        f.Three = r.ReadUInt32();
        f.Four = r.ReadUInt32();

        r.Close();
        m.Close();

        return f;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Foo f = new Foo() { One = 1, Two = 2, Three = 3, Four = 4 };
        byte[] b = (byte[])f;
        Console.WriteLine(b.Length);

        f = (Foo)b;
        Console.WriteLine("{0} {1} {2} {3}", f.One, f.Two, f.Three, f.Four);

        Console.ReadKey(true);
    }
}
like image 22
Markus Avatar answered Nov 07 '22 09:11

Markus


Keep in mind that when you serialise an object with the BinaryFormatter, you're including things like type information. That results in the overhead you're seeing.

If you want to serialise an object into (in this case) just 16 bytes then you'll need to do it manually with something like a StreamWriter.

If size isn't an issue then a Binaryformatter in this case isn't wrong and doesn't take much code to do it, but it's not the most memory efficient way of doing it.

Edit: Here's how you'd do it with a StreamWriter

 System.IO.MemoryStream stream = new System.IO.MemoryStream();  

 StreamWriter writer = new StreamWriter(stream);

 writer.Write(myObject.One);  // here's where we actually write the data to the stream
 writer.Write(myObject.Two);
 writer.Write(myObject.Three);
 writer.Write(myObject.Four);    

 writer.Flush();   // make sure all the data in the stream writer ends up in the 
                   // underlying stream

 byte[] result = stream.ToArray();  // here's your resulting byte array

 stream.Dispose();   // don't forget to dispose of the stream!        
like image 41
Charlie Salts Avatar answered Nov 07 '22 09:11

Charlie Salts


Here is one way to do it manually which will guarantee the 16-bytes.

using System;
using System.IO;
using System.Linq;

public class Foo 
{
    public UInt32 One { get; set; }
    public UInt32 Two { get; set; }
    public UInt32 Three { get; set; }
    public UInt32 Four { get; set; }

    public Foo() {}

    public Foo(byte[] array)
    {
        One = BitConverter.ToUInt32(array,0);    
        Two = BitConverter.ToUInt32(array,4);
        Three = BitConverter.ToUInt32(array,8);    
        Four = BitConverter.ToUInt32(array,12);    
    }
    public byte[] toByteArray()
    {
        byte[] retVal =  new byte[16];
        byte[] b = BitConverter.GetBytes(One);
        Array.Copy(b, 0, retVal, 0, 4);
        b = BitConverter.GetBytes(Two);
        Array.Copy(b, 0, retVal, 4, 4);
         b = BitConverter.GetBytes(Three);
        Array.Copy(b, 0, retVal, 8, 4);
         b = BitConverter.GetBytes(Four);
        Array.Copy(b, 0, retVal, 12, 4);
        return retVal;
    }
}
public class P{
    public static void Main(string[] args) {
        Foo foo = new Foo();
        foo.One = 1;
        foo.Two = 2;
        foo.Three = 3;
        foo.Four = 4;

        byte[] arr  = foo.toByteArray();
        Console.WriteLine(arr.Length);


        Foo bar = new Foo(arr);
        Console.WriteLine(string.Format("{0} {1} {2} {3}", bar.One, bar.Two, bar.Three, bar.Four));

    }
}

Output:

16
1 2 3 4
like image 42
merlin2011 Avatar answered Nov 07 '22 08:11

merlin2011