Имитация клавиатуры с помощью SendInput API в приложениях DirectInput
Я пытаюсь смоделировать команды клавиатуры для пользовательского приложения игрового контроллера. Потому что мне нужно имитировать команды в среде DirectInput, большинство обычных методов не работают. Я знаю, что использование хука будет работать на 100%, но я пытаюсь найти более простую реализацию.
Я провел немало поисков и обнаружил, что использование API SendInput со скан-кодами вместо виртуальных ключей должно работать, но, похоже, он ведет себя так, будто ключи "залипают". Я отправил события KEYDOWN и KEYUP, но когда я пытаюсь отправить сообщение в среде DirectInput, игра ведет себя так, как будто клавиша удерживается нажатой.
Например, если я имитирую нажатие клавиши "W" и сопоставляю эту клавишу в шутере от первого лица с действием "двигаться вперед", как только я в игре, приведенная ниже функция заставит персонажа двигаться вперед. Однако, просто выполнив команду один раз, он продвинет персонажа вперед на неопределенное время.
Вот фрагмент кода (в C#) для функции SendInput, которую я вызываю:
[DllImport("user32.dll")]
static extern UInt32 SendInput(UInt32 nInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] INPUT[] pInputs, Int32 cbSize);
public static void Test_KeyDown()
{
INPUT[] InputData = new INPUT[2];
Key ScanCode = Microsoft.DirectX.DirectInput.Key.W;
InputData[0].type = 1; //INPUT_KEYBOARD
InputData[0].wScan = (ushort)ScanCode;
InputData[0].dwFlags = (uint)SendInputFlags.KEYEVENTF_SCANCODE;
InputData[1].type = 1; //INPUT_KEYBOARD
InputData[1].wScan = (ushort)ScanCode;
InputData[1].dwFlags = (uint)(SendInputFlags.KEYEVENTF_KEYUP | SendInputFlags.KEYEVENTF_UNICODE);
// send keydown
if (SendInput(2, InputData, Marshal.SizeOf(InputData[1])) == 0)
{
System.Diagnostics.Debug.WriteLine("SendInput failed with code: " +
Marshal.GetLastWin32Error().ToString());
}
}
Я не уверен, что этот метод - безнадежное дело, или я просто скучаю по глупости. Я ненавижу чрезмерно усложнять мой код, если мне не нужно использовать хуки, но это также новая территория для меня.
Любая помощь, которую кто-либо может оказать, высоко ценится.
Спасибо!
2 ответа
Я нашел решение своей проблемы. Итак, я решил опубликовать здесь, чтобы помочь любому, кто может иметь подобные проблемы в будущем.
Команда keyup не работала должным образом, потому что при отправке только кода сканирования ключ должен быть ИЛИ с флагом кода сканирования (эффективно включающим оба флага), чтобы сообщить API SendInput (), что это и KEYUP, и SCANCODE команда.
Например, следующий код будет правильно выдавать код сканирования ключа:
INPUT[] InputData = new INPUT[1];
InputData[0].Type = (UInt32)InputType.KEYBOARD;
//InputData[0].Vk = (ushort)DirectInputKeyScanCode; //Virtual key is ignored when sending scan code
InputData[0].Scan = (ushort)DirectInputKeyScanCode;
InputData[0].Flags = (uint)KeyboardFlag.KEYUP | (uint)KeyboardFlag.SCANCODE;
InputData[0].Time = 0;
InputData[0].ExtraInfo = IntPtr.Zero;
// Send Keyup flag "OR"ed with Scancode flag for keyup to work properly
SendInput(1, InputData, Marshal.SizeOf(typeof(INPUT)))
Спасибо Гансу за ответ. Я провел некоторое исследование и отправил два сообщения подряд, как в оригинальном примере действительно имитирует "нажатие клавиш", но это очень быстро. Это не будет хорошо работать для команды движения, но будет идеальным, когда клавиши действия должны быть "нажаты" и не удерживаются.
Кроме того, поле виртуального ключа игнорируется при отправке кода сканирования. MSDN сказал по этому поводу следующее:
"Установите флаг KEYEVENTF_SCANCODE, чтобы определить ввод с клавиатуры в терминах кода сканирования. Это полезно для имитации физического нажатия клавиши независимо от того, какая клавиатура используется в данный момент. Значение виртуальной клавиши может изменяться в зависимости от текущей раскладки клавиатуры или того, что другие клавиши были нажаты, но код сканирования всегда будет таким же. "
http://msdn.microsoft.com/en-us/library/ms646271%28v=VS.85%29.aspx
Я пытался смоделировать клавиатуру для презентации во Flash, и у меня тоже была такая же проблема. Он работал для приложений, таких как Блокнот, но не для флэш-памяти. После нескольких часов поисков в Интернете я наконец заставил это работать:
public static void GenerateKey(int vk, bool bExtended)
{
INPUT[] inputs = new INPUT[1];
inputs[0].type = INPUT_KEYBOARD;
KEYBDINPUT kb = new KEYBDINPUT(); //{0};
// generate down
if ( bExtended )
kb.dwFlags = KEYEVENTF_EXTENDEDKEY;
kb.wVk = (ushort)vk;
inputs[0].ki = kb;
SendInput(1, inputs, System.Runtime.InteropServices.Marshal.SizeOf(inputs[0]));
// generate up
//ZeroMemory(&kb, sizeof(KEYBDINPUT));
//ZeroMemory(&inputs,sizeof(inputs));
kb.dwFlags = KEYEVENTF_KEYUP;
if ( bExtended )
kb.dwFlags |= KEYEVENTF_EXTENDEDKEY;
kb.wVk = (ushort)vk;
inputs[0].type = INPUT_KEYBOARD;
inputs[0].ki = kb;
SendInput(1, inputs, System.Runtime.InteropServices.Marshal.SizeOf(inputs[0]));
}