Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preferred way to parse a custom binary flat file?

I have a flat file generated by a C program. Each record in the file consists of a fixed length header followed by data. The header contains a field indicating the size of the following data. My ultimate goal is to write a C#/.NET program to query this flat file, so I'm looking for the most efficient way to read the file using C#.

I am having trouble finding the .NET equivalent of line 7 in the following code. As far as I can tell, I have to issue multiple reads (one for each field of the header using BinaryReader) and then issue one read to get the data following the header. I'm trying to learn a way to parse a record in two read operations (one read to get the fixed length header and a second read to get the following data).

This is the C code I am trying to duplicate using C#/.NET:

struct header header; /* 1-byte aligned structure (48 bytes) */
char *data;

FILE* fp = fopen("flatfile", "r");
while (!feof(fp))
{
  fread(&header, 48, 1, fp);
  /* Read header.length number of bytes to get the data. */
  data = (char*)malloc(header.length);
  fread(data, header.length, 1, fp);
  /* Do stuff... */
  free(data);
}

This is C structure of the header:

struct header
{
    char  id[2];
    char  toname[12];
    char  fromname[12];
    char  routeto[6];
    char  routefrom[6];
    char  flag1;
    char  flag2;
    char  flag3;
    char  flag4;
    char  cycl[4];
    unsigned short len;
};

I've come up with this C# object to represent the C header:

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi, Size = 48)]
class RouterHeader
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    char[] Type;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    char[] To;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    char[] From;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    char[] RouteTo;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    char[] RouteFrom;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    char[] Flags;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    char[] Cycle;

    UInt16 Length;
}
like image 873
Trevor Balcom Avatar asked Aug 21 '10 15:08

Trevor Balcom


1 Answers

Well, you can use one call to Stream.Read to read the length (although you need to check the return value to make sure you've read everything you've asked for; you may not get it all in one go) and then another call to Stream.Read to get the data itself into a byte array (again, looping until you've read anything). Once it's all in memory, you can pick out the appropriate bytes from the buffer to create an instance of your struct (or class).

Personally I prefer to do all of this explicitly rather than using StructLayout - the latter always feels somewhat brittle to me.

like image 147
Jon Skeet Avatar answered Oct 04 '22 20:10

Jon Skeet