16

我正在尝试为自定义游戏控制器应用程序模拟键盘命令。因为我需要在 DirectInput 环境中模拟命令,所以大多数常用方法都不起作用。我知道使用钩子可以 100% 工作,但我正试图找到一个更简单的实现。

我做了很多搜索,发现通过使用带有 Scancodes 而不是虚拟键的 SendInput API 应该可以工作,但它的行为似乎就像键是“粘着”的。我已经发送了 KEYDOWN 和 KEYUP 事件,但是当我尝试在 DirectInput 环境中发送消息时,游戏就像按键被按住一样。

例如,如果我模拟“W”键按下并将该键映射到第一人称射击游戏中的“向前移动”动作,一旦我进入游戏,下面的函数将导致角色向前移动。然而,只要发出一次命令,它就会无限期地向前移动角色。

这是我正在调用的 SendInput 函数的代码片段(在 C# 中):

[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());
    }
}

我不确定这种方法是否是一个失败的原因,或者我是否只是缺少一些愚蠢的东西。如果我不必使用钩子,我讨厌过度复杂化我的代码,但这对我来说也是一个新领域。

任何人都可以提供任何帮助,我们将不胜感激。

谢谢!

4

2 回答 2

22

我找到了解决我自己问题的方法。所以,我想我会在这里发帖以帮助将来可能遇到类似问题的任何人。

keyup 命令无法正常工作,因为仅发送扫描码时,keyup 必须与扫描码标志进行“或”运算(有效地启用两个标志),以告诉 SendInput() API 这既是 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

于 2010-09-05T21:54:25.923 回答
3

我试图模拟键盘以在 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]));
    }
于 2013-01-29T03:15:10.310 回答