Понимание низкоуровневого мыши и клавиатуры (win32)
Я пытаюсь захватить глобальный ввод мыши и клавиатуры.
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 раз в секунду. И без сна я привязываю процессор на 100%. Но это нормально сейчас (это уходит, если я использую GetMessage
).
Теперь, насколько я понимаю, низкоуровневые перехватчики работают путем переключения контекста на процесс, который его установил, а затем отправляют процессу какое-то сообщение, позволяющее ему выполнять обратный вызов hook. Меня немного смущает, поэтому моя программа никогда не будет печатать "msg recvd", но при каждом нажатии правой кнопки мыши она выводит "правую мышь вниз/вверх". Это приводит меня к выводу, что мой MouseHookProc
вызывается во время вызова PeekMessage
. Просто случается, что это какое-то специальное сообщение, а PeekMessage
возвращает 0. Но мне все равно нужно называть PeekMessage
или какой-то эквивалент.
Поскольку моя программа должна выполнять кучу вещей, я, очевидно, не могу взвесить цикл пересылки сообщений (тот, который вызывает PeekMessage
), вызывая другую функцию, которая принимает, скажем, 50 мс для возврата. Как я могу многократно использовать мою программу для поддержания реакции на мышь, одновременно делая небольшой тяжелый подъем? В многопоточной программе win32 существует еще одна очередь сообщений, правильно?
Обновление: прочитав документацию по MS, я думаю, что знаю, что мне нужно делать правильно. Я должен просто создать поток в моем приложении, который вызывает SetWindowsHookEx
, чтобы зарегистрировать крючок мыши, а затем посидеть в своем собственном контуре сообщений, и система позаботится о том, чтобы поместить обновления мыши в этот поток. Он будет свободен делать все, что захочет, в MouseHookProc
, а остальная часть моего приложения будет работать независимо.
Ответы
Ответ 1
Проблема заключается в вашей петле сообщений, она сжигает 100% циклов процессора, потому что вы используете PeekMessage(). Windows знает, как держать крюк в живых, даже если вы не просматриваете сообщения, используйте GetMessage(), чтобы решить вашу проблему. Использование Sleep (1) также решит вашу проблему, но здесь не требуется.
Почему SetWindowsHookEx должен использоваться с очередью сообщений Windows
Ответ 2
Я спросил вас, размещаете ли вы место 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. Чтобы быть точным, если вы хотите поддерживать как 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 для ввода вашей syshook.dll во всех других процессах на одной и той же станции Windows (dll не будет внедряться в службы и процессы других пользователей, зарегистрированных через быстрое переключение пользователей). Затем user32.dll вызывает LoadLibrary
в syshook.dll в разных процессах. Затем, если будет вызвана функция MouseHookProc
, она будет вызываться в контексте процесса, который обрабатывает сообщение мыши. Если процесс не является консольным приложением, код типа
printf("right mouse down\n");
не может работать.
Итак, надеюсь, что теперь вы откажетесь от необходимости размещать MouseHookProc
в DLL.
Ответ 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);
Это позволит вашему приложению продолжать обрабатывать все сообщения в очереди до тех пор, пока они не опустятся (например, не спят), , а затем отказаться от некоторого времени процессора, когда приложение "простаивает".
Ответ 4
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
}