键盘钩子问题

6

我正在开发一个语音聊天应用程序,它使用按键对讲功能。我已经完成了一个钩子,使其能够在应用程序外部注册按键对讲功能。

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) 有时,(例如应用程序中的第一次执行该过程),该过程在继续之前会导致系统冻结5秒钟。为什么?

2) 钩子只能在启动我的应用程序之前启动的进程上工作,如果我在启动我的应用程序后启动文本程序,则无法注册钩子。有解决方法吗?

3) 如果我按住键约3秒钟,显然会显示很多MessageBoxes,但是之后,该过程将不再注册另一个按键被按下,所以我猜我从钩链中断开了连接?

干杯

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

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是一个子/超类,旨在拦截在编辑控件“send”内部按下“enter”键的操作。

这有帮助吗?

以下是消息循环;它是标准的,所以我认为没有什么复杂的问题会出现 :)

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

不,因为我使用了 WH_KEYBOARD_LL,所以我将 proc 放在主应用程序内部。 - KaiserJohaan
如果你将MessageBox调用注释掉(或者替换为类似MessageBeep的东西),你还是会遇到同样的问题吗? - Tadmas
是的,相同的问题再次出现。 - KaiserJohaan
请编辑您的问题,包括应用程序的消息循环。(如果您没有,请检查一下是否是这个问题。)我在您发布的代码中没有看到任何明显的问题 - 消息循环可以帮助重现问题。 - Tadmas
1
今天早上偶然发现了这个 - http://blogs.msdn.com/b/alejacma/archive/2010/10/14/global-hooks-getting-lost-on-windows-7.aspx - soulseekah
显示剩余3条评论
1个回答

3
你正在多次调用CallNextHookEx。如果key < 0,则返回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。您不希望在钩子中执行该检查,而是在钩子外执行该检查,并仅使用钩子来确定按键是否被按下。


我该如何检查“已连接”,并在其设置为false时不使用hook?您是指在Connected时创建hook,在Disconnected时删除它吗?此外,如果像您最后在proc中返回0,那么这是否会“拦截”按键消息并阻止它们被发送到当前具有焦点的应用程序?例如,如果我在文本编辑器中写入“k”,而我的程序正在运行,则我想捕获“k”,但我也不想干扰文本编辑器的输出。CallNextHook将消息传递给实际的接收者,对吗? - KaiserJohaan
你需要知道按下对讲键的状态(按下或释放)。就像我在这里所做的那样,在处理程序中完成。你可能想要做一些响应操作而不是 OutputDebugString,那段代码可以检查连接状态。 - Tergiver
你的系统烂透了?在我的电脑上从未发生过。钩子激活时会有一个短暂的明显暂停,但绝不会长达5秒钟。 - Tergiver
这段内容没有意义。首先,挂钩的注册已经完成并且在按键之前挂钩是活动的,其次我的系统也不旧。我只希望这种情况不会在其他客户端重现。 - KaiserJohaan
挂钩代码直到挂钩监视的第一个事件发生时才真正“挂钩”。在这种情况下,它是键盘事件。我没有所有的细节,但是SetWindowsHookEx似乎只是在内核中“添加便笺”。 - Tergiver
显示剩余2条评论

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