Monday, February 7, 2011

Reading a Structure from a Stream

Here is a nice way to read a data structure from a stream.



I created a generic method to help reading structured data from a Stream

It is easy to implement as an Extension method on the base type Stream.



public T ReadStruct(this Stream stream) where T : struct
{
var size = Marshal.SizeOf(typeof(T));

byte[] buffer = new byte[size];
stream.Read(buffer, 0, size);

var ptr = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var obj = (T)Marshal.PtrToStructure(ptr.AddrOfPinnedObject(), typeof(T));
ptr.Free();

return obj;
}


This way it may for example help to read an ID3Tag of an MP3 file

As an example I will show you how to read this ID3Tag



But first I have to explain a few things about Structures in C#,

because there are a few ways to do this.

1. LayoutKind.Explicit

this way you can explicitly set the Offset of each field.

It wouldn't make it easy for you to read array's of a ValueType because the array is instantiated, so you will be able to read only the first char for example.


[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct HeaderData
{
[FieldOffset(0)]
public char[] TYPECODE;
[FieldOffset(100)]
public Int32 Code;
}



2. LayoutKind.Sequential and fixed fields

This way you'll be able to define fixed array's in .Net

There is only one catch that still makes it a bit difficult to access these arrays

This is marking an field as fixed will actually only give you a pointer to this array.


[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct HeaderDataFixed
{
public fixed char TYPECODE[4];
public Int32 Code;
}

Declaring fixed char TYPECODE[4]; will give you char*



3. LayoutKind.Sequential and MashalAs

This is the way I think will fit our needs the best and for no other reason I used in my sample code below.




[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct tagID3TAG
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public char[] tag;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
public char[] songname;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
public char[] artist;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
public char[] album;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public char[] year;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
public char[] comment;

public char genre;
}

public tagID3TAG ReadID3Tag(string filename)
{
using (var stream = File.OpenRead(filename))
{
var size = Marshal.SizeOf(typeof(tagID3TAG));

stream.Seek(-size, SeekOrigin.End);

byte[] buffer = new byte[size];
stream.Read(buffer, 0, size);

var ptr = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var obj = (tagID3TAG)Marshal.PtrToStructure(ptr.AddrOfPinnedObject(), typeof(tagID3TAG));
ptr.Free();

return obj;
}
}



Well, that will be it for this great little article.

Hope you find good use for it, if anyone needs help or would like me to explain how to do certain thing in .Net, please don't hesitate to contact me :-)

No comments:

Post a Comment