【问题标题】:C++ - Simulating keystrokes using SendInput method, failure to distinguish rightctrl keyC ++ - 使用SendInput方法模拟击键,无法区分rightctrl键
【发布时间】:2015-03-21 06:09:56
【问题描述】:

我在 C++ 方面的经验很少,而且我完全不熟悉 SendInput 方法。我已经通过注册表修改设置了我的笔记本电脑(带有英国键盘),以便在按住 right control 键并按两次 scroll lock 时创建故障转储。我正在尝试通过在 Visual C++ 2010 Express 中编译的 c++ 可执行文件以编程方式实现这一点。

使用这篇文章:how to use sendinput function C++ 作为我的灵感,我在下面创建了代码 sn-p。除了多个Cannot find or open the PDB 调试输出(通过阅读这篇文章:Error Message : Cannot find or open the PDB file 显然可以忽略),代码编译并运行。但是没有BSOD发生。我已经手动“强制”了 BSOD,所以我知道它有效。

请记住我是新手,请说明必须进行哪些更改才能使其正常工作?

#define WINVER 0x500
#include <windows.h>

int main()
{
INPUT ip;
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0;
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;

ip.ki.wVk = VK_RCONTROL;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = VK_SCROLL;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = VK_SCROLL;
ip.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = VK_SCROLL;
ip.ki.dwFlags = 0;
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = VK_SCROLL;
ip.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = VK_RCONTROL;
ip.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &ip, sizeof(INPUT));

return 0;
}

【问题讨论】:

  • 有一个名为 NotMyFault 的程序可以用来触发 BSOD。它是 Windows Internals 书籍的一部分,但似乎可以单独免费下载。
  • 谢谢大卫,我会研究这个......目前正在朝着编程启蒙的不同道路奋斗,但迟早会回到这个微型项目。 :D 问候!

标签: c++ visual-studio-2010 exe sendinput bsod


【解决方案1】:

以下是我编写的一个简单应用程序的相关代码,用于显示键入到应用程序中的键的虚拟键、扫描码、标志等。 (代码演示了创建列表框并处理WM_KEYDOWNWM_KEYUPWM_SYSKEYDOWNWM_SYSKEYUP 消息,然后显示参数:

void CChildView::ReportKey (UINT nChar, UINT nRepCnt, UINT nFlags)
{
    CString str;
    str.Format ( "%s Virtual key=%d; Scan code=%d Extended=%d AltDown=%d",
             (nFlags & 0x8000) ? "Up" : "DOWN",
             nChar, (nFlags & 0xFF), !!(nFlags & 0x0100), !!(nFlags & 0x2000) );
    AddString (str);
}


void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    ReportKey (nChar, nRepCnt, nFlags);
    CListBox::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CChildView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    ReportKey (nChar, nRepCnt, nFlags);
    CListBox::OnKeyUp(nChar, nRepCnt, nFlags);
}

void CChildView::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    ReportKey (nChar, nRepCnt, nFlags);
    CListBox::OnSysKeyDown(nChar, nRepCnt, nFlags);
}

void CChildView::OnSysKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    ReportKey (nChar, nRepCnt, nFlags);
    CListBox::OnSysKeyUp(nChar, nRepCnt, nFlags);
}

Right Control 键被按下,然后释放,当这个应用程序有键盘焦点时,它会显示:

DOWN Virtual key=17; Scan code=29 Extended=1 AltDown=0
Up Virtual key=17; Scan code=29 Extended=1 AltDown=0

奇怪的是,虚拟键“17”是0x11,根据this chartVK_CONTROL,而不是VK_RCONTROLExtended 标志为真。

Left Control键被按下并释放时,输出为:

DOWN Virtual key=17; Scan code=29 Extended=0 AltDown=0
Up Virtual key=17; Scan code=29 Extended=0 AltDown=0

因此,Windows 似乎永远不会看到VK_RCONTROL,而是看到VK_CONTROLExtended = true

所以尝试用它调用 SendInput():

INPUT ip[6];
...
ip[0].ki.wVk = VK_CONTROL;
ip[0].ki.wScan = MapVirtualKey(VK_CONTROL, 0);
ip[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY; 
....
ip[5].ki.wVk = VK_CONTROL;
ip[5].ki.wScan = MapVirtualKey(VK_CONTROL, 0);
ip[5].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY; 
SendInput(_countof(ip), &ip[0], sizeof(INPUT));

编辑:由于comment 而指定ip.ki.wScan

不使用 KEYEVENTF_SCANCODE 并不意味着 wScan 值将被忽略。如果您将 wScan 设置为 0,它不会,并且某些应用程序(例如 RDP-Client)可能会表现不同/错误。

编辑 2:我认为这并不重要,但最好只调用一次 SendInput,然后将一个 INPUT 结构数组传递给它以作为事务执行,因此所有击键都作为一个单元重放(例如,用户不能在你的中间穿插他自己的钥匙)。

编辑 3:您可以 download the application 显示输入的键。

【讨论】:

  • 非常感谢,非常有用,你在那里写的一个非常有用的应用程序。 VK_RCONTROL 被视为带有扩展标志的 0x11(如 VK_CONTROL)确实很奇怪,因为 VK_RCONTROL 的 MSDN 常量明确为 0xA3。我根据您建议的行动方案在 OP 中编辑了我的代码,但不幸的是仍然没有。我尝试了我的脚本来检查其他关键输入(字母数字)是否使用记事本工作 - 并确保我的代码结构正确 - 一切都成功了。
  • 在我的笔记本电脑上,必须按下 FN 键才能使 SCROLL LOCK 工作,这应该不会影响 SendInput 方法吧?
  • @Chris:当然,请确保将您的代码从 ip.ki.wVk = VK_RCONTROL 更改为 ip.ki.wVk = VK_CONTROL;。另请参阅我关于指定扫描码的新编辑。我认为Fn 键不会有任何区别,因为笔记本电脑硬件应该生成相同的普通 ScrollLock 键。但是您可能希望将您的组合键从 RCtrl+ScrollLock+ScrollLock 更改为其他更容易复制的组合以进行测试,例如Ctrl+F10。
  • 抱歉,我试过的应该是VK_CONTROL...将根据您对扫描代码的新编辑修改我的代码。
【解决方案2】:

此代码成功地将 RControl+ScrollLock+ScrollLock 填充到 ScanCode 应用程序中,但是,很抱歉,计算机不会像手动键入这些键时那样重新启动。

#define WINVER 0x0500
#include <windows.h>

int main()
{
    // Must specify INPUT_KEYBOARD for all INPUT structs
    INPUT ip[6] = { 
            { INPUT_KEYBOARD },
            { INPUT_KEYBOARD },
            { INPUT_KEYBOARD },
            { INPUT_KEYBOARD },
            { INPUT_KEYBOARD },
            { INPUT_KEYBOARD },
    };

    Sleep(3000);


    // Specify keys by scancode.  For the VK_SCROLL, it was necessary
    // to instead specify the wVK, otherwise VK==3 was received by ScanCode, instead
    // of VK_SCROLL == 145!
    //ip[0].ki.wVk = VK_CONTROL;
    ip[0].ki.wScan = MapVirtualKey(VK_RCONTROL, MAPVK_VK_TO_VSC);
    ip[0].ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY;

    ip[1].ki.wVk = VK_SCROLL;
    ip[1].ki.wScan = MapVirtualKey(VK_SCROLL, MAPVK_VK_TO_VSC);
    ip[1].ki.dwFlags = /*KEYEVENTF_SCANCODE*/ 0;

    ip[2].ki.wVk = VK_SCROLL;
    ip[2].ki.wScan = MapVirtualKey(VK_SCROLL, MAPVK_VK_TO_VSC);
    ip[2].ki.dwFlags = /*KEYEVENTF_SCANCODE |*/ KEYEVENTF_KEYUP;

    ip[3].ki.wVk = VK_SCROLL;
    ip[3].ki.wScan = MapVirtualKey(VK_SCROLL, MAPVK_VK_TO_VSC);
    ip[3].ki.dwFlags = /*KEYEVENTF_SCANCODE*/ 0;

    ip[4].ki.wVk = VK_SCROLL;
    ip[4].ki.wScan = MapVirtualKey(VK_SCROLL, MAPVK_VK_TO_VSC);
    ip[4].ki.dwFlags = /*KEYEVENTF_SCANCODE |*/ KEYEVENTF_KEYUP;

    //ip[5].ki.wVk = VK_CONTROL;
    ip[5].ki.wScan = MapVirtualKey(VK_RCONTROL, MAPVK_VK_TO_VSC);
    ip[5].ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;

    int i = _countof(ip);
    int numSuccessful = SendInput(i, ip, sizeof(INPUT));
    if (numSuccessful == i)
        printf("Stuffed successful.\n");
    else
    {
        printf("Succeeded with %d of %d; error %d\n", numSuccessful, i, GetLastError());
    }

    return 0;
}

我认为原因是 SendInput() 将按键注入到键盘驱动程序之上的层中,并且是键盘驱动程序监视这些击键以启动 BSOD。

【讨论】:

  • 我只能说,非常感谢你所有的时间、努力和善良...感谢你非常有用的应用程序的链接,我相信我会它可以很好地用于我的代码开发尝试。我想我会暂时搁置这个项目,也许在某个地方,有一天我会偶然发现一种成功模拟这个序列的方法......直到那时再次感谢您,先生是一位学者和绅士。
  • @Chris - 感谢您的客气话,很抱歉我们没有让它发挥作用。祝你下一部作品好运!
【解决方案3】:

@David Ching...这让我很沮丧...我已经阅读了所有相关文档(并试图理解它,记住我是这方面的新手),我尝试了无数次考虑到您的建议和我已阅读的内容,对代码进行排列。根据您的最后建议,下面的代码是我最后一次尝试但没有成功。我正在尝试确定哪些其他因素可能对该问题产生影响 - 硬件(笔记本电脑是东芝 Satellite L670D-10N)或操作系统(Windows 7 Ultimate - 带有英语语言包的西班牙语版本)会有所不同吗? - 我无法想象。我真的非常感谢您的帮助,请不要放弃帮助! - 顺便说一句,感谢您的应用链接。

#define WINVER 0x0500
#include <windows.h>

int main()
{
INPUT ip[6];

ip[0].ki.wVk = VK_CONTROL;
ip[0].ki.wScan = MapVirtualKey(VK_CONTROL, 0);
ip[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;

ip[1].ki.wVk = VK_SCROLL;
ip[1].ki.dwFlags = 0;

ip[2].ki.wVk = VK_SCROLL;
ip[2].ki.dwFlags = KEYEVENTF_KEYUP;

ip[3].ki.wVk = VK_SCROLL;
ip[3].ki.dwFlags = 0;

ip[4].ki.wVk = VK_SCROLL;
ip[4].ki.dwFlags = KEYEVENTF_KEYUP;

ip[5].ki.wVk = VK_CONTROL;
ip[5].ki.wScan = MapVirtualKey(VK_CONTROL, 0);
ip[5].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;
SendInput(_countof(ip), &ip[0], sizeof(INPUT));

return 0;
}

更新 - 成功的发送测试代码

#define WINVER 0x0500
#include <windows.h>

int main()
{
INPUT ip;

Sleep(3000);

ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0;
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;

ip.ki.wVk = 0x43;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x43;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x48;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x48;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x52;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x52;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x49;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x49;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x53;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x53;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x10;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x43;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x43;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x48;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x48;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x52;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x52;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x49;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x49;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x53;
ip.ki.dwFlags = 0; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x53;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

ip.ki.wVk = 0x10;
ip.ki.dwFlags = KEYEVENTF_KEYUP; 
SendInput(1, &ip, sizeof(INPUT));

return 0;
}

更新 - 不成功的修改 CTRL((SCROLL LOCK)X2) 代码

#define WINVER 0x0500
#include <windows.h>

int main()
{
INPUT ip[6] = {0};

Sleep(3000);

ip[0].ki.wVk = VK_CONTROL;
ip[0].ki.wScan = MapVirtualKey(VK_CONTROL, 0);
ip[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;

ip[1].ki.wVk = VK_SCROLL;
ip[1].ki.wScan = MapVirtualKey(VK_SCROLL, 0);
ip[1].ki.dwFlags = 0;

ip[2].ki.wVk = VK_SCROLL;
ip[2].ki.wScan = MapVirtualKey(VK_SCROLL, 0);
ip[2].ki.dwFlags = KEYEVENTF_KEYUP;

ip[3].ki.wVk = VK_SCROLL;
ip[3].ki.wScan = MapVirtualKey(VK_SCROLL, 0);
ip[3].ki.dwFlags = 0;

ip[4].ki.wVk = VK_SCROLL;
ip[4].ki.wScan = MapVirtualKey(VK_SCROLL, 0);
ip[4].ki.dwFlags = KEYEVENTF_KEYUP;

ip[5].ki.wVk = VK_CONTROL;
ip[5].ki.wScan = MapVirtualKey(VK_CONTROL, 0);
ip[5].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY;
SendInput(_countof(ip), &ip[0], sizeof(INPUT));

return 0;
}

【讨论】:

  • 我应该为每个数组成员定义:ip.ki.timeip.ki.dwExtraInfoip.ki.wScan吗?
  • 可以初始化为全零成员:INPUT ip[6] = {0}。是的,请尝试设置所有ip.ki.wScan = MapVirtualKey(...);ip[5].ki.wVk = VK_RCONTROL; 必须是 VK_CONTROLSendInput doc 表示“此功能受 UIPI 约束。应用程序只能将输入注入到具有相同或更低完整性级别的应用程序中。”。所以你可能需要让你的程序以管理员身份运行,这取决于接收击键的程序。
  • 我建议你把你的代码放到一个程序中,该程序设置一个大约 5 秒的计时器,然后执行你的代码。启动程序运行,然后单击并激活我的 ScanCode 应用程序(因为击键被发送到活动的应用程序)。当您的计时器触发时,它会将SendInput 击键输入我的 Scancode 应用程序,该应用程序将显示它们。确保它们是正确的。
  • 你好 David Ching... 所以我重新开始了,正如他们所说的那样,从头开始。首先,我下载了你的应用程序,我非常喜欢它,非常令人印象深刻!我在我的机器上对其进行了一些测试,所有键都被识别(除了FN 键本身)FN 组合按下被捕获,快捷输入也是如此......所以一切都很棒。接下来,我根据我在 OP 中的代码编写了一个简单的脚本,它基本上执行了以下操作:打开notepadsleep(3 秒)然后输入chrisspaceCHRISctrl+actrl+c@987654343 @ctrl+vmessagebox。这是在 VS C++ 2010 Express 中调试的,运行没有问题...
  • 接下来我用你的应用截取了脚本,它完美地捕捉到了一切! ...现在这就是奇怪的地方。当我尝试在此线程中尝试的各种方法以使我的 OP 代码工作(使用 VS 调试和构建 .exe)时,扫描码应用程序中没有出现任何内容。至少可以说这很奇怪。我拒绝放弃这个鬼魂……我做错了什么? ...我已经阅读了 MSDN 信息。触发 BSOD msdn.microsoft.com/en-us/library/windows/hardware/… - 我在这里看不到任何可以解释这一点的东西......
猜你喜欢
  • 2012-05-09
  • 2021-05-11
  • 2020-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-24
  • 2011-11-14
  • 2010-11-04
相关资源
最近更新 更多