views:

69

answers:

3

I installed a global mouse hook function like this:

mouseEventHook = ::SetWindowsHookEx( WH_MOUSE_LL, mouseEventHookFn, thisModule, 0 );

The hook function looks like this:

RESULT CALLBACK mouseEventHookFn( int code, WPARAM wParam, LPARAM lParam )
{
    if ( code == HC_ACTION ) {
        PMSLLHOOKSTRUCT mi = (PMSLLHOOKSTRUCT)lParam;
        // .. do interesting stuff ..
    }
    return ::CallNextHookEx( mouseEventHook, code, wParam, lParam );
}

Now, my problem is that I cannot control how long the 'do interesting stuff' part takes exactly. In particular, it might take longer than the LowLevelHooksTimeout defined in the Windows registry. This means that, at least on Windows XP, the system no longer delivers mouse events to my hook function. I'd like to avoid this, but at the same time I need the 'do interesting stuff' part to happen before the target GUI receives the event.

I attempted to solve this by doing the 'interesting stuff' work in a separate thread so that the mouseEventHookFn above can post a message to the worker thread and then do a return 1; immediately (which ends the hook function but avoids that the event is handed to the GUI). The idea was that the worker thread, when finished, performs the CallNextHookEx call itself.

However, this causes a crash inside of CallNextHookEx (in fact, the crash occurs inside an internal function called PhkNextValid. I assume it's not safe to call CallNextHookEx from outside a hook function, is this true?

If so, does anybody else know how I can run code (which needs to interact with the GUI thread of an application) before the GUI receives the event and avoid that my hook function blocks too long?

+1  A: 

No fix, you'll have to make your code faster. These hooks are potentially very detrimental to the user interface responsiveness, Windows makes sure that a misbehaving one gets put in the cellar. Even if the timeout were configurable, it would never be documented. That would defeat the purpose of having a timeout in the first place.

Hans Passant
Hmm, actually - according to the LowLevelKeyboardProc page at MSDN, the timeout is actually available for reading and writing in the registry. The key is `HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout`.
Frerich Raabe
Well, I'll be. Sounds like a workaround.
Hans Passant
+1  A: 

Why are you using a mouse event hook? are you hooking the mouse generally or just for a specific window? If it is for a specific window then you need to - instead of using a hook - actually subclass the target window.

This is normally a 2 stage process - hooks always need to be in dll's as the hook needs to be executed in the context of the process (and thread) thats actually handling the message.

So you start by writing a hook dll that, when sent a message, calls SetWindowLong on an HWND to replace the GWL_WINDOWPROC with your new window proc.

In your WindowProc you can take as long as you want to handle messages.

Chris Becke
+1 True; I'm using a global mouse event hook since I need to trace mouse actions in a number of windows (which can change at runtime) and I was too lazy to do the bookkeeping work of checking which windows I subclassed already and doing the IPC between the hook DLL (which should be very small, of course) and my code (which does the actual work). Maybe this is the only correct solution though.
Frerich Raabe
+1  A: 

assume it's not safe to call CallNextHookEx from outside a hook function, is this true?

I believe this is true.

Since there is a finite number of operations that you can receive through your low-level mouse hook, you could just put them onto a queue to be re-posted to the receiving window once your long-running operation has finished. If you put your long-running on another thread, you'll not 'lock up' the UI, but merely 'eat' or 'defer' the user actions. Return 1 to prevent other hooks happening. Use a boolean flag to signify whether you're collecting events (because your long-running operation has to run yet) or re-posting them (and thus shouldn't hook them).

There aren't likely to be (m)any other low-level hooks in the system that you're cancelling, but you should test this mechanism thoroughly in your situation. I have used it only to block operations before (kill right-mouse-click) rather than defer them.

JBRWilkinson
+1: This is basically my original idea except that you left it unclear how to 'repost' an event which a lowlevel hook caught. I tried to do it with `CallNextHookEx` - without success. :-) Is there a simple (and accurate) way of deducing `SendInput` invocations which reproduce the action? For what it's worth, it seems the `MSLLHOOKSTRUCT` structure (an argument to the `LowLevelMouseProc` callback) already has a field which tells whether it's a "real" event or a SendInput-generated event. So I don't need to maintain my own flag. :-)
Frerich Raabe
Your hook is intercepting the mouse activity before it is posted into the threads' Window Message queue, so you could just do PostThreadMessage() since you have all the info you need for this API but no HWND yet (so can't do PostMessage())
JBRWilkinson
Right, but at the point I'm intercepting the mouse activity, no window messages have been generated yet. All I have is a pointer to an `MSLLHOOKSTRUCT` structure. In order to use `PostThreadMessage()`, I will need to find out which messages a given mouse event triggers and (this is the tricky part, I think) I will need to populate a structure with the message parameters and pass that as the `LPARAM` argument. Is that what you mean? Sounds error-prone. :-}
Frerich Raabe
Copy out the information from the LPARAM to your own mechanism - if you're in C++ land, something like map<ThreadID, deque<pair<WPARAM, MSLLHOOKSTRUCT> > > would do the trick. Alternately, send a message to your own private thread using WM_COPYDATA to clone the MSLLHOOKSTRUCT or use a shared memory technique.
JBRWilkinson