November 9, 2009

Packing structures with Mono or how to fix alignment problems for P/Invoke

Categories: FreeBSD, Mono.

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!

No Comments Yet

Comments RSS feed | Leave a Reply…

top