C++中的SendInput()与手动按键不相等吗?

11

我想编写一个C++代码,模拟按下键盘上的"A"键:

// Set up a generic keyboard event.
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0; // hardware scan code for key
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;

// Press the "..." key
ip.ki.wVk = code; // virtual-key code for the "a" key
ip.ki.dwFlags = 0; // 0 for key press
SendInput(1, &ip, sizeof(INPUT));

// Release the "..." key
ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
SendInput(1, &ip, sizeof(INPUT));

当我启动其他程序并等待我的程序执行时,单击"A"按钮,第一个程序对其做出反应,这很好用。但是我发现在其他应用程序中,我的操作被某种方式阻止了(我可以手动按键盘上的"A",但是使用我的程序不会引起任何动作)。

那么,我该怎么做才能让程序中按下"A"的效果更像手动按下的"A"(使第二个程序无法识别它是从程序中调用的)?

我没有第二个程序的源代码,也不知道它如何识别手动按下的"A"。

我确定我想要响应我的代码窗口是前台窗口,可以接收和阻止我的按键(因此它可以决定事件不是来自用户而是来自程序)。


1
SendInput 的发明是为了解决在单独调用 keybd_event 发送单个键盘消息时出现的问题。不要滥用 SendInput,通过多次使用单个 INPUT 结构来调用它。应该建立一个 INPUT 结构数组并一次性发送它们。 - IInspectable
我也使用了相同结果的keybd_event。仍然感谢您指出一些技巧。 - PolGraphic
除非您提供有关“第二个程序”的更多详细信息,否则我怀疑这个问题不会有任何进展。 - Hans Passant
OldNewThing上有一些相关的帖子可能会引起兴趣... - Bhargav
据我所知,另一个程序似乎总是检查发送者的hwnd。这是一种保护措施。 - Maher
3个回答

26

您可以使用SendInput()发送硬件扫描码(而不是虚拟扫描码,这可能会被DirectInput忽略)。这个函数的文档不太好,但SendInput()确实可以绕过DirectInput。Eric的解决方案之所以不起作用是因为他设置了硬件扫描码,但最终使用的却是虚拟扫描码(通过将dwFlags设置为0并将wVk设置为非零值)。

基本上,要进行按键操作,您需要设置:

ip.ki.dwFlags = KEYEVENTF_SCANCODE;

要释放键,设置:

ip.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
下面是一个完整的工作样例,它打印字母“a”。您可以在此处找到其他扫描码
#define WINVER 0x0500
#include <windows.h>

using namespace std;

int main()
{

    //Structure for the keyboard event
    INPUT ip;

    Sleep(5000);

    //Set up the INPUT structure
    ip.type = INPUT_KEYBOARD;
    ip.ki.time = 0;
    ip.ki.wVk = 0; //We're doing scan codes instead
    ip.ki.dwExtraInfo = 0;

    //This let's you do a hardware scan instead of a virtual keypress
    ip.ki.dwFlags = KEYEVENTF_SCANCODE;
    ip.ki.wScan = 0x1E;  //Set a unicode character to use (A)

    //Send the press
    SendInput(1, &ip, sizeof(INPUT));

    //Prepare a keyup event
    ip.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
    SendInput(1, &ip, sizeof(INPUT));



    return 0;
}

注意:您可以通过向SendInput()传递一个INPUT结构数组来组合按键(例如,shift + a表示A)。


1
那么,也可以通过这种方式模拟鼠标点击吗? - Dave Chandler
使用 ip.type = INPUT_MOUSE。没错。 - David
好的。但我需要传递什么作为wScan参数?有一个代表鼠标点击的Unicode字符吗? - Dave Chandler
2
尽管我知道得更清楚,但我还是在搜索所需按键的unicode字符而不是扫描码。希望这个链接能够帮助别人避免暂时的愚蠢:http://www.philipstorr.id.au/pcbook/book3/scancode.htm。 - JR Smith
你可以使用Win32的MapVirtualKey/Ex()API来获取虚拟键码的扫描码,反之亦然。 - Remy Lebeau
显示剩余2条评论

5

您经常需要设置扫描代码:

// Set up a generic keyboard event.
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = MapVirtualKey(code, MAPVK_VK_TO_VSC); // hardware scan code for key
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;

// Press the "..." key
ip.ki.wVk = code; // virtual-key code for the "a" key
ip.ki.dwFlags = 0; // 0 for key press
SendInput(1, &ip, sizeof(INPUT));

如IInspectable所建议的那样构建一个数组绝对是正确的做法。


谢谢你的建议。不幸的是,它也没有起作用。我想编写游戏机器人,但仅在聊天窗口中我的程序有效(只有在我通过单击激活该窗口后才能将字符放入聊天窗口),但在游戏中(关闭聊天)键停止工作(我相信这是某种过滤器决定输入来自程序而不是用户)。 - PolGraphic
@PolGraphic 这个游戏可能正在使用DirectInput来获取键盘输入。DirectInput直接与驱动程序通信,这使得注入变得相当棘手。这个游戏是否区分小写字母A和大写字母A? - Eric Brown
当我使用WSAD移动角色时(我无法通过代码实现,只能手动按键盘上的键),它在游戏聊天窗口中(我可以通过我的代码影响)不区分大小写,但是在游戏聊天窗口中会有所区别。 - PolGraphic
这意味着游戏很可能在使用DirectInput。注入仍然是可能的,但要困难得多;你需要打开原始键盘设备并发送一些IOCTL来注入原始扫描码。我对这些API不是很熟悉,所以你需要自己解决... - Eric Brown

1
如果你想要创建一个游戏机器人,你有没有考虑过使用AutoHotKey这个程序呢? http://www.autohotkey.com/ 它提供了一种脚本语言,可以让你完成很多涉及到“机器人”创建的任务,而且比试图用C++完成要容易得多。
(当我所有的家人都向我施压要我创建一个账户时,它确实为我玩了Farmville)

如果游戏是3D的话,您可能需要查看这个 - http://www.autohotkey.com/board/topic/63664-solved-imagesearch-failure-wdirectx-fullscreen-gamewin7/ - Strings
这些应用程序当然很棒。但大多数被游戏识别后会导致游戏关闭,甚至更糟的是会让你被封号。所以我更喜欢编写自己的应用程序,它们永远不会被识别。 - Evren Ozturk
1
游戏(或任何应用程序)很容易区分“虚假”的应用程序生成输入和“真实”的硬件输入。无论你如何尝试,但如果他们不想接受模拟输入,那么它们就不会接受模拟输入,没有其他的。 - Remy Lebeau

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