在DirectInput应用程序中使用SendInput API模拟键盘

16

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

我已经进行了相当多的搜索,并发现使用SendInput API与Scancodes而不是虚拟键应该可以解决问题,但似乎会出现按键“粘滞”的情况。我已经发送了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());
    }
}

我不确定这种方法是否行不通,或者是否只是有些愚蠢的地方我忽略了。如果不使用钩子(hooks)让代码变得过于复杂,我会很不情愿的,但这也是我不熟悉的领域。

非常感谢任何人能够给予的帮助。

谢谢!


7
INPUT[]是什么?那应该来自于"user32.dll"吗? - Taylor Lopez
2个回答

22

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

由于只发送扫描码时,keyup命令无法正常工作,因此必须将keyup与扫描码标志进行或运算(有效地启用两个标志),以告诉SendInput() API这是一个KEYUP和SCANCODE命令。

例如,以下代码将正确发出扫描码的keyup:

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)))

感谢Hans的回复。经过一番调查,像原始示例一样连续发送两条消息确实可以模拟“键按下”,但速度非常快。这对于移动命令不起作用,但在需要“轻敲”操作键而不是长按下时则会很理想。

此外,在发送扫描码时虚拟键字段将被忽略。MSDN对此有以下说明:

“设置KEYEVENTF_SCANCODE标志以根据扫描码定义键盘输入。无论当前使用的是哪个键盘,这都很有用,以模拟物理按键。键的虚拟键值可能会随着当前键盘布局或其他按下的键而改变,但扫描码始终相同。”

http://msdn.microsoft.com/en-us/library/ms646271%28v=VS.85%29.aspx


3
在您提供的解决方案中,您提到了一个DirectInputKeyScanCode,但我不知道如何使用它,您能帮我吗? - Fede
你能否提供一个完整的按下和松开键的解决方案?我不确定我做错了什么,以及我需要在你发送的第一段代码片段中替换什么。谢谢! - Stefan Vasiljevic

3

我正在尝试在Flash中模拟键盘以进行演示,但也遇到了同样的问题。它可以用于像记事本这样的应用程序,但对于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]));
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接