WinApi: Может ли цикл обработки сообщений быть прерван асинхронным вызовом процедуры?

Следующий код регистрирует низкоуровневую ловушку мыши для глобального мониторинга событий мыши.
Это самый простой рабочий пример, который я могу получить.
Скомпилировано с VC ++ 2010: cl test.cpp /link /entry:mainCRTStartup /subsystem:windows

#include <windows.h>

HWND label1 ;

//THE HOOK PROCEDURE
LRESULT CALLBACK mouseHookProc(int aCode, WPARAM wParam, LPARAM lParam){
static int msgCount = 0 ;
static char str[20] ;
SetWindowText( label1, itoa(++msgCount, str, 10) ) ;
return CallNextHookEx(NULL, aCode, wParam, lParam) ;
}

int main(){
/**///  STANDARD WINDOW CREATION PART //////////////////////////////////////////////////////
/**/
/**/    WNDCLASSEX classStruct = { sizeof(WNDCLASSEX), 0, DefWindowProc, 0, 0, GetModuleHandle(NULL), NULL,
/**/                            LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_BTNFACE+1), NULL, "winClass", NULL } ;
/**/    RegisterClassEx(&classStruct) ;
/**/
/**/    HWND mainWin = CreateWindow("winClass", "", 0, 200,200, 100,100, NULL, NULL, NULL, NULL) ;
/**/    ShowWindow(mainWin, SW_SHOWDEFAULT) ;
/**/
/**/    label1 = CreateWindow("static", "0", WS_CHILD, 5,5, 80,20, mainWin, NULL, NULL, NULL) ;
/**/    ShowWindow(label1, SW_SHOWNOACTIVATE) ;
/**/
/**///  END OF WINDOW CREATION PART ////////////////////////////////////////////////////////

//HOOK INSTALATION
HHOOK hookProc = SetWindowsHookEx(WH_MOUSE_LL, mouseHookProc, GetModuleHandle(NULL), 0) ;

//MESSAGE LOOP
MSG msg ;
while( GetMessage(&msg, NULL, 0, 0) ){
TranslateMessage(&msg) ;
DispatchMessage(&msg) ;
}

UnhookWindowsHookEx(hookProc) ;
}

Это основной пример потока, одного окна, одного сообщения. За исключением крючка для мыши.

Я сомневаюсь, что то, что делает этот код, противоречит двум вещам, которые я много раз читал в SO, MSDN, форумах, блогах и т. Д.

  1. Глобальные подключаемые процедуры должны находиться в DLL
    Документация MSDN для SetWindowsHookEx подтверждает это, говоря:

    Если параметр dwThreadId равен нулю, параметр lpfn ДОЛЖЕН указать на процедуру подключения в DLL

  2. Поток GUI (один с насосом сообщений) не может быть прерван, потому что GetMessaseСостояние ожидания не оповещается. Что означает, что когда GetMessage блокирует ожидание большего количества сообщений, он не может получить сигнал, который прерывает его состояние ожидания.

Тем не менее, здесь нет ни одной DLL, и процедура подключения должна прерывать поток, иначе программа не будет работать, и это работает (я предполагаю, что в этой программе есть только один поток).

Так что я либо полностью неверно истолковал эти две точки, либо этот код работает так, что не соответствует подходу асинхронного вызова процедур, который я ожидал.

В любом случае, я не знаю, что здесь происходит.

Не могли бы вы объяснить, как работает этот код?
Это однопоточная программа?
Процедура ловушки прерывает поток?
Являются ли какие-либо из двух пунктов выше на самом деле верно?

1

Решение

Процедура ловушки должна быть в DLL, только если нужно подключить ловушку к другому процессу.
за WH_MOUSE_LL

Однако хук WH_MOUSE_LL не внедряется в другой процесс.
Вместо этого контекст переключается обратно на процесс, который установил
крюк, и это называется в первоначальном контексте. Тогда контекст
переключается обратно на приложение, которое сгенерировало событие.

так что здесь не нужна DLL (для чего ??) и процедура подключения тоже может быть помещена в EXE.

Это однопоточная программа?

в общем да. если не учитывать возможные рабочие потоки системы, то все сообщения и перехватчики вызываются в контексте первого потока

Процедура ловушки прерывает поток?

из MSDN

Этот хук вызывается в контексте потока, который его установил.
Вызов осуществляется путем отправки сообщения в поток, который установил
крюк. Поэтому нить, на которой установлен крюк, должна иметь
Цикл сообщений.

так можно сказать, что mouseHookProc называется внутри GetMessage вызов. эта функция ждет в ядре сообщений. когда система хочет вызвать подключаемую процедуру, она делает это через KiUserCallbackDispatcher вызов.
«прерывание потока» — что вы имеете в виду под прерыванием?

1.) это не так, как показано на этом примере. Процедура ловушки должна быть в DLL, только если ловушка должна вызываться в контексте потока, который получил сообщение, поэтому в произвольном контексте процесса — в этом случае нам нужно внедрить код в другой процесс — потому что это и DLL нужны. если мы вызывали всегда в контексте собственного процесса — не нужно внедрять, не нужна DLL

2.) GetMessage действительно ждать не в состоянии оповещения, поэтому любой APC не может быть доставлен, когда код ожидает GetMessageНо БТР тут абсолютно не связан. APC и Windows сообщений разные функции. конечно существуют и похожие моменты. как для APC, так и для некоторых сообщений Windows, поток должен ждать в ядре. для APC в состоянии готовности, для сообщений Windows в GetMessage или же PeekMessage, для вызова системы доставки APC KiUserApcDispatcher, для сообщений Windows KiUserCallbackDispatcher, оба являются обратными вызовами из режима ядра, но он вызывается при разных условиях


прерывание в точном смысле этого слова, когда выполнение кода может быть прервано в произвольном месте и начинается выполнение программы прерывания.
в этом смысле прерывания вообще не существуют в пользовательском режиме Windows. Сообщения APC или Windows (сообщения о перехвате — это особый случай сообщений Windows) никогда не прекращают выполнение в произвольном месте, а вместо этого используют механизм обратного вызова. исключения это особый случай. поток должен сначала вызвать некоторый API для входа в пространство ядра (для сообщений Windows это GetMessage или же PeekMessage, для APC — ждать в состоянии оповещения или ZwTestAlert). и затем ядро ​​может использовать обратный вызов в пользовательское пространство для доставки APC или сообщения Windows. на данный момент существует только 3 точки обратного вызова от ядра к пользовательскому пространству (это не изменилось с win2000 до win10)
KiUserApcDispatcher — используется для доставки БТР,
KiUserCallbackDispatcher — используется для процедуры окна вызова или процедуры подключения
KiUserExceptionDispatcher — используется для исключительной инфраструктуры — это наиболее близко к прерыванию по смыслу,

4

Другие решения

Поведение явно задокументировано под LowLevelMouseProc:

Этот хук вызывается в контексте потока, который его установил. Вызов осуществляется путем отправки сообщения в поток, который установил хук.

Когда событие ввода мыши собирается поместить в очередь сообщений потока, система отправляет сообщение потоку, который установил ловушку, ожидает его возврата и затем продолжает обработку события ввода.

Хук не должен быть в DLL, чтобы это работало. Все это сделано до событие ввода мыши помещается во входную очередь целевого потока, поэтому прерывание функции извлечения сообщения не требуется. Все выполняется в последовательности:

  • Событие ввода выбирается из очереди ввода оборудования.
  • Сообщение отправляется на все низкоуровневые хуки.
  • Если какой-либо из хуков вернул ненулевое значение, отбросьте событие ввода.
  • В противном случае поместите его в очередь сообщений целевого потока.
  • Целевой поток может забрать входное событие, используя любую из функций поиска сообщений (GetMessage, PeekMessage, так далее.).

Несколько замечаний о том, как это реализовано в подключаемом приложении:

Хук-приложение должно запустить цикл сообщений, поскольку система отправляет ему сообщения, чтобы сообщить ему о входных событиях, которые должны быть помещены во входную очередь целевого потока необработанным входным потоком. GetMessage call (в приложении ловушки) действует как диспетчер в случае сообщений ловушки и вызывает процедуру ловушки перед возвратом. Даже если GetMessage это блокирующий вызов, его можно разбудить (без предупреждения1)). Это, вероятно, ждет на Объект события который получает сигнал, когда сообщение доступно для поиска. Кроме того, оба звонка TranslateMessage а также DispatchMessage не требуются в вашем приложении крючка.


1) Ссылка: Ожидание с предупреждением является аналогом не-GUI для прокачки сообщений.

0

По вопросам рекламы [email protected]