11
\$\begingroup\$

I have a need to turn various structures into byte arrays to be sent over serial port to another machine. I created generic extensions to turn any structure into a byte array and from a byte array back into a given structure.

I've not used the Marshal class often, and I'm uncomfortable manipulating memory on this level.

  • Do I need to explicitly free any memory?
  • Is this an efficient implementation? I'll be calling this a lot, it's important that it's efficient in both terms of memory and speed.

public static class StructureExtensions
{
    public static byte[] ToByteArray<T>(this T structure) where T : struct
    {
        var bufferSize = Marshal.SizeOf(structure);
        var byteArray = new byte[bufferSize];

        IntPtr handle = Marshal.AllocHGlobal(bufferSize);
        Marshal.StructureToPtr(structure, handle, true);
        Marshal.Copy(handle, byteArray, 0, bufferSize);

        return byteArray;
    }

    public static T ToStructure<T>(this byte[] byteArray) where T : struct
    {
        var packet = new T();
        var bufferSize = Marshal.SizeOf(packet);
        IntPtr handle = Marshal.AllocHGlobal(bufferSize);
        Marshal.Copy(byteArray, 0, handle, bufferSize);

        return Marshal.PtrToStructure<T>(handle);
    }
}

Example Call:

var point = new Point(10,5);
byte[] serialized = point.ToByteArray(); 

Point deserialized = serialized.ToStructure<Point>();
\$\endgroup\$
  • \$\begingroup\$ my reading of ptrtostructure is that it will work with any type, class or struct. So you dont need the where T : struct clause - unless you want to impose it for some other reason. Maybe I am reading it wrong \$\endgroup\$ – pm100 Jul 26 '16 at 22:56
  • \$\begingroup\$ @pm100 that's good to know. I was reading a bit, and it seems you're correct. Only, if it's a reference type, you have to call the DestroyStructure method. Since I'm only using this to serialize some struct's for transport, I think I'll leave it. \$\endgroup\$ – RubberDuck Jul 26 '16 at 23:32
3
\$\begingroup\$

Structs with strings cause an AccessViolationException on x64 builds

The code below gives me an AccessViolationException on Marshal.StructureToPtr in StructureExtensions.ToByteArray on an x64 build (both Release and Debug).

[Serializable]
struct MyStruct
{
    public int w, x,y,z;
    public string a,b,c;
}

static void Main(string[] args)
{

    MyStruct testingStruct = new MyStruct();

    testingStruct.w = 86;
    testingStruct.x = 75;
    testingStruct.y = 30;
    testingStruct.z = 9;

    testingStruct.a = "";
    testingStruct.b = "This causes an AccessViolationException on an x64 build.";
    testingStruct.c = "Some other string.";

    byte[] structBytes;

    structBytes = testingStruct.ToByteArray();

    Console.WriteLine("Dear Compiler: Please don't optmize out " + structBytes[0].ToString());
    Console.ReadLine();

}

I'm really curious as to why this is happening.


If w,x,y,z in MyStruct are changed to bytes, it causes a hard crash to desktop instead.

It seems to happen on both x86 and x64 builds.

  • If I change w,x,y, and z to byte, it causes a hard crash instead of an AccessViolationException.

  • The crash with bytes in the struct happens in Marshal.StructureToPtr().


[Serializable]
struct MyStruct
{
    public byte w, x,y,z;
    public string a,b,c;
}

class Program
{
    static void Main(string[] args)
    {

        MyStruct testingStruct = new MyStruct();

        testingStruct.w = 86;
        testingStruct.x = 75;
        testingStruct.y = 30;
        testingStruct.z = 9;

        testingStruct.a = "testing";
        testingStruct.b = "";
        testingStruct.c = "Some other string.";

        byte[] structBytes;

        structBytes = testingStruct.ToByteArray();

        Console.WriteLine("Dear Compiler: Please don't optmize out " + structBytes[0].ToString());
        Console.ReadLine();

    }
}
\$\endgroup\$
  • \$\begingroup\$ It crashes before you call tobytearray? \$\endgroup\$ – forsvarir Jul 27 '16 at 22:34
  • 1
    \$\begingroup\$ @jrh I think you have to pass false for the first call if you're using reference types like strings, then true for subsequent calls. You could see if that makes a difference. \$\endgroup\$ – forsvarir Jul 27 '16 at 22:44
  • 1
    \$\begingroup\$ Related : stackoverflow.com/a/20003830/4975230 , codeproject.com/Articles/97711/sizeof-vs-Marshal-SizeOf . It looks like the Marshal functions are calculating the size of a string reference, and copying that in, not the actual string value. \$\endgroup\$ – jrh Jul 27 '16 at 23:15
  • 1
    \$\begingroup\$ Aha! I wonder if [MarshalAs(BStr)] would fix it? stackoverflow.com/a/3278956/3198973 \$\endgroup\$ – RubberDuck Jul 28 '16 at 1:55
  • 1
    \$\begingroup\$ @RubberDuck That does seem to work. \$\endgroup\$ – forsvarir Jul 28 '16 at 9:43
8
\$\begingroup\$

I haven't used it, however all of the documentation seems to suggest that you need explicitly free the memory allocated through:

IntPtr handle = Marshal.AllocHGlobal(bufferSize);

With a paired call to:

Marshal.FreeHGlobal(handle);

You don't seem to need the handle after you've performed the marshalling, so it could be as simple as updating the function to:

public static byte[] ToByteArray<T>(this T structure) where T : struct
{
    var bufferSize = Marshal.SizeOf(structure);
    var byteArray = new byte[bufferSize];

    IntPtr handle = Marshal.AllocHGlobal(bufferSize);
    try
    {
        Marshal.StructureToPtr(structure, handle, true);
        Marshal.Copy(handle, byteArray, 0, bufferSize);

    }
    finally
    {
        Marshal.FreeHGlobal(handle);
    }
    return byteArray;
}

Depending on the overhead of The AllocHGlobal / FreeHGlobal and if you're going to be marshalling the same structures over and over again you might be better off going with something like:

public sealed class Marshaller<T> : IDisposable where T : struct 
{
    readonly IntPtr _handle;
    readonly int _bufferSize;

    public Marshaller()
    {
        _bufferSize = Marshal.SizeOf(typeof(T));
        _handle = Marshal.AllocHGlobal(_bufferSize);
    }

    public byte[] ToByteArray(T structure)
    {
        var byteArray = new byte[_bufferSize];

        Marshal.StructureToPtr(structure, _handle, true);
        Marshal.Copy(_handle, byteArray, 0, _bufferSize);

        return byteArray;
    }

    public T ToStructure(byte[] byteArray)
    {
        var packet = new T();

        Marshal.Copy(byteArray, 0, _handle, _bufferSize);

        return Marshal.PtrToStructure<T>(_handle);
    }


    #region IDisposable Support
    private bool disposedValue = false;

    ~Marshaller() {
       Dispose();
    }

    public void Dispose()
    {
        if (!disposedValue)
        {
            Marshal.FreeHGlobal(_handle);

            disposedValue = true;
        }

        GC.SuppressFinalize(this);
    }
    #endregion

}
\$\endgroup\$
  • 1
    \$\begingroup\$ This is a nice candidate for a helper disposable to be used like using(var handle = new AllocHGlobal(bufferSize)) { ... } \$\endgroup\$ – t3chb0t Jul 26 '16 at 20:28
  • \$\begingroup\$ @t3chb0t Funny, I was thinking disposable, but without immediate cleanup so that you could avoid the alloc/free cycle, but it really depends on the use case. \$\endgroup\$ – forsvarir Jul 26 '16 at 20:37
  • \$\begingroup\$ The Marshaller class isn't a terrible idea. \$\endgroup\$ – RubberDuck Jul 26 '16 at 20:52
  • \$\begingroup\$ For the Marshaller <T> class both the _handle and _bufferSize could/should be readonly. You aren't using the disposing parameter of the Dispose () method. \$\endgroup\$ – Heslacher Jul 26 '16 at 21:26
  • \$\begingroup\$ @Heslacher Agree on both counts, I have to confess I let VS template out the Dispose code and wasn't quite zealous enough in cleaning up after it. \$\endgroup\$ – forsvarir Jul 26 '16 at 21:54
3
\$\begingroup\$

You could avoid Marshal and use Serialization instead. The solution below is an example of that and it handles string, all primitive types and serializable types:

public static byte[] ToByteArray<T>(this T structure) where T : struct
{
  using (MemoryStream stream = new MemoryStream())
  {
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(stream, structure);

    stream.Flush();
    stream.Seek(0, SeekOrigin.Begin);
    return stream.ToArray();
  }
}

public static T ToStruct<T>(this byte[] buffer) where T : struct
{
  using (MemoryStream stream = new MemoryStream(buffer))
  {
    BinaryFormatter bf = new BinaryFormatter();
    var data = bf.Deserialize(stream);
    return (T)data;
  }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ I considered this approach, but there's a significant size overhead with using the standard serialization (153 bytes compared to 8) for a Point class with 2 integers, which if it's going over a serial port may be a consideration. \$\endgroup\$ – forsvarir Jul 30 '16 at 20:26
  • 1
    \$\begingroup\$ @forsvarir it definitely is a consideration. Both speed and size are big concerns for me. My total packet length maxes at 20 bytes. Unfortunately this isn't viable for me, but gets a +1 because it may be useful to others. \$\endgroup\$ – RubberDuck Jul 31 '16 at 22:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.