Thursday, October 18, 2007

Unmanaged Structures, Padding and C#, Part 2

Continuing on from my previous post, we find that we have problems when we try to use our C# structure on a 64bit environment due to the way structures are padded in memory. The other problem that this will lead to is that using an Explicit layout in the structure will also no longer work as the exact layout of the structure in memory will now vary depending on whether or not we are in a 32bit or 64bit environment.

There are two solutions to this problem. You can either create a 32bit and 64bit version of your structure, and probibly make a 32bit and 64bit version of your application, or you can employ a more cunning approach. You probibly do not want to create two versions of your structure, especially if you were not planning on making two versions of your application, so from here on we will look at the approach of making a structure that is compatible with both 32 bit and 64 bit applications.

The first problem that we need to solve is the union part of the C structure. What we will do at this stage is take the biggest part of the union. In a 32 bit environment they are both the same size, but in a 64bit environment the structure with the pointer will be bigger, so we will use this.

The first step is to go ahead and create this internal structure.

[StructLayout(LayoutKind.Sequential)]
public struct PRINTER_NOTIFY_INFO_DATA_DATA
{
public uint cbBuf;
public IntPtr pBuf;
}
Following this we now should create a structure to represent the union clause. As mentioned previously because unions involve overlapped data we need to use an explicit layout. The other problem we will get is if we try to set the array of DWORDs adwData to overlap with the PRINTER_NOTIFY_INFO_DATA_DATA structure. This problem occurs because the .Net compiler treats the contained structure as a value type, and the contained array as a reference type, and can not handle them bothe being at the same location in the structure. To get around this I have created a couple of private fields to read the elements of the array and a public property to provide the expected public interface.

[StructLayout(LayoutKind.Explicit)]
public struct PRINTER_NOTIFY_INFO_DATA_UNION
{
[FieldOffset(0)]
private uint adwData0;
[FieldOffset(4)]
private uint adwData1;
[FieldOffset(0)]
public PRINTER_NOTIFY_INFO_DATA_DATA Data;
public uint[] adwData
{
get
{
return new uint[] { this.adwData0, this.adwData1
};

}
}
Now we create the PRINTER_NOTIFY_INFO_DATA structure, and include the above structure in. Note that we are also no longer defining the structure explicitly as we want it to size correctly in both 32nit and 64bit environments.

[StructLayout(LayoutKind.Sequential)]
public struct PRINTER_NOTIFY_INFO_DATA
{
public ushort Type;
public ushort Field;
public uint Reserved;
public uint Id;
public PRINTER_NOTIFY_INFO_DATA_UNION NotifyData;
}
This will now layout correctly in 32bit and 64bit environments and still provide access to all of the fields defined in the C structure. The new and correct in memory layout of a PRINTER_NOTIFY_INFO structure followed by two PRINTER_NOTIFY_INFO_DATA structure in a 64 bit environment is as follows.

0x0000 PRINTER_NOTIFY_INFO.Version
0x0004 PRINTER_NOTIFY_INFO.Flags
0x0008 PRINTER_NOTIFY_INFO.Count
0x000c Padding as PRINTER_NOTIFY_INFO_DATA must start on a 8 byte boundary.
0x0010 PRINTER_NOTIFY_INFO_DATA[0].Type
0x0012 PRINTER_NOTIFY_INFO_DATA[0].Field
0x0014 PRINTER_NOTIFY_INFO_DATA[0].Reserved
0x0018 PRINTER_NOTIFY_INFO_DATA[0].Id
0x001c Padding as PRINTER_NOTIFY_INFO_DATA_DATA must start on a 8 byte boundary.
0x0020 PRINTER_NOTIFY_INFO_DATA[0].Data.cbBuf
0x0024 Padding as a 8 byte Pointer must be on a 8 byte boundary.
0x0028 PRINTER_NOTIFY_INFO_DATA[0].Data.pBuf
0x0030 PRINTER_NOTIFY_INFO_DATA[0].Type
0x0032 PRINTER_NOTIFY_INFO_DATA[0].Field
0x0034 PRINTER_NOTIFY_INFO_DATA[0].Reserved
0x0038 PRINTER_NOTIFY_INFO_DATA[0].Id
0x003c Padding as PRINTER_NOTIFY_INFO_DATA_DATA must start on a 8 byte boundary.
0x0040 PRINTER_NOTIFY_INFO_DATA[0].Data.cbBuf
0x0044 Padding as a 8 byte Pointer must be on a 8 byte boundary.
0x0048 PRINTER_NOTIFY_INFO_DATA[0].Data.pBuf

No comments: