C# Memory Pinning: Convert a byte array to a data structure

C# Memory Pinning: Convert a byte array to a data structure. Have you ever wanted to simply typecast a byte array to a data structure and then use that data structure like you used to be able to do in C? Well, C# provides a facility to accomplish this.

This solution is several months old to me now, but I feel it is something very worthwhile for C# programmers to know.

Have you ever wanted to simply typecast a byte array to a data structure and then use that data structure like you used to be able to do in C? Well, C# provides a facility to accomplish this. Several months ago I needed to take a byte array 64 bytes long and extract different fields from it. At first it seemed that the only solution was to convert the byte array to a string and then use substrings to get what I needed. To add insult to injury, I then had to take each of the substrings and convert them into the data types that I wanted and then assign the members of my data structre to each of those. Something ain’t right about that!!

Now, being the stubborn programmer that I am, I was not satisfied with this solution as I used to have the ability to just typecast a byte array into whatever structure I wanted in C and dangit newer languages such as C# shouldn’t remove features. They should enhance existing ones along with introducing new ones.

Enter memory pinning. Now, I can’t take credit for finding this on my own. It was a tough find and I was only most of the way there when a co-worker of mine found the answer. Here is a code snippet to show how it’s done:

public static object RawDataToObject(ref byte[] rawData,
		Type overlayType) 
{ 
	object result = null;




	GCHandle pinnedRawData = GCHandle.Alloc(rawData,
		GCHandleType.Pinned);
	try
	{
	
		// Get the address of the data array
		IntPtr pinnedRawDataPtr = 
			pinnedRawData.AddrOfPinnedObject(); 

		// overlay the data type on top of the raw data
		result = Marshal.PtrToStructure(
			pinnedRawDataPtr ,
			overlayType);
	}
	finally
	{
		// must explicitly release
		pinnedRawData.Free(); 
	} 

	return result;
} 

Becaues the garbage collector will normally have the ability to change the location of where we have our object allocated in memory, we have to actually pin it by calling GCHandle.Alloc() passing it a handle type of GCHandleType.Pinned.

In the application I was working on I knew that the byte array would always be the same size. If, however, it had been a variable size, then we would have to ensure that the size of the overlay type is the same as the length of the byte array.

Now that we know how to actually do the conversion from byte array to data type, we should probably look at the data types themselves. The data types and their members have to be explicitly set to their correct sizes in the code. The following is an example structure:

[StructLayout(LayoutKind.Sequential, Size=64, Pack=1,
		CharSet=CharSet.Ansi)]
public class MyDataStructure
{
	[ MarshalAs( UnmanagedType.ByValArray, SizeConst=40 )]
	public byte[] name;
    
	public byte eyeColor;
	public byte hairColor;
	public Int16 age; // Two byte integer
	public Int16 height;
	public Int16 weight;
	public Int16 noOfPets;
	public byte sign;

	[ MarshalAs( UnmanagedType.ByValArray, SizeConst=3 )]
	public byte[] birthDate; //yymmdd

	[ MarshalAs( UnmanagedType.ByValArray, SizeConst=10 )]
	public byte[] unused;
	
}

There are several items that need to be explained here. First off, our structure is actually a class. Both classes and structs can be used. Classes just need to be treated as structs and include no methods. Next, notice our StructLayout attribute. Here are the meanings of each of the fields used in this attribute:

  • LayoutKind.Sequential: Tells the compiler that the fields in the structure should be allocated in the same order they are laid out in code
  • Size: This is the total size of the structure. When this is set, you must ensure that the fields all add up to this number.
  • Pack: This is the packing size that the compiler should use to allocate memory for the structure. The default is 8, so if you left this blank, Pack would would be set to 8. In this case, however, we have set the pack to 1, ensuring that memory for the structure is all allocated together. Pack is only used when the LayoutKind is set to Seqential.
  • CharSet: Indicates how the data fields should be marshaled.

Next, look at each of the fields in the data structure. We are representing three different data types in this particular code snippet. They include a single byte a two byte integer and byte arrays which are normally used for strings. To indicate that a field should be marshaled as an array, we have to use the MarshalAs attribute specifying the size the array should be in the attribute rather than in the delcaration code.

Now, when we put this all together, it looks something like this:

// We assume GetNextRecord retuns the 64 byte array
byte[] arrayOfBytes = GetNextRecord();
MyDataStructure mds = (MyDataStructure)RawDataToObject( 
		ref arrayOfBytes, typeof( MyDataStructure ) );

// Now we can reference the data structure
// however we would like
string name = ASCIIEncoding.ASCII.GetString( mds.name );
string age = mds.age.ToString();
...

This solution works great if you have to read in a fixed length data record from either a binary file or even some hardware device. It’s a very cool technique (if you ask me).

One thought on “C# Memory Pinning: Convert a byte array to a data structure”

  1. Nice .. It worked without any change. I was looking for it for quite some time. Thanks a lot.

Leave a Reply