13
\$\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\$
2
  • \$\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
    Commented Jul 26, 2016 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
    Commented Jul 26, 2016 at 23:32

3 Answers 3

4
\$\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\$
13
  • 1
    \$\begingroup\$ It crashes in ToByteArray, in Marshal.StructureToPtr(structure, handle, true); \$\endgroup\$
    – jrh
    Commented Jul 27, 2016 at 22:36
  • 1
    \$\begingroup\$ It's crashing because the struct you're using has string members. Strings are reference types. Duly noted. Thank you. \$\endgroup\$
    – RubberDuck
    Commented Jul 27, 2016 at 22:42
  • 2
    \$\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
    Commented Jul 27, 2016 at 23:15
  • 2
    \$\begingroup\$ Aha! I wonder if [MarshalAs(BStr)] would fix it? stackoverflow.com/a/3278956/3198973 \$\endgroup\$
    – RubberDuck
    Commented Jul 28, 2016 at 1:55
  • 2
    \$\begingroup\$ @RubberDuck That does seem to work. \$\endgroup\$
    – forsvarir
    Commented Jul 28, 2016 at 9:43
9
\$\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\$
7
  • 1
    \$\begingroup\$ This is a nice candidate for a helper disposable to be used like using(var handle = new AllocHGlobal(bufferSize)) { ... } \$\endgroup\$
    – t3chb0t
    Commented Jul 26, 2016 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
    Commented Jul 26, 2016 at 20:37
  • \$\begingroup\$ The Marshaller class isn't a terrible idea. \$\endgroup\$
    – RubberDuck
    Commented Jul 26, 2016 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
    Commented Jul 26, 2016 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
    Commented Jul 26, 2016 at 21:54
4
\$\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
  • 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
    Commented Jul 30, 2016 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
    Commented Jul 31, 2016 at 22:44

Your Answer

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

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