【问题标题】:Keyboard Hook problems键盘挂钩问题
【发布时间】:2023-03-13 08:44:01
【问题描述】:

我正在做一个使用一键通键的语音聊天应用程序。我已经做了一个钩子,所以它也会在应用程序之外注册一键通。

HHOOK hHook = SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC)pushtotalk,0,0);



LRESULT CALLBACK pushtotalk(int key, WPARAM wParam,LPARAM lParam) {
if (key < 0) {
    return (CallNextHookEx(hook,key,wParam,lParam));
}
else if (connected) {
    KBDLLHOOKSTRUCT* kbdll  = (KBDLLHOOKSTRUCT*)lParam;
    if (kbdll ->vkCode == 75 && wParam == WM_KEYDOWN) {
        MessageBox(mainhWnd,"KEYSTART","KEYSTART",0);
    }
    else if (kbdll ->vkCode == 75 && wParam == WM_KEYUP) {
        MessageBox(mainhWnd,"KEYSTOP","KEYSTOP",0);

    }
}

return (CallNextHookEx(hook,key,wParam,lParam));
}

问题;

1) 有时,(例如应用程序中第一次执行 proc),proc 在继续之前会导致系统冻结 5 秒。为什么?

2) 该挂钩仅适用于在我的应用程序启动之前启动的进程,如果我在启动应用程序后启动文本程序,则挂钩不会注册。有解决办法吗?

3) 如果我按住键约 3 秒,很多 MessageBoxes 显然会显示,但在那之后,proc 将永远不会注册另一个被按下的键,所以我想我不知何故与钩子链断开了连接?

干杯

编辑:这是应用程序的主要消息循环

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
            case ID_MENU_EXIT:
                SendMessage(hWnd,WM_CLOSE,0,0);
                break;

            case ID_MENU_PREFERENCES:
                voiceManager->send((void*) "1");
                break;

            case ID_BUTTON_CONNECT:
                onConnect(hWnd);
                break;

            case ID_BUTTON_DISCONNECT:
                onDisconnect(hWnd);
                break;

            case ID_BUTTON_SEND:
                onSendText(hWnd);
                break;

            default:
                break;
        }
        break;
    case SOCKET_TCP:
        switch (lParam) {
            case FD_READ:
                {
                // Disable repeated FD_READ call while we process message 
                WSAAsyncSelect(wParam,hWnd,SOCKET_TCP,   FD_WRITE | FD_ACCEPT  | FD_CLOSE);

                // first four bytes is packet size
                // second four bytes are used to identify type of msg
                char* psize = (char*)malloc(5);
                char* ptype = (char*)malloc(5);
                psize[4] = '\0';
                ptype[4] = '\0';

                recv(wParam,psize,4,0);
                recv(wParam,ptype,4,0);

                // allocate memory for the buffer 
                int size_to_recv = atoi(psize);      
                char* textbuff = (char*)malloc(size_to_recv);

                // receive 
                int i = size_to_recv;
                while (i > 0) {
                    int read = recv(wParam,textbuff,i,0);
                    i = i - read;
                }

                // handle msg depending on type
                switch(identifyMsg(ptype)) {
                    case 1:
                        // handle 'text' msg
                        onReadText(hWnd,textbuff);
                        break;

                    case 2:
                        // handle 'name' msg
                        onReadName(hWnd,textbuff);
                        break;
                    case 3:
                        // handle 'list' msg
                        onReadList(hWnd,textbuff);
                        break;
                    case 4:
                        // handle 'remv' msg
                        onReadRemv(hWnd,textbuff,size_to_recv);
                        break;
                    case 5:
                        // handle 'ipad' msg -- add ip
                        voiceManager->addParticipant(inet_addr(textbuff));
                        break;
                    case 6:
                        // handle 'iprm' msg -- remove ip 
                        voiceManager->removeParticipant(inet_addr(textbuff));
                        break;

                    default:
                        break;
                }

                // re-enable FD_READ
                WSAAsyncSelect(wParam,hWnd,SOCKET_TCP,   FD_WRITE | FD_ACCEPT | FD_READ | FD_CLOSE);

                // free resources
                free(psize);
                free(ptype);
                free(textbuff);
                break;
                }

            case FD_WRITE:
                break;

            case FD_CONNECT:
                break;

            case FD_CLOSE:
                onDisconnect(hWnd);
                break;

            default:
            break;
        }
        break;



    case WM_PAINT:
        paintText(hWnd);
        break;

    case WM_DESTROY:
        shutdownConnection(hWnd);
        // reset window procs
        SetWindowLong(GetDlgItem(hWnd,ID_EDIT_SEND), GWL_WNDPROC,(LONG) OriginalEditProc);
        SetWindowLong(GetDlgItem(hWnd,ID_EDIT_IP), GWL_WNDPROC,(LONG) OriginalEditProc);
        PostQuitMessage(0);
        return 0;
        break;

    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;

    default:
        break;
}


return DefWindowProc(hWnd, message, wParam, lParam);
}


LRESULT CALLBACK sendEditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_CHAR) {
    if (wParam == VK_RETURN) {
        onSendText(GetParent(hWnd));
        return 0;
    }
}
if (message == WM_KEYUP || message == WM_KEYDOWN) {
    if (wParam == VK_RETURN) {
        return 0;
    }
}
return CallWindowProc(OriginalEditProc, hWnd, message, wParam,lParam);
}

其中 sendEditProc 是一个子类/超类,旨在在编辑控件“发送”内截取“输入”键 这有帮助吗?

这是消息循环;这是标准,所以没有什么花哨的东西会出错 afaik :)

while (GetMessage(&msg, NULL,0,0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

【问题讨论】:

  • 不,因为我使用了 WH_KEYBOARD_LL,所以我将 proc 放在了主应用程序中。
  • 如果你把MessageBox调用注释掉(或者用MessageBeep之类的东西代替),你还有同样的问题吗?
  • 是的,同样的问题又出现了
  • 请编辑您的问题以包含您的应用程序的消息循环。 (如果您没有,那可能就是问题所在。)在您发布的代码中,我没有看到任何突如其来的东西——消息循环可能有助于重现问题。
  • 今天早上偶然发现了这个 - blogs.msdn.com/b/alejacma/archive/2010/10/14/…

标签: c++ winapi input keyboard hook


【解决方案1】:

您调用 CallNextHookEx 的次数过多。如果key &lt; 0 return CallNextHookEx,否则return 0

您看到的问题与键盘重复和 MessageBox 或 MessageBeep 方法是非常非常昂贵的调用有关。试试这个测试:

HHOOK hHook;
BOOL bTalkEnabled = FALSE;

LRESULT CALLBACK pushtotalk(int key, WPARAM wParam, LPARAM lParam)
{
    if (key < 0)
        return CallNextHookEx(hHook, key, wParam, lParam);

    KBDLLHOOKSTRUCT* kbdll  = (KBDLLHOOKSTRUCT*)lParam;
    if (kbdll->vkCode == VK_F11)
    {
        BOOL bStarted = FALSE;
        BOOL bStopped = FALSE;

        if (wParam == WM_KEYDOWN)
        {
            if (!bTalkEnabled)
            {
                bStarted = TRUE;
                bTalkEnabled = TRUE;
            }
        }
        else if (wParam == WM_KEYUP)
        {
            if (bTalkEnabled)
            {
                bStopped = TRUE;
                bTalkEnabled = FALSE;
            }
        }

        if (bStarted)
            OutputDebugString(L"Pushed\r\n");
        if (bStopped)
            OutputDebugString(L"Released\r\n");
    }

    return 0;
}

您可以通过在调试器下运行应用程序来监控调试字符串(检查输出窗口),或者您可以获取DebugView 并观看。

请注意,我没有像您那样检查connected。您不想在挂钩中执行该检查,而是在挂钩之外执行此操作,并且仅使用挂钩来确定是否按下了键。

【讨论】:

  • 如果钩子设置为假,我将如何检查“已连接”而不使用钩子?例如,您的意思是在 Connected 时创建钩子并在 Disconnected 时将其删除?另外,如果我像您在 proc 结束时所做的那样返回 0,那不是“拦截”按键消息并阻止它们发送到当前具有焦点的应用程序吗?例如,如果我在程序运行时在文本编辑器中编写“k”,我想捕捉“k”,但我也不想干扰文本编辑器的输出。 CallNextHook 将消息传递给它的实际目标人,对吗?
  • 您需要知道一键通键的状态(向下或向上)。就像我在这里所做的那样,在处理程序中执行此操作。您可能想要做除OutputDebugString 之外的其他操作来响应,并且该代码可以检查连接状态。
  • 至于CallNextHookEx,你是对的。我让我的钩子处理程序感到困惑。你第一次说对了。 msdn.microsoft.com/en-us/library/ms644985.aspx
  • 我知道如果我长时间按下按钮,所有的消息框/消息哔声都会变得昂贵并阻塞系统(因此使用你提到的全局 bool 变量),但是它没有解释为什么第一次短按键会在系统中产生 5 秒的冻结。有什么想法吗?
  • 你的系统很糟糕?它不会发生在我身上。钩子激活时有一个短暂的、明显的停顿,但距离 5 秒还很远。
猜你喜欢
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-18
  • 2015-09-22
  • 2010-09-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多