I presented this on Thursday, March 12, at the Indianapolis .Net Developers Association C# SIG so I figured it was time to actually get this up on the blog. This is a follow up to Part One.
C# C-Style Unions For C# Developers
The SendInput method is defined as the following in the Windows Platform SDK documentation:
UINT SendInput(
UINT nInputs,
LPINPUT pInputs,
int cbSize
);
With a dependency on the INPUT structure, defined as the following in the Platform SDK:
typedef struct tagINPUT {
DWORD type;
union {MOUSEINPUT mi;
KEYBDINPUT ki;
HARDWAREINPUT hi;
};
}INPUT, *PINPUT;
INPUT consists of a DWORD value and a UNION of MOUSEINPUT, KEYBDINPUT and HARDWAREINPUT structures. The definition of those three structures is not important for our purposes, if you are curious follow them down the rabbit hole and see for yourself.
The way to translate this C structure into something useable in C# is to use the StructLayout attribute on your struct and to define the LayoutKind value as LayoutKind.Explicit. This leaves you with something like this for 32-bit architectures:
[StructLayout(LayoutKind.Explicit)] public struct INPUT { [FieldOffset(0)] public int type; [FieldOffset(4)] public MOUSEINPUT m; [FieldOffset(4)] public KBINPUT k; [FieldOffset(4)] public HWINPUT h; }
What this does is to tell the runtime that whatever gets written to either the mouse, keyboard or hardware input variables it should all start at the same point in memory when this structure is marshaled for interop. So they all point to the same memory space, just like in our C unions from part one. Pretty slick.
The issue with SendInput working fine on 32-bit Vista and 32-bit XP but just NOT working in 64-bit Vista ended up being caused by the length of a DWORD datatype in 64-bit architecture vs 32-bit architecture; it appears that on a 64-bit architecture the DWORD type has a length of 8 bytes, while on a 32-bit architecture it has a length of 4 bytes. I can’t find the documentary proof that I’m right on this point but it was proved to be the case through extensive testing and experimentation at my previous employer when trying to make this SendInput call work.
If I changed the definition of my input struct to the following everything magically started working on 64-bit architectures:
[StructLayout(LayoutKind.Explicit)] public struct INPUT { [FieldOffset(0)] public int type; [FieldOffset(8)] public MOUSEINPUT m; [FieldOffset(8)] public KBINPUT k; [FieldOffset(8)] public HWINPUT h; }
This is because on 64-bit architecture the SendInput starts looking at an offset that matches that of my structure for the INPUT information it needs to get the job done. The INPUT structure defined this way caused the test program to just NOT function in 32-bit XP and 32-bit Vista, but it worked flawlessly in 64-bit Vista.
I don’t currently have a 64-bit machine but I hear that Parallels Workstation and Desktop both support 64-bit guest OS now, so if you were interested in trying it an unlicensed copy of Vista 64-bit (before the activation grace period expires) and Parallels should get you up and running.
I wasn’t planning on dragging this out to three parts, but I think it fits. In Part Three I will victoriously present a pattern that allows your Any CPU compiled assembly to work in this case on both 32-bit and 64-bit architectures (and why that is even a goal worth pursuing)! Stay tuned to the Glorious Future.
Comments