了解低级鼠标和键盘钩子(win32)

时间:2010-06-28 16:22:59

标签: c++ multithreading winapi

我正在尝试捕获全局鼠标和键盘输入。

LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
  if (nCode >= 0) {
    if (wParam == WM_RBUTTONDOWN) printf("right mouse down\n");
    if (wParam == WM_RBUTTONUP) printf("right mouse up\n");
  }
  return CallNextHookEx(0, nCode, wParam, lParam);
}

HHOOK mousehook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, NULL, 0);
while(true) {
  MSG msg;
  if (PeekMessage(&msg,0,0,0,PM_REMOVE)) {
    printf("msg recvd\n");
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
#ifdef TEST
  Sleep(50);
#endif
}

所以一切都在这里工作,除非我#define TEST放入Sleep,鼠标变得非常迟钝,如果我突然只允许鼠标每秒更新20次,可能会出现这种情况。没有睡眠,我将CPU固定在100%。但是现在没关系(如果我使用GetMessage,那就消失了。)

现在据我所知,低级钩子通过上下文切换到安装它的进程,然后发送进程某种消息让它执行钩子回调。令我困惑的是,为什么我的程序永远不会打印“msg recvd”,但只要我点击鼠标右键,就会打印“鼠标右键/向上”。这使我得出结论,MouseHookProc调用期间我的PeekMessage被调用。它恰好是某种特殊消息,而PeekMessage返回0.但我仍然需要调用PeekMessage或等价物。

由于我的程序需要做很多事情,所以我显然不能通过调用另一个需要50ms返回的函数来权衡我的消息循环(调用PeekMessage的循环)。我怎样才能多线程化我的程序以保持鼠标响应能力,同时做一些繁重的工作?在多线程win32程序中,仍然只有一个消息队列,对吧?

更新:在阅读了MS的文档后,我想我知道对我来说正确的做法是什么。我应该在我的应用程序中生成一个线程,它调用SetWindowsHookEx来注册鼠标钩子,然后在它自己的消息循环中坐下来,系统将负责将鼠标更新发送到这个线程。它可以在MouseHookProc内随意做任何事情,我的应用程序的其余部分将独立运行。

4 个答案:

答案 0 :(得分:10)

问题是你的消息循环,因为你使用PeekMessage()会烧掉100%的CPU周期。 Windows知道如何保持挂钩活动,即使您不轮询消息,使用GetMessage()来解决您的问题。使用Sleep(1)也可以解决您的问题,但这里没有必要。

Why must SetWindowsHookEx be used with a windows message queue

答案 1 :(得分:6)

我想你是否将地点MouseHookProc放在DLL中,因为试图将它放在EXE中这是一个典型的错误。我也是在很多年前做的。

首先,您如何阅读http://msdn.microsoft.com/en-us/library/ms644990.aspx

  

SetWindowsHookEx可用于注入   一个DLL进入另一个进程。一个32位   DLL无法注入64位   进程,一个64位DLL不能   注入32位进程。如果   应用程序需要使用钩子   在其他过程中,它是必需的   一个32位的应用程序调用   SetWindowsHookEx注入一个32位   DLL进入32位进程,和   64位应用程序调用   SetWindowsHookEx注入一个64位   DLL进入64位进程。 32位   和64位DLL必须有不同   名。

所以你必须放在DLL中。确切地说,如果您想要支持32位和64位平台,则必须实现两个dll:一个32位和64位DLL。但为什么? SetWindowsHookEx如何运作?

如果您在EXE中执行如下代码

HINSTANCE hinstDLL = LoadLibrary(TEXT("c:\\myapp\\syshook.dll"));
HOOKPROC hkprcMouse = (HOOKPROC)GetProcAddress(hinstDLL, "MouseHookProc");
HHOOK hhookMouse = SetWindowsHookEx( 
                    WH_MOUSE_LL,
                    hkprcMouse,
                    hinstDLL,
                    0); 

你给 user32.dll 请求在同一个windows工作站的所有其他进程中注入你的 syshook.dll (dll不会被注入到其他服务和进程中用户通过快速用户切换登录)。然后 user32.dll 在不同的进程中调用{em> syshook.dll LoadLibrary。然后,如果将调用函数MouseHookProc,则将在处理鼠标消息的进程的上下文中调用in。如果进程不是控制台应用程序,则代码如

printf("right mouse down\n");

无法正常工作。

所以我希望你现在不会忘记为什么必须将MouseHookProc放在DLL中。

答案 2 :(得分:3)

而不是:

if (PeekMessage(&msg,0,0,0,PM_REMOVE)) {
    printf("msg recvd\n");
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
Sleep(50);

将其切换为:

while (PeekMessage(&msg,0,0,0,PM_REMOVE)) {
    // Add this potentially...
    if (msg.message == WM_QUIT)
        break;
    printf("msg recvd\n");
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
Sleep(10);

这将允许您的应用继续处理队列中的所有消息,直到它为空(如没有睡眠),然后在应用程序“空闲”时放弃一些CPU时间。

答案 3 :(得分:3)

MouseHookProc应该驻留在dll中,否则您无法捕获“全局”输入(http://msdn.microsoft.com/en-us/library/ms997537.aspx

关于循环 - 您可以像这样修改它:

while(true) {
  MSG msg;
  while (PeekMessage(&msg,0,0,0,PM_REMOVE)) {
    printf("msg recvd\n");
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
#ifdef TEST
  DoStuff();
  Sleep(50);
#endif
}