Packing structures with Mono or how to fix alignment problems for P/Invoke
This is basically a[nother] note for myself.
I have just spent a while wondering why the size of some data structures in C# was wrong while writing bindings to the CD-ROM control features of the FreeBSD libc.
Considering the following code snippet:
struct msf { public byte unused; public byte minute; public byte second; public byte frame; }; [StructLayout (LayoutKind.Explicit)] struct msf_lba { [FieldOffset (0)] public msf msf; [FieldOffset (0)] public int lba; [MarshalAs (UnmanagedType.ByValArray, SizeConst = 4)] [FieldOffset (0)] public byte [] addr; }; struct cd_toc_entry { byte unused1; public byte control_addr_type; public byte track; byte unused2; public msf_lba addr; }; struct cd_sub_channel_media_catalog { public byte data_format; public int mc_valid; [MarshalAs (UnmanagedType.ByValArray, SizeConst = 15)] public byte [] mc_number; };
We can compute the size of each structure (MSDN page about types size):
- msf
- A msf structure contains 4 members each of 1 byte long so the whole structure is 4 bytes;
- msf_lba
- This is not more than the union of a msf structure (4 bytes), an int (4 bytes) and an array of 4 byte (4 bytes), so it's 4 bytes long too;
- cd_toc_entry
- A cd_toc_entry structure is composed of an unused byte (1 byte), 2 data byte (1 byte + 1 byte), another unused byte (1 byte) and a msf_lba structure (4 bytes) so it is 1 + 1 + 1 + 1 + 4 = 8 bytes long;
- cd_sub_channel_media_catalog
- A cd_sub_channel_media_catalog structure starts with 1 byte (1 byte), followed by an int (4 bytes), and an array of 15 byte (15 bytes). The whole structure is therefore 1 + 4 + 15 = 20 bytes long.
These results can be checked using Marshal.SizeOf:
Marshal.SizeOf (typeof (msf)) = 4 Marshal.SizeOf (typeof (msf_lba)) = 4 Marshal.SizeOf (typeof (cd_toc_entry)) = 8 Marshal.SizeOf (typeof (cd_sub_channel_media_catalog)) = 24
So the cd_sub_channel_media_catalog structure it 4 bytes long more that expected. This really sounds like alignment problems in C. A quick search on the Interop with Native Libraries page on Novell's wiki does not give any info about such a problem. Switching the two first fields order fix the size (but of course breaks the workability) so it is definitively an alignment problem that takes place here.
After some search, I could find the StructLayoutAttribute.Pack Field (actually I could not find this piece of information using the MSDN search engine, I had to search on the web similar problems and find this one with a reference to Pack
to find this page in the Microsoft .NET documentation).
Finally, the solution is to write this:
[StructLayout (LayoutKind.Sequential, Pack = 1)] struct cd_sub_channel_media_catalog { public byte data_format; public int mc_valid; [MarshalAs (UnmanagedType.ByValArray, SizeConst = 15)] public byte [] mc_number; };
The MusicBrainzSharp port to FreeBSD can go on!