2

I'm trying port the following C++ struct to C#.

#pragma pack(push,1)
struct FatBootSectorStruct {
    UCHAR  BS_jmpBoot[3];          // 0
    UCHAR  BS_OEMName[8];          // 3
    USHORT BPB_BytsPerSec;         // 11
    UCHAR  BPB_SecPerClus;         // 13
    USHORT BPB_RsvdSecCnt;         // 14
    UCHAR  BPB_NumFATs;            // 16
    USHORT BPB_RootEntCnt;         // 17
    USHORT BPB_TotSec16;           // 19
    UCHAR  BPB_Media;              // 21
    USHORT BPB_FATSz16;            // 22
    USHORT BPB_SecPerTrk;          // 24
    USHORT BPB_NumHeads;           // 26
    ULONG  BPB_HiddSec;            // 28
    ULONG  BPB_TotSec32;           // 32
    union {
        struct {
            UCHAR  BS_DrvNum;          // 36
            UCHAR  BS_Reserved1;       // 37
            UCHAR  BS_BootSig;         // 38
            ULONG  BS_VolID;           // 39
            UCHAR  BS_VolLab[11];      // 43
            UCHAR  BS_FilSysType[8];   // 54
            UCHAR  BS_Reserved2[448];  // 62
        } Fat16;
        struct {
            ULONG  BPB_FATSz32;        // 36
            USHORT BPB_ExtFlags;       // 40
            USHORT BPB_FSVer;          // 42
            ULONG  BPB_RootClus;       // 44
            USHORT BPB_FSInfo;         // 48
            USHORT BPB_BkBootSec;      // 50
            UCHAR  BPB_Reserved[12];   // 52
            UCHAR  BS_DrvNum;          // 64
            UCHAR  BS_Reserved1;       // 65
            UCHAR  BS_BootSig;         // 66
            ULONG  BS_VolID;           // 67
            UCHAR  BS_VolLab[11];      // 71
            UCHAR  BS_FilSysType[8];   // 82
            UCHAR  BPB_Reserved2[420]; // 90
        } Fat32;
    };
    USHORT Signature;              // 510
};

This is what I have:

[StructLayout(LayoutKind.Explicit, Size = 512, Pack=1)]
internal struct FATBootSector
{
    [FieldOffset(0)]
    public byte[] BS_jmpBoot; // 0
    [FieldOffset(3)]
    public byte[] BS_OEMName; // 3
    [FieldOffset(11)]
    public ushort BPB_BytsPerSec; // 11
    [FieldOffset(13)]
    public byte BPB_SecPerClus; // 13
    [FieldOffset(14)]
    public ushort BPB_RsvdSecCnt; // 14
    [FieldOffset(16)]
    public byte BPB_NumFATs; // 16
    [FieldOffset(17)]
    public ushort BPB_RootEntCnt; // 17
    [FieldOffset(19)]
    public ushort BPB_TotSec16; // 19
    [FieldOffset(21)]
    public byte BPB_Media; // 21
    [FieldOffset(22)]
    public ushort BPB_FATSz16; // 22
    [FieldOffset(24)]
    public ushort BPB_SecPerTrk; // 24
    [FieldOffset(26)]
    public ushort BPB_NumHeads; // 26
    [FieldOffset(28)]
    public ulong BPB_HiddSec; // 28
    [FieldOffset(32)]
    public ulong BPB_TotSec32; // 32

    // FAT16
    [FieldOffset(36)]
    public byte FAT16_BS_DrvNum; // 36
    [FieldOffset(37)]
    public byte FAT16_BS_Reserved1; // 37
    [FieldOffset(38)]
    public byte FAT16_BS_BootSig; // 38
    [FieldOffset(39)]
    public ulong FAT16_BS_VolID; // 39
    [FieldOffset(43)]
    public byte[] FAT16_BS_VolLab; // 43
    [FieldOffset(54)]
    public byte[] FAT16_BS_FilSysType; // 54
    [FieldOffset(62)]
    public byte[] FAT16_BS_Reserved2; // 62

    // FAT32
    [FieldOffset(36)]
    public ulong FAT32_BPB_FATSz32; // 36
    [FieldOffset(40)]
    public ushort FAT32_BPB_ExtFlags; // 40
    [FieldOffset(42)]
    public ushort FAT32_BPB_FSVer; // 42
    [FieldOffset(44)]
    public ulong FAT32_BPB_RootClus; // 44
    [FieldOffset(48)]
    public ushort FAT32_BPB_FSInfo; // 48
    [FieldOffset(50)]
    public ushort FAT32_BPB_BkBootSec; // 50
    [FieldOffset(52)]
    public byte[] FAT32_BPB_Reserved; // 52
    [FieldOffset(64)]
    public byte FAT32_BS_DrvNum; // 64
    [FieldOffset(65)]
    public byte FAT32_BS_Reserved1; // 65
    [FieldOffset(66)]
    public byte FAT32_BS_BootSig; // 66
    [FieldOffset(67)]
    public byte FAT32_BS_VolID; // 67
    [FieldOffset(71)]
    public byte[] BS_VolLab; // 71
    [FieldOffset(82)]
    public byte[] FAT32_BS_FilSysType; // 82
    [FieldOffset(90)]
    public byte[] FAT32_BPB_Reserved2; // 90

    [FieldOffset(510)]
    public ushort Signature;

}

The problem lies in field offset 0 and 3. When I try to load the structure in C#, I get Could not load type 'FATBootSector' from assembly 'xxxx' because it contains an object field at offset 3 that is incorrectly aligned or overlapped by a non-object field.. I came across this previous question on SO but that doesn't seem to help.

I've also tried changing it into a sequential structure like below:

[StructLayout(LayoutKind.Sequential, Size = 512, Pack=1, CharSet=CharSet.Ansi)]
internal struct FATBootSector
{

    [MarshalAs(UnmanagedType.U1)]
    public byte BS_jmpBoot0; // 0
    [MarshalAs(UnmanagedType.U1)]
    public byte BS_jmpBoot1; // 0
    [MarshalAs(UnmanagedType.U1)]
    public byte BS_jmpBoot2; // 0

    [MarshalAs(UnmanagedType.LPStr, SizeConst=8)]
    public string BS_OEMName; // 3
    [MarshalAs(UnmanagedType.U2)]
    public ushort BPB_BytsPerSec; // 11
    [MarshalAs(UnmanagedType.U1)]
    public byte BPB_SecPerClus; // 13
    [MarshalAs(UnmanagedType.U2)]
    public ushort BPB_RsvdSecCnt; // 14
    [MarshalAs(UnmanagedType.U1)]
    public byte BPB_NumFATs; // 16
    [MarshalAs(UnmanagedType.U2)]
    public ushort BPB_RootEntCnt; // 17
    [MarshalAs(UnmanagedType.U2)]
    public ushort BPB_TotSec16; // 19
    [MarshalAs(UnmanagedType.U1)]
    public byte BPB_Media; // 21
    [MarshalAs(UnmanagedType.U2)]
    public ushort BPB_FATSz16; // 22
    [MarshalAs(UnmanagedType.U2)]
    public ushort BPB_SecPerTrk; // 24
    [MarshalAs(UnmanagedType.U2)]
    public ushort BPB_NumHeads; // 26
    [MarshalAs(UnmanagedType.U4)]
    public ulong BPB_HiddSec; // 28
    [MarshalAs(UnmanagedType.U4)]
    public ulong BPB_TotSec32; // 32

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 474)]
    public byte[] FAT1632Info;

    [MarshalAs(UnmanagedType.U2)]
    public ushort Signature;

}

And I'm using the following code to get the structure:

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool ReadFile(IntPtr hFile, [Out] IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [In] ref System.Threading.NativeOverlapped lpOverlapped);

public bool GetPartitionDetails()
{
        uint BytesRead;

        IntPtr BootSectorPtr = Marshal.AllocHGlobal(512);
        PInvoke.FATBootSector BootSector;

        System.Threading.NativeOverlapped Overlapped = new System.Threading.NativeOverlapped();

        bool ret = PInvoke.ReadFile(this.Handle, BootSectorPtr, (uint)512, out BytesRead, ref Overlapped);

        BootSector = (PInvoke.FATBootSector)Marshal.PtrToStructure(BootSectorPtr, typeof(PInvoke.FATBootSector)); // causes access violation

        return true;
}

I'm hoping I don't have to, but the only alternative I can think of is iterating through the memory using the IntPtr. Any ideas?

Community
  • 1
  • 1
SameOldNick
  • 2,397
  • 24
  • 33
  • Does `public byte[] BS_jmpBoot;` declare an array of unknown size, or a pointer to an array, in C#? – user253751 Jun 03 '14 at 06:49
  • I've tried specifying the size using ``MarshalAs``, fixed byte, and as shown above, splitting it up into 3 separate bytes. – SameOldNick Jun 03 '14 at 06:53

2 Answers2

2

There are quite a few problems here. First of all you are abusing FieldOffset. You use that when the compiler cannot layout the struct for you. Invariably that is when you have a union. Don't use FieldOffset here. And certainly don't specify the size up front. Again, let the compiler do that. Check it against the native version.

Then you handle the arrays incorrectly. You don't need fixed, but you do need to say how long the arrays are. The start of the struct should look like this:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct FATBootSector
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] BS_jmpBoot;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] BS_OEMName;
    ....
}

The rest of your problems are caused by erroneous type conversions. ULONG in C++ is a 4 byte unsigned type. That's uint in C#. You using ulong which is 8 bytes wide.

Here's a version of the struct which will at least avoid the access violations:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct FATBootSector
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] BS_jmpBoot; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] BS_OEMName; 
    public ushort BPB_BytsPerSec;
    public byte BPB_SecPerClus; 
    public ushort BPB_RsvdSecCnt;
    public byte BPB_NumFATs; 
    public ushort BPB_RootEntCnt;
    public ushort BPB_TotSec16; 
    public byte BPB_Media; 
    public ushort BPB_FATSz16;
    public ushort BPB_SecPerTrk;
    public ushort BPB_NumHeads;
    public uint BPB_HiddSec; 
    public uint BPB_TotSec32; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 474)]
    public byte[] FAT1632Info;
    public ushort Signature;
}

To translate the FAT16/FAT32 union, you may need to use fixed. Or alternatively have two FATBootSector types. One for FAT16 and one for FAT32.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
0

I decided to use the IntPtr to fill out the structure fields. The following code walks through the memory and get each field.

    internal struct FATBootSector
    {
        public byte[] BS_jmpBoot; // 0
        public string BS_OEMName; // 3
        public ushort BytesPerSector; // 11
        public byte SectorsPerCluster; // 13
        public ushort ReservedSectors; // 14
        public byte NumberOfFATs; // 16
        public ushort RootEntries; // 17
        public ushort TotalSectors16; // 19
        public byte MediaDescriptor; // 21
        public ushort SectorsPerFAT; // 22
        public ushort SectorsPerTrack; // 24
        public ushort Heads; // 26
        public uint HiddenSectors; // 28
        public uint TotalSectors32; // 32

        public FAT1632Info FAT1632Info;

        public ushort Signature;

        public FATBootSector(IntPtr ptr)
        {
            int i;

            this.BS_jmpBoot = new byte[3];

            for (i = 0; i < 3; i++)
            {
                this.BS_jmpBoot[i] = Marshal.ReadByte(ptr);
                ptr = IntPtr.Add(ptr, 1);
            }

            StringBuilder oemInfo = new StringBuilder(8);

            for (i = 0; i < 8; i++)
            {
                char c = (char)Marshal.ReadByte(ptr);
                oemInfo.Append(c);
                ptr = IntPtr.Add(ptr, 1);
            }

            this.BS_OEMName = oemInfo.ToString();

            this.BytesPerSector = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);

            this.SectorsPerCluster = Marshal.ReadByte(ptr);
            ptr = IntPtr.Add(ptr, 1);

            this.ReservedSectors = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);

            this.NumberOfFATs = Marshal.ReadByte(ptr);
            ptr = IntPtr.Add(ptr, 1);

            this.RootEntries = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);

            this.TotalSectors16 = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);

            this.MediaDescriptor = Marshal.ReadByte(ptr);
            ptr = IntPtr.Add(ptr, 1);

            this.SectorsPerFAT = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);

            this.SectorsPerTrack = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);

            this.Heads = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);

            this.HiddenSectors = (uint)Marshal.ReadInt32(ptr);
            ptr = IntPtr.Add(ptr, 4);

            this.TotalSectors32 = (uint)Marshal.ReadInt32(ptr);
            ptr = IntPtr.Add(ptr, 4);

            this.FAT1632Info = new PInvoke.FAT1632Info(ptr);
            ptr = IntPtr.Add(ptr, 474);

            this.Signature = (ushort)Marshal.ReadInt16(ptr);
            ptr = IntPtr.Add(ptr, 2);
        }
    }

    [StructLayout(LayoutKind.Sequential, Size=474, Pack=1)]
    internal struct FAT1632Info
    {
        // FAT16
        public byte FAT16_LogicalDriveNumber; // 36
        public byte FAT16_Reserved1; // 37
        public byte FAT16_ExtendedSignature; // 38
        public uint FAT16_PartitionSerialNumber; // 39
        public string FAT16_VolumeName; // 43
        public string FAT16_FSType; // 54
        public byte[] FAT16_Reserved2; // 62

        // FAT32
        public uint FAT32_SectorsPerFAT32; // 36
        public ushort FAT32_ExtFlags; // 40
        public ushort FAT32_FSVer; // 42
        public uint FAT32_RootDirStart; // 44
        public ushort FAT32_FSInfoSector; // 48
        public ushort FAT32_BackupBootSector; // 50
        public byte[] FAT32_Reserved1; // 52
        public byte FAT32_LogicalDriveNumber; // 64
        public byte FAT32_Reserved2; // 65
        public byte FAT32_ExtendedSignature; // 66
        public uint FAT32_PartitionSerialNumber; // 67
        public string FAT32_VolumeName; // 71
        public string FAT32_FSType; // 82
        public byte[] FAT32_Reserved3; // 90

        public FAT1632Info(IntPtr ptr)
        {
            int i;
            IntPtr startPtr = ptr;

            // FAT 16
            this.FAT16_LogicalDriveNumber = Marshal.ReadByte(ptr); // 0
            ptr = IntPtr.Add(ptr, 1);

            this.FAT16_Reserved1 = Marshal.ReadByte(ptr); // 1
            ptr = IntPtr.Add(ptr, 1);

            this.FAT16_ExtendedSignature = Marshal.ReadByte(ptr); // 2
            ptr = IntPtr.Add(ptr, 1);

            this.FAT16_PartitionSerialNumber = (uint)Marshal.ReadInt32(ptr); // 3
            ptr = IntPtr.Add(ptr, 4);

            StringBuilder volName16 = new StringBuilder(11);

            for (i = 0; i < 11; i++)
            {
                char c = (char)Marshal.ReadByte(ptr);
                volName16.Append(c);

                ptr = IntPtr.Add(ptr, 1);
            }

            this.FAT16_VolumeName = volName16.ToString();

            StringBuilder fileSystemType16 = new StringBuilder(8);

            for (i = 0; i < 8; i++)
            {
                char c = (char)Marshal.ReadByte(ptr);
                fileSystemType16.Append(c);

                ptr = IntPtr.Add(ptr, 1);
            }

            this.FAT16_FSType = fileSystemType16.ToString();

            this.FAT16_Reserved2 = new byte[448];

            for (i = 0; i < 448; i++)
            {
                this.FAT16_Reserved2[i] = Marshal.ReadByte(ptr);
                ptr = IntPtr.Add(ptr, 1);
            }

            // FAT32
            ptr = startPtr;

            this.FAT32_SectorsPerFAT32 = (uint)Marshal.ReadInt32(ptr); // 36, 4
            ptr = IntPtr.Add(ptr, 4);

            this.FAT32_ExtFlags = (ushort)Marshal.ReadInt16(ptr); // 40, 2
            ptr = IntPtr.Add(ptr, 2);

            this.FAT32_FSVer = (ushort)Marshal.ReadInt16(ptr); // 42, 2
            ptr = IntPtr.Add(ptr, 2);

            this.FAT32_RootDirStart = (uint)Marshal.ReadInt32(ptr); // 44, 4
            ptr = IntPtr.Add(ptr, 4);

            this.FAT32_FSInfoSector = (ushort)Marshal.ReadInt16(ptr); // 48, 2
            ptr = IntPtr.Add(ptr, 2);

            this.FAT32_BackupBootSector = (ushort)Marshal.ReadInt16(ptr); // 50, 2
            ptr = IntPtr.Add(ptr, 2);

            this.FAT32_Reserved1 = new byte[12];  // 52, 12

            for (i=0;i<12;i++) 
            {
                this.FAT32_Reserved1[i] = Marshal.ReadByte(ptr);
                ptr = IntPtr.Add(ptr, 1);
            }

            this.FAT32_LogicalDriveNumber = (byte)Marshal.ReadByte(ptr); // 64, 1
            ptr = IntPtr.Add(ptr, 1);

            this.FAT32_Reserved2 = (byte)Marshal.ReadByte(ptr); // 65, 1
            ptr = IntPtr.Add(ptr, 1);

            this.FAT32_ExtendedSignature = (byte)Marshal.ReadByte(ptr); // 66, 1
            ptr = IntPtr.Add(ptr, 1);

            this.FAT32_PartitionSerialNumber = (uint)Marshal.ReadInt32(ptr); // 67, 1
            ptr = IntPtr.Add(ptr, 4);

            StringBuilder volName32 = new StringBuilder(11); // 71, 11

            for (i = 0; i < 11; i++)
            {
                char c = (char)Marshal.ReadByte(ptr);
                volName32.Append(c);
                ptr = IntPtr.Add(ptr, 1);
            }

            this.FAT32_VolumeName = volName32.ToString();

            StringBuilder fileSystemType32 = new StringBuilder(8);   // 82, 8

            for (i = 0; i < 8; i++)
            {
                char c = (char)Marshal.ReadByte(ptr);
                fileSystemType32.Append(c);
                ptr = IntPtr.Add(ptr, 1);
            }

            this.FAT32_FSType = fileSystemType32.ToString();

            this.FAT32_Reserved3 = new byte[420]; // 90, 420

            for (i = 0; i < 420; i++)
            {
                this.FAT32_Reserved3[i] = Marshal.ReadByte(ptr);
                ptr = IntPtr.Add(ptr, 1);
            }
        }
    }

I do however like the good explanation left by David on why it doesn't work as it should so I will accept that as the answer and leave this here for the future when somebody else is running into the same problem I am having trying to read the FAT boot sector in C#.

SameOldNick
  • 2,397
  • 24
  • 33