Sending Keystrokes to Other Apps with Windows API and C#
Recently I had to tackle a task where I needed to send keystrokes to another application, that are initiated from a .NET Windows app.
Join the DZone community and get the full member experience.
Join For FreeRecently I had to tackle a task where I needed to send keystrokes to another application, that are initiated from a .NET Windows app. Obviously, there is a way to do it through WinAPI, and the way is called SendInput - a core function that can be used to simulate key presses, mouse actions and button clicks.
Let's talk about what's needed. First of all, you are using it to send keystrokes, and not characters, to wherever the current input focus is located. So, for example, if I would want to print the { symbol on the screen, I would not be able to just get it's code representation because the actual key that carries it on a standard US QWERTY keyboard would be [. The problem that I've seen happen more than once is a developer attempting to leverage VkKeyScan to get the necessary virtual-key code to replicate it later, it producing another character instead.
Now let's look at the native signature for it:
UINT WINAPI SendInput(
_In_ UINT nInputs,
_In_ LPINPUT pInputs,
_In_ int cbSize
);
In the constraints of a managed environment, in my case - in a C# application, this declaration would look like this:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
Remember, since you are using P/Invoke, you need to add a
public struct INPUT
{
public int type;
public InputBatch u;
}
What exactly is InputBatch? If you look at the INPUT structure layout, you will notice that it relies on a union of MOUSEINPUT, KEYBDINPUT and HARDWAREINPUT - structs that are carrying data related to their own class of input simulation. A basic implementation of those in C# looks like this:
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct HARDWAREINPUT
{
public uint uMsg;
public ushort wParamL;
public ushort wParamH;
}
In our case, we are focusing on the keyboard, so let's go through the fields that we're using in the KEYBDINPUT struct.
wVk - the virtual key code. Not the same as the character code. You can find a list of virtual key codes
wScan - the hardware scan code (read the spec here).
- dwFlags - complementary flags that pre-determine input processing behavior. You can find the complete list on MSDN.
- time - unless you need to specify a different timestamp, let Windows provide its own and set the value to 0.
- dwExtraInfo - will be associated with a call to GetMessageExtraInfo.
Let's assume that I want to simulate the Enter key press. To do this, I first need to generate an array of INPUT structs that would act as the input descriptor:
WindowsAPI.INPUT[] data = new WindowsAPI.INPUT[] {
new WindowsAPI.INPUT()
{
type = WindowsAPI.INPUT_KEYBOARD,
u = new WindowsAPI.InputBatch
{
ki = new WindowsAPI.KEYBDINPUT
{
wVk = 0x0D,
wScan = 0,
dwFlags = 0,
dwExtraInfo = WindowsAPI.GetMessageExtraInfo(),
}
}
}
};
Notice that I am using the HEX representation for Enter (that's the proper associated virtual-key code). When I want to invoke SendInput, I can call this:
WindowsAPI.SendInput((uint)data.Length, data, Marshal.SizeOf(typeof(WindowsAPI.INPUT)));
Marshal.SizeOf will return the value for the size of the INPUT struct. What happens if I need to send a key combination? This is easily implemented through multiple INPUT instances. For example, for Ctrl+F5 you could use this:
public static WindowsAPI.INPUT[] Find()
{
WindowsAPI.INPUT[] data = new WindowsAPI.INPUT[] {
new WindowsAPI.INPUT()
{
type = WindowsAPI.INPUT_KEYBOARD,
u = new WindowsAPI.InputBatch
{
ki = new WindowsAPI.KEYBDINPUT
{
wVk = 0xA2,
wScan = 0,
dwFlags = 0,
dwExtraInfo = WindowsAPI.GetMessageExtraInfo(),
}
}
},
new WindowsAPI.INPUT()
{
type = WindowsAPI.INPUT_KEYBOARD,
u = new WindowsAPI.InputBatch
{
ki = new WindowsAPI.KEYBDINPUT
{
wVk = (ushort)WindowsAPI.VkKeyScan('f'),
wScan = 0,
dwFlags = 0,
dwExtraInfo = WindowsAPI.GetMessageExtraInfo(),
}
}
}
};
return data;
}
Afterwards, call SendInput the same way as you called it for the Enter key. There is one problem here, though. As you are going to call this function, you will notice that the OS will still consider the Ctrl key pressed. To avoid this, you would need to use a KEYBDINPUT instance with dwFlags for set to KEYEVENTF_KEYUP to release the key.
Opinions expressed by DZone contributors are their own.
Comments