Reading binary file into struct

2.4k views Asked by At

I know this has been answered but after reading the other questions I'm still with no solution. I have a file which was written with the following C++ struct:

typedef struct myStruct{
    char Name[127];
    char s1[2];
    char MailBox[149];
    char s2[2];
    char RouteID[10];
} MY_STRUCT;

My approach was to be able to parse one field at a time in the struct, but my issue is that I cannot get s1 and MailBox to parse correctly. In the file, the s1 field contains "\r\n" (binary 0D0A), and this causes my parsing code to not parse the MailBox field correctly. Here's my parsing code:

[StructLayout(LayoutKind.Explicit, Size = 0x80 + 0x2 + 0x96)]
unsafe struct MY_STRUCT
{
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x80)]
    public string Name;

    [FieldOffset(0x80)]
    public fixed char s1[2];

    /* Does not work, "Could not load type 'MY_STRUCT' ... because it contains an object field at offset 130 that is incorrectly aligned or overlapped by a non-object field." */
    [FieldOffset(0x80 + 0x2)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x96)]
    public string MailBox;
}

If I comment out the last field and reduce the struct's size to 0x80+0x2 it will work correctly for the first two variables.

One thing to note is that the Name and Mailbox strings contain the null terminating character, but since s1 doesn't have the null-terminating character it seems to be messing up the parser, but I don't know why because to me it looks like the code is explicitly telling the Marshaler that the s1 field in the struct is only a fixed 2-char buffer, not a null-terminated string.

Here is a pic of my test data (in code I seek past the first row in the BinaryReader, so "Name" begins at 0x0, not 0x10). enter image description here

3

There are 3 answers

9
Chuck Claunch On

Your struct sizes are not adding up correctly. The MailBox size is 0x95 as listed in MY_STRUCT, not 0x96 as you're calling it in the C# code.

0
Ðаո On

Here's one way, it doesn't use unsafe (nor is it particularly elegant/efficient)

using System.Text;
using System.IO;

namespace ReadCppStruct
{
/*
 typedef struct myStruct{
    char Name[127];
    char s1[2];
    char MailBox[149];
    char s2[2];
    char RouteID[10];
    } MY_STRUCT;
 */
class MyStruct
{
    public string Name { get; set; }
    public string MailBox { get; set; }
    public string RouteID { get; set; }
}

class Program
{
    static string GetString(Encoding encoding, byte[] bytes, int index, int count)
    {
        string retval = encoding.GetString(bytes, index, count);
        int nullIndex = retval.IndexOf('\0');
        if (nullIndex != -1)
            retval = retval.Substring(0, nullIndex);
        return retval;
    }

    static MyStruct ReadStruct(string path)
    {
        byte[] bytes = File.ReadAllBytes(path);

        var utf8 = new UTF8Encoding();

        var retval = new MyStruct();
        int index = 0; int cb = 127;
        retval.Name = GetString(utf8, bytes, index, cb);
        index += cb + 2;
        cb = 149;
        retval.MailBox = GetString(utf8, bytes, index, cb);
        index += cb + 2;
        cb = 10;
        retval.RouteID = GetString(utf8, bytes, index, cb);

        return retval;
    }       // http://stackoverflow.com/questions/30742019/reading-binary-file-into-struct
    static void Main(string[] args)
    {
        MyStruct ms = ReadStruct("MY_STRUCT.data");
    }
}
}
3
riqitang On

Here's how I got it to work for me:

    public static unsafe string BytesToString(byte* bytes, int len)
    {
        return new string((sbyte*)bytes, 0, len).Trim(new char[] { ' ' }); // trim trailing spaces (but keep newline characters)
    }

    [StructLayout(LayoutKind.Explicit, Size = 127 + 2 + 149 + 2 + 10)]
    unsafe struct USRRECORD_ANSI
    {
        [FieldOffset(0)]
        public fixed byte Name[127];

        [FieldOffset(127)]
        public fixed byte s1[2];

        [FieldOffset(127 + 2)]
        public fixed byte MailBox[149];

        [FieldOffset(127 + 2 + 149)]
        public fixed byte s2[2];

        [FieldOffset(127 + 2 + 149 + 2)]
        public fixed byte RouteID[10];
     }

After the struct has been parsed, I can access the strings by calling the BytesToString method, e.g. string name = BytesToString(record.Name, 127);

I did notice that I don't need the Size attribute in the StructLayout, I'm not sure if it's the best practice to keep it or remove it, ideas?