Inside C# 4 Data Structs
Tuesday, 07 September 2010
Article Index
Inside C# 4 Data Structs
Being exact
The API
Serialising structs
Manual mashaling
Struct-to-pointer

Banner

Serialising structs

Now that we have looked at the complicated question of how to control the memory layout of a struct, it is time to discover how to get at the bytes that make up a struct, i.e. how do we serialise a struct?

There are many ways of doing this job and the most commonly encountered uses Marshal.AllocHGlobal to allocate an unmanaged buffer from the global heap.

After this everything is achieved using memory transfer functions such as StructToPtr or Copy. For exampl

public static byte[] RawSerialize(
object anything)
{
int rawsize = Marshal.SizeOf(anything);
IntPtr buffer = Marshal.AllocHGlobal(
rawsize);
Marshal.StructureToPtr(anything,
buffer, false);
byte[] rawdata = new byte[rawsize];
Marshal.Copy(buffer, rawdata, 0,
rawsize);
Marshal.FreeHGlobal(buffer);
return rawdata;
}

In fact there is no need to do so much bit moving as it is fairly easy to move the bytes in the struct directly to the byte array without the need for an intermediate buffer.

The key to this generally useful technique is the GCHandle object. This will return a Garbage Collection handle to any managed data type. If you ask for a “pinned” handle then the object will not be moved by the garbage collector and you can use the handle’s AddrOfPinnedObject method to retrieve its starting address.

For example the RawSerialise method can be rewritten:

 

public static byte[] RawSerialize(
object anything)
{
int rawsize = Marshal.SizeOf(anything);
byte[] rawdata = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdata,
GCHandleType.Pinned);
Marshal.StructureToPtr(anything,
handle.AddrOfPinnedObject(), false);
handle.Free();
return rawdata;
}

This is both simpler and faster. You can use the same methods to deserialise data in a byte array into a struct but rather than considering this example it is more instructive to examine the related problem of reading a struct from a stream.

Structs from streams

A fairly common requirement is to read a struct, possibly written using some other language, into a C# struct. For example, suppose you need to read in a bitmap file which starts with a file header, followed by a bitmap header and then the bitmap data. The file header structure is easy to translate:

[StructLayout(LayoutKind.Sequential, 
Pack = 1)]
public struct BITMAPFILEHEADER
{
public Int16 bfType;
public Int32 bfSize;
public Int16 bfReserved1;
public Int16 bfReserved2;
public Int32 bfOffBits;
};

A function that will read any structure available as a stream and return a struct can be written without the need for Generics:

public object ReadStruct(FileStream fs, 
Type t)
{
byte[] buffer = new byte[
Marshal.SizeOf(t)];
fs.Read(
buffer,
0,
Marshal.SizeOf(t));
GCHandle handle = GCHandle.Alloc(
buffer,GCHandleType.Pinned);
Object temp = Marshal.PtrToStructure(
handle.AddrOfPinnedObject(), t);
handle.Free();
return temp;
}

You should recognise the use of the GCHandle object to enable the data to be transferred. The new feature is the use of a Type object to specify the type of the struct being read in. Unfortunately there is no way to use this to return an object of the specified type and so we need to use a cast when calling the function, as in:

FileStream fs = new FileStream(
@"c:\1.bmp",
FileMode.Open,
FileAccess.Read);
BITMAPFILEHEADER bmFH =(BITMAPFILEHEADER)
ReadStruct(fs,
typeof(BITMAPFILEHEADER));

If we want to avoid the cast then we need to create a generic method. This is just a matter of introducing a type parameter <T> and then using it throughout the method as if it was the type of the struct :

public T ReadStruct <T> (FileStream fs)
{
byte[] buffer = new byte
[Marshal.SizeOf(typeof( T ))];
fs.Read(
buffer,
0,
Marshal.SizeOf(typeof(T)));
GCHandle handle = GCHandle.Alloc(
buffer,GCHandleType.Pinned);
T temp = (T) Marshal.PtrToStructure(
handle.AddrOfPinnedObject(),
typeof(T));
handle.Free();
return temp;
}

Notice that now we have to cast the object returned by PtrToStructure to the type in the method rather than in the method call which becomes:

BITMAPFILEHEADER bmFH = 
ReadStruct <BITMAPFILEHEADER>(fs);

It is interesting to contemplate just how much better the generic method is than the method that needs the explicit cast.

Banner

<ASIN:1430229799>

<ASIN:0262201755>

<ASIN:0596800959>

<ASIN:047043452X>

<ASIN:193435645X>

<ASIN:0596007124>

Last Updated ( Tuesday, 28 September 2010 )
 
 

   
RSS feed of all content
I Programmer - full contents
Copyright © 2014 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.