views:

4634

answers:

5

What would be the best way to fill a C# struct from a byte[] array where the data was from a C/C++ struct? The C struct would look something like this (my C is very rusty):

typedef OldStuff {
CHAR Name[8];
UInt32 User;
CHAR Location[8];
UInt32 TimeStamp;
UInt32 Sequence;
CHAR Tracking[16];
CHAR Filler[12];
}

And would fill something like this:

[StructLayout(LayoutKind.Explicit, Size = 56, Pack = 1)]
public struct NewStuff
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
[FieldOffset(0)]
public string Name;

[MarshalAs(UnmanagedType.U4)]
[FieldOffset(8)]
public uint User;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
[FieldOffset(12)]
public string Location;

[MarshalAs(UnmanagedType.U4)]
[FieldOffset(20)]
public uint TimeStamp;

[MarshalAs(UnmanagedType.U4)]
[FieldOffset(24)]
public uint Sequence;

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
[FieldOffset(28)]
public string Tracking;
}

What is best way to copy OldStuff to NewStuff, if OldStuff was passed as byte[] array?

I'm currently doing something like the following, but it feels kind of clunky.

GCHandle handle;
NewStuff MyStuff;

int BufferSize = Marshal.SizeOf(typeof(NewStuff));
byte[] buff = new byte[BufferSize];

Array.Copy(SomeByteArray, 0, buff, 0, BufferSize);

handle = GCHandle.Alloc(buff, GCHandleType.Pinned);

MyStuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),
typeof(NewStuff));

handle.Free();

Is there better way to accomplish this?

A: 

If you have a byte[] you should be able to use the BinaryReader class and set values on NewStuff using the available ReadX methods.

Mufaka
A: 

Would using the BinaryReader class offer any performance gains over pinning the memory and using Marshal.PtrStructure?

Chris Miller
+12  A: 

From what I can see in that context, you don't need to copy SomeByteArray into a buffer. You simply need to get the handle from SomeByteArray, pin it, copy the IntPtr data using PtrToStructure and then release. No need for a copy.

That would be:

NewStuff ByteArrayToNewStuff(byte[] bytes)
{
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    NewStuff stuff = (NewStuff)Marshal.PtrToStructure(
        handle.AddrOfPinnedObject(), typeof(NewStuff));
    handle.Free();
    return stuff;
}

Generic version:

T ByteArrayToStructure<T>(byte[] bytes) where T: struct 
{
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),
        typeof(T));
    handle.Free();
    return stuff;
}

...

Coincoin
Although somewhat late of a comment, maybe there should have been a 'struct' constraint to the type parameter T in the generic version of the method? Otherwise the PtrToStructure method may throw an exception as it expects a typeof(struct).
Cecil Has a Name
Nice catch, fixed it. Thanks.
Coincoin
+1  A: 

Watch out for packing issues. In the example you gave all fields are at the obvious offsets because everything is on 4 byte boundaries but this will not always be the case. Visual C++ packs on 8 byte boundaries by default.

Tim Ring
+1  A: 

FYI, If your program runs on various machines you might need to handle little vs big endian.

KPexEA
How can you handle that on the level of the struct, i.e. without having to individually reverse the bytes for each value in the struct?
Pat