Some structures can be difficult to marshal, as their size varies on x86 compared to x64:
typedef struct _TBBUTTON { int iBitmap; int idCommand; BYTE fsState; BYTE fsStyle; #ifdef _WIN64 BYTE bReserved[6] // padding for alignment #elif defined(_WIN32) BYTE bReserved[2] // padding for alignment #endif DWORD_PTR dwData; INT_PTR iString; } TBBUTTON, NEAR *PTBBUTTON *LPTBBUTTON;
The layouts in x86 and x64 would look like this:
In C the standard is for, say, an int to stay within a 4 byte boundary of the start of the structure. A short would not cross a 2 byte boundary. Bytes can go where they like (well, they shouldn't cross the byte boundary, which I guess they could by having bits spread over 2 bytes!)
A pointer on x86 is a 4 byte value, and shouldn't cross a 4 byte boundary. A pointer on x64 is an 8 byte value, and shouldn't cross an 8 byte boundary. Phew. Enough about byte boundaries...
TBBUTTON causes problems as it has the two BYTE fields in the middle. We need to pad, to get the dwData field
to start at a suitable location. Once it is in the correct place, iString will be fine.
With .Net we have a few ways to arrange structures. We can use StructLayout(LayoutKind.Sequential, Pack:=x).
Sequential means that .Net is not allowed to move the fields around at runtime, we want them to be arranged
in memory in the order that they appear in our declaration, this will match the C behaviour. Pack allows you to
specify the boundary to align the fields against. Often you would use sequential and pack = 1, then add fields of
padding to get it aligned.
Alternatively, you can use StructLayout(LayoutKind.Explicit) and hard-code where the fields should go. But,
this will only work if the structures have the same layout in x86 and x64. (Unfortunately you must use a
constant for the field offset, so you can't calculate it at runtime).
For TBBUTTON we are stuffed. There is no arrangement of pack and padding so that "one structure fits all", we will
need 2 declarations. And then we'll choose between them at runtime. (I think it might also be possible to just
compile it for x86 and run it under WOW64 on x64...)
<StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Structure TBBUTTON32 Public bitmapIndex As Integer Public command As Integer Public state As TBStates Public style As TBStyles Public padding As UShort Public data As IntPtr Public iString As IntPtr Public Function Size() As Integer Return Marshal.SizeOf(Me) End Function End Structure <StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Structure TBBUTTON64 Public bitmapIndex As Integer Public command As Integer Public state As TBStates Public style As TBStyles Public padding1 As Integer Public padding2 As UShort Public data As IntPtr Public iString As IntPtr Public Function Size() As Integer Return Marshal.SizeOf(Me) End Function End Structure
The function is there for convenience. The 6 bytes of padding in the 64 bit version are spread across two variables.
We could equally use field offsets, but it takes a bit longer as you have to calculate them all. Unless you brain finds
that easy. My brain prefers arranging them in blocks and having padding bytes, and doesn't consider the numbers.
One thing that you think might work is the conditional compile thingy in VB.Net:
#if Platform="x86"
but this depends on the build target, it's not determined at runtime. So again, you would end up with two builds.
Actually, you could be sneaky and use an IntPtr for all three fields - state, style and padding. Marshalled as SysUint. Then convert it later.
ReplyDelete