1

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

riqitang
  • 3,241
  • 4
  • 37
  • 47

3 Answers3

1

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");
    }
}
}
Ðаn
  • 10,934
  • 11
  • 59
  • 95
1

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?

riqitang
  • 3,241
  • 4
  • 37
  • 47
  • Another interesting thing I noticed is that if I add a single char attribute to the struct (the struct in the OP is an incomplete version of the entire struct) then declaring it as `public char aChar;` instead of `public byte aChar;` causes the rest of the strings to not be parsed correctly. So the moral of the story is that it's really easy to screw up reading a binary file into a struct if the offsets and types aren't exactly right. The safest thing seems to be to read in the data as byte arrays and to convert those byte arrays. – riqitang Jun 10 '15 at 14:31
  • In this case unsafe is fine because the data is guaranteed to be formatted in that specific way (and if it's not then the customer has way bigger problems). Thanks for the explanation, it make sense now why I was having trouble with `char`s. – riqitang Jun 11 '15 at 12:26
  • According to this answer (http://stackoverflow.com/a/584157/747275) it seems like unsafe would be appropriate because I'm reading structures from disk – riqitang Jun 11 '15 at 12:31
0

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.

Chuck Claunch
  • 1,624
  • 1
  • 17
  • 29
  • indeed, though correcting that still doesn't seem to solve my "incorrectly aligned or overlapped by a non-object field" problem :\ Actually I had 0x96 in there to account for the null character – riqitang Jun 09 '15 at 20:56
  • It seems the name field is the same way. Should be 0x7F instead of 0x80, correct? – Chuck Claunch Jun 09 '15 at 20:58
  • 0x80 to include the null character, which gets written to the file in the C++ code – riqitang Jun 09 '15 at 21:00
  • 1
    You shouldn't need that. You're reading straight binary, not a C++ string. – Chuck Claunch Jun 09 '15 at 21:01
  • You're right, I'll need to look at this more tomorrow when I get back, I'm curious to see if it is an offsets and length problem. – riqitang Jun 09 '15 at 21:04
  • You can clearly see in the binary where the words start and that may help figure out where your offsets are wrong. – Chuck Claunch Jun 09 '15 at 21:07
  • even with the corrections it doesn't work, I keep getting an error that says the file I'm reading into the struct "contains an object field at offset 129 that is incorrectly aligned or overlapped by a non-object field." – riqitang Jun 10 '15 at 12:05
  • interestingly when I change the second variable (s1) to be a string by changing the code to "[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] public string s1;" it crashes and gives me the same error message except it says that the incorrectly aligned field is at offset 127. Something tells me that marshaling is going to not be possible because of that field. – riqitang Jun 10 '15 at 12:11
  • I found a hacky solution where I set all the fields in the struct to bytes and then convert the bytes to strings – riqitang Jun 10 '15 at 12:31