Board index » cppbuilder » Moving hooke callback to thread - suggestions? (was:Hooks, once again)

Moving hooke callback to thread - suggestions? (was:Hooks, once again)


2008-05-07 05:40:21 PM
cppbuilder23
Hi all,
The code below has a potential flaw: The callback code is
running in the main thread, and so may block the system event
processing or at least delay it.
Is there an easy/obvious/recommended way to do this? A
suggestion that has one of the listed attibutes is fine, but
I would of course love one that has all three :)
Anders
"Anders Johansen" < XXXX@XXXXX.COM >wrote:
Quote

Hi all.

I have so far been using the "old fashioned" approach to
keyboard and mouse hooking, meaning putting the hook procedures
in a DLL, and using the SetWindowsHookEx with WH_KEYBOARD and
WH_MOUSE. Now Vista arrives, and this approach is apparently
blocked for some applications (notable IE) when not running as
Administrator. I understand the rationale is that this allows
for code injection, which I guess is fair enough.

This is my alternate approach, submitted for comments and
inspiration.

I understand that the _LL versions of the hooks will work. They
have the disadvantage of not being available on systems older
than NT4 SP3. On the other hand, the callback does not have to
be in a DLL, which seems to work - can anybody confirm that this
is correct, e.g. that a DLL is NOT needed when using the _LL
hooks?

If, as my testing seems to validate, the hooking does not have
to happen in a DLL, this allows for a much nicer implementation
than the usual approach. Here's my first attempt, in case
anybody wants to comment or inspect...

Header:
#ifndef HookMasterH
#define HookMasterH
//---------------------------------------------------------------------------

#include <windows.h>
#include <map>
#include <set>

class HookObserver {
public:
virtual void keyDown(const UINT vkey, const bool altDown, const int keyDownNumber) = 0;
};

/*
The HookMaster uses hooks to monitor keyboard and mouse, and MSAA to
monitor focus change and window move/resize.
*/
class HookMaster {
private:
static HHOOK LLKeyHook;
static HHOOK LLMouseHook;
static int keyDownNumber;
static std::set<HookObserver*>observers;

static LRESULT CALLBACK LLKeyHookProc(int nCode, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK LLMouseHookProc(int nCode, WPARAM wParam, LPARAM lParam);
static void notifyKeyDown(const UINT vkey, const bool altDown);
public:
static void addObserver(HookObserver *o);
static void removeObserver(HookObserver *o);
static int getKeyDownNumber();
static bool setHooks();
static bool removeHooks();
};


CPP file:
//---------------------------------------------------------------------------


#pragma hdrstop

#include "HookMaster.h"
#include <SysInit.hpp>
//---------------------------------------------------------------------------

#pragma package(smart_init)


HHOOK HookMaster::LLKeyHook = NULL;
HHOOK HookMaster::LLMouseHook = NULL;
int HookMaster::keyDownNumber = 0;
std::set<HookObserver*>HookMaster::observers;

LRESULT CALLBACK HookMaster::LLKeyHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
try {
if (nCode<0 || nCode==HC_NOREMOVE )
return CallNextHookEx(LLKeyHook, nCode, wParam, lParam);
KBDLLHOOKSTRUCT * pData = ( KBDLLHOOKSTRUCT *) lParam;
bool Down = (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN);
bool Alt=(pData->flags & LLKHF_ALTDOWN); // Alt is down
UINT vkey = pData->vkCode;
if (Down) {
if (GetKeyState(VK_CONTROL)>=0 ) {
notifyKeyDown(vkey, Alt);
}
}
} catch (...) {
}
return CallNextHookEx(LLKeyHook, nCode, wParam, lParam);
}

LRESULT CALLBACK HookMaster::LLMouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
try {
if (nCode==HC_ACTION) {
switch (wParam) {
case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK:
notifyKeyDown(VK_RBUTTON, false);
break;
case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK:
notifyKeyDown(VK_LBUTTON, false);
break;
case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK:
notifyKeyDown(VK_MBUTTON, false);
break;
}
}
} catch (...) {
}
return CallNextHookEx(LLMouseHook, nCode, wParam, lParam);
}

void HookMaster::addObserver(HookObserver *o) {
observers.insert(o);
}

void HookMaster::removeObserver(HookObserver *o) {
observers.erase(o);
}

int HookMaster::getKeyDownNumber() {
return keyDownNumber;
}

bool HookMaster::setHooks() {
if (LLKeyHook == NULL) {
LLKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)LLKeyHookProc, HInstance, 0);
}
if (LLMouseHook == NULL) {
LLMouseHook = SetWindowsHookEx(WH_MOUSE_LL, (HOOKPROC)LLMouseHookProc, HInstance, 0);
}
return (LLKeyHook != NULL && LLMouseHook != NULL);
}

bool HookMaster::removeHooks() {
if (LLKeyHook != NULL) {
if (UnhookWindowsHookEx(LLKeyHook)) {
LLKeyHook = NULL;
}
}
if (LLMouseHook != NULL) {
if (UnhookWindowsHookEx(LLMouseHook)) {
LLMouseHook = NULL;
}
}
return (LLKeyHook == NULL && LLMouseHook == NULL);
}


void HookMaster::notifyKeyDown(const UINT vkey, const bool altDown) {
for (std::set<HookObserver*>::iterator i = observers.begin(); i != observers.end(); ++i) {
try {
HookObserver *obs = *i;
obs->keyDown(vkey, altDown, keyDownNumber);
} catch (...) {
// Something bad happened with the observer
}
}
++keyDownNumber;
}


Yes, it needs a little clean-up in sethooks, as it may return
false and leave a keyboard hook dangling if setting the mouse
hook fails...

A
 
 

Re:Moving hooke callback to thread - suggestions? (was:Hooks, once again)

Anders Johansen wrote:
Quote
The code below has a potential flaw: The callback code is
running in the main thread, and so may block the system event
processing or at least delay it.
The only slow part in the hooks I can see is notifyKeyDown,
and it's reliance on virtual KeyDown functions.
Everything else should be quite fast enough.
Two thoughts on notifyKeyDown:
1) You use "set" with pointers, apparently ordered by address.
I would think "vector" would be more appropriate.
2) You could place notifyKeyDown in a thread and have
the hooks place data in a FIFO for the thread to consume.
void notifyKeyDown(const UINT vkey, const bool altDown)
{ ThreadStruct.vkey = vkey;
ThreadStruct.altDown = alt.Down;
Fifo.Push( ThreadStruct );
Thread.Resume();
}
Quote
>void HookMaster::notifyKeyDown(const UINT vkey, const bool altDown) {
>for (std::set<HookObserver*>::iterator i = observers.begin(); i != observers.end(); ++i) {
>try {
>HookObserver *obs = *i;
>obs->keyDown(vkey, altDown, keyDownNumber);
>} catch (...) {
>// Something bad happened with the observer
>}
>}
>++keyDownNumber;
>}
 

Re:Moving hooke callback to thread - suggestions? (was:Hooks, once again)

Bob Gonder < XXXX@XXXXX.COM >wrote:
Quote
Anders Johansen wrote:

>The code below has a potential flaw: The callback code is
>running in the main thread, and so may block the system event
>processing or at least delay it.

The only slow part in the hooks I can see is notifyKeyDown,
and it's reliance on virtual KeyDown functions.
Everything else should be quite fast enough.
Good suggestions!
The problem is however that if you include this code in a program that blocks the message processing loop (say, by doing while (true) {};) the system temporarily becomes unresponsive, as the callbacks are blocked from executing for some reason. This happens even if no observers are registered. This is why I expect that moving the event processing etc. to another thread is a Good Idea.
Of course, this introduces a risk that the event handlers could be run outside the main thread, which would create problems with VCL code I expect.
Therefore my idea is:
* Place Hook code in separate thread
* Convert events to window messages sent asynchronously to the main thread (or some other way to avoid threading issues in VCL).
Anders
A
 

{smallsort}

Re:Moving hooke callback to thread - suggestions? (was:Hooks, once again)

Anders Johansen wrote:
Quote
The problem is however that if you include this code in a program that blocks the message processing loop
(say, by doing while (true) {};)
Don't do that!
Never put that kind of code in the main thread.
Quote
the system temporarily becomes unresponsive, as the callbacks are blocked
from executing for some reason.
Because the main thread is executing blocking code instead of running the message pump.
Quote
This is why I expect that moving the event processing etc. to another thread is a Good Idea.
No, moving blocking code to another thread is a Good Idea.
Quote
Of course, this introduces a risk that the event handlers could be run outside the main thread, which would create problems with VCL code I expect.
Yes. See Syncronize()
Quote
Therefore my idea is:
* Place Hook code in separate thread
Hook code belongs in Main Thread.
Quote
* Convert events to window messages sent asynchronously to the main thread (or some other way to avoid threading issues in VCL).
Run the virtual obs->keyDown() functions from threads.
The virtual obs->keyDown() functions should use Syncronize() to update VCL controls.
 

Re:Moving hooke callback to thread - suggestions? (was:Hooks, once again)

Bob Gonder < XXXX@XXXXX.COM >wrote:
Quote
Anders Johansen wrote:

>The problem is however that if you include this code in a program that blocks the message processing loop
>(say, by doing while (true) {};)

Don't do that!
Never put that kind of code in the main thread.
Well, sure :D
But the problem is not a deadlock - it is cases where you
might be doing things that block the message loop temporarily,
such as playing a WAV file without using asynchronous dispatch
of sound. This would make the mouse jitter.
Quote
>This is why I expect that moving the event processing etc.
>to another thread is a Good Idea.

No, moving blocking code to another thread is a Good Idea.
Yes, if you can find it :D
A