WinApi: Can the message loop be interrupted by an Async Procedure Call?

1.9k views Asked by At

The following code register a low level mouse hook to monitor mouse events globally.
It's the simplest working example I can get.
Compiled with 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) ;
}

It's the basic one thread, one window, one message pump example. Except for the mouse hook.

My doubt is that what this code is doing contradicts two things I've read over and over in SO, MSDN, forums, blogs, etc..

  1. Global hook procedures must reside in a DLL
    MSDN documentation for SetWindowsHookEx confirms this by saying:

    If the dwThreadId parameter is zero, the lpfn parameter MUST point to a hook procedure in a DLL

  2. A GUI thread (one with a message pump) can't be interrupted because GetMessase's waiting state is not alertable. Which means that when GetMessage blocks in wait for more messages, it cannot receive a signal that interrupts its wait state.

However, there's no DLL anywhere to be seen here, and also the hook procedure must be interrupting the thread, otherwise the program wouldn't work, and it does (I'm assuming there's only one thread in this program).

So either I've completely misinterpreted these two points or this code is working in a way that doesn't match the asynchronous procedure call approach that I was expecting.

Either way, I'm cluless as to what is happening here.

Could you please explain how this code works.
Is it a single-thread program?
Is the hook procedure interrupting the thread?
Are any of the two points above actually true?

2

There are 2 answers

20
RbMm On BEST ANSWER

hook procedure must be in a DLL only if hook is need be injected into another process. for WH_MOUSE_LL

However, the WH_MOUSE_LL hook is not injected into another process. Instead, the context switches back to the process that installed the hook and it is called in its original context. Then the context switches back to the application that generated the event.

so here not need DLL (for what ??) and hook procedure can be placed in EXE too.

Is it a single-thread program?

in general yes. if not take to account possible system working threads, however all messages and hooks called in context of first thread

Is the hook procedure interrupting the thread?

from MSDN

This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

so may say that mouseHookProc called inside GetMessage call. this function wait in kernel for messages. when system want call hook procedure, it do this via KiUserCallbackDispatcher call. "interrupting the thread" - what you mean under interrupting ?

1.) this is not true, as show this example. hook procedure must be in a DLL, only if hook must be called in context of thread which received message, so in arbitrary process context - in this case we need inject code to another process - because this and DLL need. if we called always in self process context - no injection, not need DLL

2.) GetMessage really wait not in alertable state, so any APC cannot be delivered when code wait in GetMessage, but APC here absolute not related. APC and windows messaging different features. of course exist and similar points. for both APC and some windows message delivery, thread must wait in kernel . for APC in alertable state, for windows messages in GetMessage or PeekMessage. for APC delivery system call KiUserApcDispatcher, for windows messages KiUserCallbackDispatcher. both is callbacks from kernel mode, but it called under different conditions


interruption in exactly sense this when code execution can be interrupted at arbitrary place and interrupt routine begin executed. in this sense interrupt not exist at all in windows user mode. APC or windows messages (hook messages is special case of windows messages) never break execution at arbitrary place, but use callback mechanism instead. exceptions is special case. thread must first call some api for enter to kernel space (for windows messaging this is GetMessage or PeekMessage, for APC - wait in alertable state functions or ZwTestAlert). and then kernel can use callback to user space for deliver APC or windows message. at this moment only 3 callbacks point exist from kernel to user space (this unchanged from win2000 up to win10) KiUserApcDispatcher - used for APC delivery , KiUserCallbackDispatcher - used for call window procedure or hook procedure KiUserExceptionDispatcher - used for exception infrastructure - this most close to interrupt by sense,

2
IInspectable On

The behavior is explicitly documented under LowLevelMouseProc:

This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook.

When a mouse input event is about to be placed into a thread's message queue, the system sends a message to the thread that installed the hook, waits for it to return, and then continues processing the input event.

The hook need not be in a DLL for this to work. All of this is done before a mouse input event is placed into the target thread's input queue, so no interruption of a message retrieval function is required. Everything is executed in sequence:

  • Input event is picked up from the hardware input queue.
  • A message is sent to all low-level hooks.
  • If any of the hooks returned a non-zero value, drop the input event.
  • Otherwise, place it into the target thread's message queue.
  • The target thread can pick up the input event, using any of the message retrieval functions (GetMessage, PeekMessage, etc.).


A few notes on how this is implemented in the hook application:

The hook application needs to run a message loop, since the system sends messages to it, to inform it about input events that are about to be placed into the target thread's input queue by the Raw Input Thread. The GetMessage call (in the hook application) acts as a dispatcher in case of the hook messages, and calls into the hook procedure before returning. Even though GetMessage is a blocking call, it can be woken (without being alertable1)). It is probably waiting on an Event object that gets signaled, whenever a message is available for retrieval. As an aside, both calls to TranslateMessage and DispatchMessage aren't required in your hook application.


1) Reference: The alertable wait is the non-GUI analog to pumping messages.