使用C#和SendInput模拟按键事件

3

根据此主题的建议,我正在尝试创建一个程序,使用SendInput()方法发送键盘按键事件。

然而,问题是当我尝试模拟按键事件时,根本没有任何反应。目前我的代码如下:

    [DllImport("user32.dll")]
    static extern UInt32 SendInput(UInt32 nInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] INPUT[] pInputs, Int32 cbSize);

    [StructLayout(LayoutKind.Sequential)]
    struct KEYBDINPUT
    {
        public short wScan; public int dwFlags;
        public int time; public IntPtr dwExtraInfo;
    }

    [StructLayout(LayoutKind.Explicit)]
    struct INPUT
    {
        [FieldOffset(0)] public int type;
        [FieldOffset(8)]  public KEYBDINPUT ki; //x64 - 8, x32 - 4
    }

    const int KEYEVENTF_DOWN = 0; //key UP
    const int KEYEVENTF_EXTENDEDKEY = 0x0001;
    const int KEYEVENTF_KEYUP = 0x0002; //key UP
    const int KEYEVENTF_UNICODE = 0x0004;
    const int KEYEVENTF_SCANCODE = 0x0008; // scancode

    public void Send_Key(short Keycode)
    {
        INPUT[] InputData = new INPUT[1];
        InputData[0].type = 1;
        InputData[0].ki.wScan = Keycode;
        InputData[0].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE;
        InputData[0].ki.time = 0;
        InputData[0].ki.dwExtraInfo = IntPtr.Zero;
        SendInput(1, InputData, Marshal.SizeOf(typeof(INPUT)));
    }

为了解密扫描码,我下载了本帖中建议的程序:https://superuser.com/questions/293609/windows-7-tool-to-capture-keyboard-scan-codes。根据这个程序的显示,我的键盘上按下"a"键的代码是"65"。
我的程序被设计成在文本框文本更改时触发按键事件,所以如果我们输入"q",文本应该更改为"q"再加上send_key()的结果,即"qa":
    private void textBox2_TextChanged(object sender, EventArgs e)
    {
        Send_Key(0x0065); // nothing happens (0x65 also fails)
    }

我做错了什么?将来我会更改这段代码,以便可以指定按键保持时间(在DOWN和UP事件之间)。但是现在,出于测试目的,我只模拟按键抬起事件。

编辑:汉斯,根据您的建议进行了编辑-但不幸的是,它无法正常工作。

 public void Send_Key(short Keycode)
        {
            INPUT[] InputData = new INPUT[1];
            InputData[0].type = 1;
            InputData[0].ki.wScan = Keycode;
            InputData[0].ki.dwFlags = KEYEVENTF_DOWN;
            InputData[0].ki.time = 0;
            InputData[0].ki.dwExtraInfo = IntPtr.Zero;

            uint intReturn = SendInput(1, InputData, Marshal.SizeOf(typeof(INPUT)));

            if (intReturn == 0) //!=1
            {
                throw new Exception("Could not send keyDOWN: " + Keycode);
            }

            INPUT[] InputData2 = new INPUT[1];
            InputData2[0].type = 1;
            InputData2[0].ki.wScan = Keycode;
            InputData2[0].mi.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE;
            InputData2[0].ki.time = 0;
            InputData2[0].ki.dwExtraInfo = IntPtr.Zero;

            uint intReturn2 = SendInput(1, InputData2, Marshal.SizeOf(typeof(INPUT)));

            if (intReturn2 == 0) //!=1
            {
                throw new Exception("Could not send keyUP: " + Keycode);
            }
        }

没有错误,也没有输出。


在任何尝试打印字母失败后,当我关闭程序时 - 我无法更改 Google Chrome 中的 TAB !已经多次检查了这个问题 - 我的程序是原因!奇怪。。 - Alex
我不是100%确定,但我认为你不能直接从TextChanged中进行“追加”操作 - 你可能需要为此生成一个线程 - 但仍然可能不够。无论如何,为什么要在“自己的应用程序”中使用它?你正在使用win-forms吗?... - NSGaga-mostly-inactive
是的,这确实是WinForms。我通过完全更改代码(在某个论坛上找到的)并从此页面选择正确的扫描码来解决了我的问题:http://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx 例如,要打印“a”,我必须输入“30”或“0x1E”。 - Alex
1个回答

2
但目前只为了测试而模拟keyUP事件,这是可以的。但这不会让Windows做任何事情。要生成一个按键,您必须首先发送KEYDOWN消息。 "按住时间"没有任何有用的效果,因此只需将两个SendInput调用放在同一个方法中即可。

您应该添加错误检查,winapi调用不会生成任何异常。如果函数返回0,请抛出Win32Exception。并使用SetLastError = true修正[DLLImport]声明。

您可以通过设置ki.wVk字段而省略KEYEVENTF_SCANCODE标志以避免使用superuser.com上找到的那个程序。因此,您可以指定虚拟键而不是扫描代码。 .NET Keys枚举类型 给出了所需的值。例如,Keys.A可以生成A键的按键。


请参考编辑:添加了 keydown 事件,跳过了保持时间,添加了 SetLastError 和异常。关于扫描代码 - 如果可能的话,我想坚持使用它们。 - Alex
没有必要使用虚拟键,只是使用Keys枚举更简单。 - Hans Passant

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