【问题标题】:How is the LPARAM of PostMessage() constructed?PostMessage() 的 LPARAM 是如何构造的?
【发布时间】:2019-02-11 20:36:51
【问题描述】:

我在使用 PostMessage() 的参数 LPARAM 时遇到问题。

这是使用 PostMessage() 键入字母 z 的一个广泛可用的示例:

PostMessage(handle, WM_KEYDOWN, 0x5A, 0x002C0001) // key down for z
PostMessage(handle, WM_KEYUP, 0x5A, 0xC02C0001) // key up for z

到达LPARAM“0x002C0001”键按下和“0xC02C0001”键按下的公式是什么?我想为所有键复制它。 是否可以创建两个函数,例如 CreateLPARAM_KeyDown() 和 CreateLPARAM_KeyUp(),您只需传递扫描码——或者更好的是,独立于设备的虚拟键码——然后它们返回 LPARAM?

使用 keybd_event() 更容易,您只需将 dwFlags 参数 0 用于按键按下,并使用 KEYEVENTF_KEYUP 按键向上,但窗口必须具有焦点,并且我要发送到的窗口在后台,所以 keybd_event () 和 SendInput() 在我的情况下没有用。

【问题讨论】:

  • 您不能通过发布此类消息来可靠地伪造输入。 SendInput 是您所需要的。无论这个问题在这里被问多少次,这始终是答案。
  • David Heffernan 这是一个普遍的观点,但它是一个有缺陷的概括。我发布到的窗口接收消息没有问题,我测试过,你甚至可以发送组合键,例如shift 键向下,字母向下/向上,shift 键向上,您将获得所需的输出。如果这些 API(PostMessage 和 SendMessage)没用,它们就不会存在。同样,这取决于接收消息的应用程序。

标签: c++ winapi postmessage


【解决方案1】:

LPARAMWPARAM 的含义因正在处理的特定消息而异。这就是为什么PostMessage 的文档不能对这些参数过于具体,只说明:

其他特定于消息的信息。

两者都有。要准确了解它们对每条消息的含义,您需要查看该消息的文档。

对于您询问的消息WM_KEYUPWM_KEYDOWNLPARAM 的值表示:

重复计数、扫描码、扩展键标志、上下文码、前一个键状态标志和转换状态标志,如下表所示。 (来源#1#2

Bits    Meaning
0-15    The repeat count for the current message. The value is the number of times the keystroke is autorepeated as a result of the user holding down the key. 
16-23   The scan code. The value depends on the OEM.
24      Indicates whether the key is an extended key, such as the right-hand ALT and CTRL keys that appear on an enhanced 101- or 102-key keyboard. The value is 1 if it is an extended key; otherwise, it is 0.
25-28   Reserved; do not use.
29      The context code. 
30      The previous key state. 
31      The transition state.

让我们看看你的WM_KEYDOWN LPARAM 的位:

0x002C0001

0b0000000000101100000000000001

设置的位是 21、19、18 和 0。这告诉我们:

重复次数为 1

剩下的位是z的扫描码,明显是0b00101100或者0x2C。

WM_KEYUP 消息的 LPARAM 值为 0xC02C0001,它仅在最重要的 nybble 处有所不同,给我们:

0b1100000000101100000000000001

所以,这里唯一的区别是前一个状态位和转换状态位都是 1,这对于 WM_KEYUP 消息是有保证的。

关于你的其他问题:

是否可以创建两个函数,例如 CreateLPARAM_KeyDown() 和 CreateLPARAM_KeyUp(),您只需传递扫描码?

当然。查看MapVirtualKey 以确定如何从密钥代码中获取扫描代码,并使用位操作从中构造一个 32 位 LPARAM 以及您从上表中知道的关于必须设置的位的所有其他信息这些消息。您将需要使用位移和其他位操作来完成此操作,因为扫描代码是一个 8 位字节,存储为 32 位 LPARAM 的一部分。

【讨论】:

  • 我正在考虑使用 MapVirtualKey() 将虚拟键码(设备无关)映射到键盘的扫描码(可能会有所不同),并使用获得的扫描码创建LPARAM 用于按键和按键。看起来很简单,但我仍然不知道如何为给定的扫描码创建按键和按键的 LPARAM。
  • @John 这将需要位移和其他位操作。如果您对如何执行此操作有疑问,最好再问一个问题。
【解决方案2】:

是否可以创建两个函数,例如 CreateLPARAM_KeyDown() 和 CreateLPARAM_KeyUp(),您只需传递扫描码(或者更好的是,独立于设备的虚拟键码),然后它们返回 LPARAM?

试试这样的:

std::pair<USHORT, bool> VirtualKeyToScanCode(UINT VirtualKey)
{
    USHORT ScanCode = (USHORT) MapVirtualKey(VirtualKey, MAPVK_VK_TO_VSC);
    bool IsExtended = false;

    // because MapVirtualKey strips the extended bit for some keys
    switch (VirtualKey)
    {
        case VK_RMENU: case VK_RCONTROL:
        case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: // arrow keys
        case VK_PRIOR: case VK_NEXT: // page up and page down
        case VK_END: case VK_HOME:
        case VK_INSERT: case VK_DELETE:
        case VK_DIVIDE: // numpad slash
        case VK_NUMLOCK:
        {
            IsExtended = true;
            break;
        }
    }

    return std::make_pair(ScanCode, IsExtended);
}

LPARAM CreateLPARAM_KeyUpDown(UINT VirtualKey, USHORT RepeatCount, bool TransitionState, bool PreviousKeyState, bool ContextCode)
{
    std::pair<USHORT, bool> ScanCode = VirtualKeyToScanCode(VirtualKey);
    return (
        (LPARAM(TransitionState) << 31) |
        (LPARAM(PreviousKeyState) << 30) |
        (LPARAM(ContextCode) << 29) |
        (LPARAM(ScanCode.second) << 24) |
        (LPARAM(ScanCode.first) << 16) |
        LPARAM(RepeatCount)
    );
}

LPARAM CreateLPARAM_KeyDown(UINT VirtualKey, USHORT RepeatCount = 1)
{
    return CreateLPARAM_KeyUpDown(VirtualKey, RepeatCount, false, RepeatCount > 1, false);
}

LPARAM CreateLPARAM_KeyUp(UINT VirtualKey)
{
    return CreateLPARAM_KeyUpDown(VirtualKey, 1, true, true, false);
}

【讨论】:

  • 它运行良好,包括 [ CTRL |移位 | ALT ] + 组合键,谢谢!只是一个问题:我读过一些键盘甚至不生成扫描码,所以如果我选择虚拟键码并且我的键盘没有键,Windows 会生成扫描码吗?我的理解是 Windows 可以做到这一点,所以理论上使用 PostMessage 和你提供的功能,使用 official list of virtual key codes 意味着它总是可以工作,还是我错了?
  • @John MapVirtualKey() 如果无法返回扫描码,则返回 0。
【解决方案3】:

我建议为此使用SendInput API。

所以你只需要填写相应的 KEYBDINPUT 结构,这是有据可查的。

【讨论】:

  • 我编辑了我的问题,SendInput() 无法使用,因为它需要焦点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-20
  • 1970-01-01
  • 2016-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多