Недавно мне пришлось решать задачу, в которой мне нужно было отправлять нажатия клавиш в другое приложение, которое запускается из приложения .NET Windows. Очевидно, что есть способ сделать это через WinAPI, и этот способ называется SendInput — основная функция, которая может использоваться для имитации нажатия клавиш, действий мыши и нажатий кнопок.
Давайте поговорим о том, что нужно. Прежде всего, вы используете его для отправки нажатий клавиш, а не символов, туда, где находится текущий фокус ввода. Так, например, если бы я захотел напечатать символ { на экране, я не смог бы просто получить его представление кода, потому что фактическая клавиша, которая несет его на стандартной клавиатуре QWERTY в США, была бы [ . Проблема, с которой я сталкивался несколько раз, заключается в том, что разработчик пытается использовать VkKeyScan для получения необходимого кода виртуальной клавиши для его последующей репликации, вместо этого он создает другой символ.
Теперь давайте посмотрим на его родную подпись:
UINT WINAPI SendInput(
_In_ UINT nInputs,
_In_ LPINPUT pInputs,
_In_ int cbSize
);
В ограничениях управляемой среды, в моем случае — в приложении C #, это объявление будет выглядеть так:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
Помните, поскольку вы используете P / Invoke, вам нужно добавить
public struct INPUT
{
public int type;
public InputBatch u;
}
Что именно представляет собой InputBatch? Если вы посмотрите на структуру структуры INPUT, вы заметите, что она опирается на объединение MOUSEINPUT , KEYBDINPUT и HARDWAREINPUT — структур, которые переносят данные, относящиеся к их собственному классу моделирования ввода. Базовая реализация этих в C # выглядит следующим образом:
[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;
}
В нашем случае мы фокусируемся на клавиатуре, поэтому давайте пройдемся по полям, которые мы используем в структуре KEYBDINPUT.
-
wVk — код виртуального ключа. Не так же, как код символа. Вы можете найти список кодов виртуальных клавиш.
-
wScan — код сканирования оборудования (см. спецификацию здесь ).
- dwFlags — дополнительные флаги, которые предопределяют поведение обработки ввода. Вы можете найти полный список на MSDN .
- время — если вам не нужно указывать другую временную метку, позвольте Windows предоставить свою собственную и установите значение 0.
- dwExtraInfo — будет связан с вызовом GetMessageExtraInfo .
Давайте предположим, что я хочу смоделировать нажатие клавиши Enter. Для этого мне сначала нужно сгенерировать массив структур INPUT, которые будут действовать как дескриптор ввода:
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(),
}
}
}
};
Обратите внимание, что я использую HEX-представление для Enter (это правильный связанный код виртуальной клавиши). Когда я хочу вызвать SendInput, я могу вызвать это:
WindowsAPI.SendInput((uint)data.Length, data, Marshal.SizeOf(typeof(WindowsAPI.INPUT)));
Marshal.SizeOf вернет значение размера структуры INPUT . Что произойдет, если мне нужно отправить комбинацию клавиш? Это легко реализовать с помощью нескольких экземпляров INPUT . Например, для Ctrl + F5 вы можете использовать это:
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;
}
Затем вызовите SendInput так же, как вы вызывали его для клавиши Enter. Здесь есть одна проблема. Когда вы собираетесь вызывать эту функцию, вы заметите, что ОС по- прежнему будет считать нажатой клавишу Ctrl . Чтобы избежать этого, вам нужно будет использовать KEYBDINPUT экземпляр с dwFlags для набора в KEYEVENTF_KEYUP отпустить клавишу.