🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Thread View: borland.public.cppbuilder.nativeapi
7 messages
7 total messages Started by salamander1965 Wed, 18 Jun 2008 22:55
Setting (and losing) a keyboard hook
#63
Author: salamander1965
Date: Wed, 18 Jun 2008 22:55
169 lines
6303 bytes
I'm having some difficulty with a system hook to intercept NUMLOCK and 
CAPSLOCK keystrokes.  My main application (THWin32) has a status window that 
displays whether these keys are toggled on or off, and I want the status 
window to be updated when the user presses these keys even if some other 
window is active.  A system keyboard hook seemed to be the best solution. 
However, I wanted there to be only one hook set for any number of instances 
of the application running to minimize the number of messages being 
broadcast to windows.  As a result, the DLL uses a FileMapping object to 
share memory among the processes attaching to the DLL.

The current DLL containing the code to set the hook seemed to work.  If I 
start one instance of THWin32, the status window updates properly.  If I 
start two or more instances of the program, all are updated properly, and if 
I close the instances in the reverse order in which they were started, the 
remaining instances update correctly.  However, if I close the first 
instance before the others, the remaining instances no longer respond to the 
message sent by the hook procedure.  So ... there seems to be something 
'special' about terminating the first instance, but I can't figure out what 
it might be.

Here are the relevant parts of the main application and the DLL -- any 
comments would be appreciated.

//===========
// THWin32.cpp
//===========
#include <windows.h>
#include <windowsx.h>
#include "THWin32WndProc.h"
#include "THWin32UI.h"
#include "THWin32KB.h"

//windows procedure for main application window
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM 
lParam) {
  static HINSTANCE hDll;
  static UINT uiKeyLockMsgID;

  switch (message) {
  case WM_CREATE:

    //set keyboard hook for NUMLOCK and CAPSLOCK keys
    hDll = LoadLibrary("THWin32KB");
    uiKeyLockMsgID = RegisterWindowMessage(szRegMsgName);
    //simulate NUMLOCK and CAPSLOCK keystrokes so initial state will be set 
correctly
    keybd_event(VK_NUMLOCK, 0, KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CAPITAL, 0, KEYEVENTF_KEYUP, 0);
    .
    .
    .
    return 0;

    // other message processing

  case WM_DESTROY:
    DeleteObject(hfont);
    FreeLibrary(hDll);
    PostQuitMessage(0);
    return 0;

  default:
    if (message == uiKeyLockMsgID) {
      switch (wParam) {
      case VK_CAPITAL:
        SetCapsLockStatus(hwnd, (int) lParam);
        break;
      case VK_NUMLOCK:
        SetNumLockStatus(hwnd, (int) lParam);
        break;
      }
    }
  }
  return DefWindowProc(hwnd, message, wParam, lParam);
}


//===========
// THWin32KB.cpp (code for the DLL)
//===========

typedef struct {
  int   nRefCount;
  HHOOK hHook;
  UINT  uiRegMsgID;
} SHAREDVARS, * LPSHAREDVARS;

#define SMSIZE  sizeof(SHAREDVARS)
#define SMNAME  "THWin32KB_SharedMem"
#define SMFILE  ((HANDLE) 0xffffffff)
#define SMPROT  PAGE_READWRITE

HHOOK AttachKeyboardHook(HINSTANCE);
BOOL  DetachKeyboardHook(HHOOK);

static LPVOID lpvSharedMem = NULL;
static HANDLE hMapObject = NULL;

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* 
lpReserved) {
  BOOL bInit;

  switch (reason) {
  case DLL_PROCESS_ATTACH:
    if ((hMapObject = CreateFileMapping(SMFILE, NULL, SMPROT, 0, SMSIZE, 
SMNAME)) == NULL)
      return false;

    //set init flag: true if first time DLL is being loaded; false otherwise
    bInit = (GetLastError() != ERROR_ALREADY_EXISTS);

    if ((lpvSharedMem = MapViewOfFile(hMapObject, FILE_MAP_WRITE, 0, 0, 0)) 
== NULL)
      return false;

    //initialize shared memory and set the keyboard hook first time DLL is 
loaded
    if (bInit) {
      memset(lpvSharedMem, '\0', SMSIZE);
      ((LPSHAREDVARS) lpvSharedMem)->hHook = AttachKeyboardHook(hinst);
      ((LPSHAREDVARS) lpvSharedMem)->uiRegMsgID = 
RegisterWindowMessage(szRegMsgName);
    }
    //increase the count of modules accessing the DLL
    ((LPSHAREDVARS) lpvSharedMem)->nRefCount++;
    break;

  case DLL_PROCESS_DETACH:
    //decrease the count of modules accessing the DLL
    ((LPSHAREDVARS) lpvSharedMem)->nRefCount--;
    //if no modules are accessing the DLL, remove the keyboard hook
    if (((LPSHAREDVARS) lpvSharedMem)->nRefCount == 0) {
      DetachKeyboardHook(((LPSHAREDVARS) lpvSharedMem)->hHook);
    }
    UnmapViewOfFile(lpvSharedMem);
    CloseHandle(hMapObject);
    break;
  }
  return 1;
}

LRESULT CALLBACK KBLockDetectProc(int nCode, WPARAM wParam, LPARAM lParam) {
  LPARAM bKeystate;

  if (nCode < 0)
    return CallNextHookEx(((LPSHAREDVARS) lpvSharedMem)->hHook, nCode, 
wParam, lParam);

  //check for release (specifically) of either NUMLOCK of CAPSLOCK key
  if ((wParam == VK_CAPITAL || wParam == VK_NUMLOCK) && (lParam & 
0x80000000)) {
    bKeystate = (LPARAM) (GetKeyState(wParam) & 0x0001);
    PostMessage(HWND_BROADCAST, ((LPSHAREDVARS) lpvSharedMem)->uiRegMsgID, 
wParam, bKeystate);
  }

  return CallNextHookEx(((LPSHAREDVARS) lpvSharedMem)->hHook, nCode, wParam, 
lParam);
}

HHOOK AttachKeyboardHook(HINSTANCE hinst) {
  return SetWindowsHookEx(WH_KEYBOARD, KBLockDetectProc, hinst, NULL);
}

BOOL DetachKeyboardHook(HHOOK hhook) {
  return UnhookWindowsHookEx(hhook);
}

Thanks -- Steve 
Re: Setting (and losing) a keyboard hook
#68
Author: salamander1965
Date: Thu, 19 Jun 2008 13:15
39 lines
3208 bytes
Here's some additional information that might be useful if you're 
looking at the code in the original post.

I put in some MessageBox() calls in the DllEntryPoint() function of the 
DLL -- one to report when the DLL_PROCESS_ATTACH case block executed, 
one in the DLL_PROCESS_DETACH block, and one in the if() block that 
detects when the nRefCount variable is 0 and calls DetachKeyboardHook(). 
  Each message box reports the section of the code being executed as 
well as the ID of the process attaching to/detaching from the DLL and 
the value of the shared nRefCount variable.

When several instances of the main application are started, the message 
boxes appear as would be expected with appropriate process ID and 
nRefCount values, so that part of the code seems to be working as 
expected.  The message box for detaching the hook itself only appears at 
the very end, as expected.

I mentioned in the original post that there seems to be something 
'special' about the first instance of the application to load the DLL. 
With the message boxes in place, it seems that after the first instance 
of the application attaches to the DLL, Explorer then attaches also. 
This occurs under Windows 2000 and Windows XP, so I'm assuming it's 
normal, although I don't understand why this is happening.  Subsequent 
instances of the application attach to the DLL normally, but Explorer 
does not attach again -- only attaching the first time the DLL is loaded.

When the application instances are terminated, again everything seems to 
occur as expected, but when the instance that was started first 
terminates (regardless of when it's terminated compared to the other 
instances), Explorer immediately detaches from the DLL, too.  After this 
is when the hook apparently stops working.  Maybe the association of 
Explorer with the first loading of the DLL is what is special about the 
first instance of the application, although I do not have enough 
experience with DLL's or hooks in general to even guess what might be 
going on.

Hopefully this might make sense to someone with more experience.

-- Steve
Re: Setting (and losing) a keyboard hook
#71
Author: Remy Lebeau (TeamB)
Date: Thu, 19 Jun 2008 16:59
24 lines
2131 bytes
"salamander1965" <salamander1@verizon.net> wrote in message 
news:4859926a@newsgroups.borland.com...

> However, if I close the first instance before the others, the
> remaining instances no longer respond to the message sent
> by the hook procedure.

Your first instance is likely deactivating the hook during its shutdown, 
thus it is no longer available for any instances that are still running. 
The behavior you desacribe would suggest that the hook is being associated 
with the first instance (per the hMod parameter of SetWindowsHookEx()). 
That instance would have to be the last instance terminated in order for the 
hook to work properly while the other instances are running, as you have 
already seen.

To do what you are attempting, I suggest moving the hook code into a 
separate .exe of its own.  The first instance of your app can launch the 
hook .exe, which then loads the DLL hook.  The last instance of your app can 
terminate the hook .exe, unloading the DLL.  That way, the hook is 
associated with its own process, not any particular instance of your app.


Gambit 
Re: Setting (and losing) a keyboard hook
#76
Author: salamander1965
Date: Thu, 19 Jun 2008 21:04
62 lines
4459 bytes
Remy Lebeau (TeamB) wrote:
> Your first instance is likely deactivating the hook during its shutdown, 
> thus it is no longer available for any instances that are still running. 
> The behavior you desacribe would suggest that the hook is being associated 
> with the first instance (per the hMod parameter of SetWindowsHookEx()). 

I played around with the message boxes a little more, having them also 
report the hMod parameter that came in to the DllEntryPoint() function 
and which was subsequently used in the call to SetWindowsHookEx().  The 
value of hMod is the same for each of the app instances that are 
started, so I'm not sure how the hook could be deactivated by one 
particular instance (the first one) terminating.

By putting a MessageBox() call in the hook procedure itself, though, I 
found that it's not the termination of the first app instance that 
deactivates the hook, but instead the detaching of Explorer.exe from the 
DLL that immediately follows the detaching of the first app instance, 
even though it was in the context of the first app process that the hook 
was set.  I'm still not clear on why Explorer.exe associates itself with 
the DLL only when the first app instance loads the library (but not 
subsequent loads), or why it even wants to be involved in this at all, 
but it seems to be causing the problem.

> To do what you are attempting, I suggest moving the hook code into a 
> separate .exe of its own.  The first instance of your app can launch the 
> hook .exe, which then loads the DLL hook.  The last instance of your app can 
> terminate the hook .exe, unloading the DLL.  That way, the hook is 
> associated with its own process, not any particular instance of your app.

If you have any idea what's going on as things stand now, I'd like to 
hear it.  In the meantime, though, I'm going to try the approach you 
suggested (for which, thanks).  Just working it out on paper at this 
point, I think I see how to go about it -- something like this:

1. hook.dll will contain the hook procedure itself (as it has to).

2. hook.exe will load the DLL and set the hook.

3. the first app instance will start hook.exe with CreateProcess(), but 
subsequent app instances will not; I think a named mutex created by the 
first app will work for this.

4. all of the app instances should be able to take advantage of the 
registered message coming from the hook procedure in the DLL.

5. the instances of the app somehow need to 'know' if they are the last 
to terminate so they can terminate hook.exe (which will unload the DLL) 
before closing.  I'm thinking shared memory through a FileMapping object 
would be the simplest way to maintain a reference count of the number of 
instances running.

6. when the last instance is closing, it signals hook.exe which calls 
ExitProcess() on its own process, allowing it clean up and unload the 
DLL.  Since there's really no reason for hook.exe to have a message 
loop, I'm thinking an event would be a good signal, with hook.exe using 
WaitForSingleObject() after it loads the DLL.

At least on paper, this seems reasonable, and I think it's at least 
roughly what was suggested.  Anyone feel free to tell me how stupid it 
really is, though.

Thanks -- Steve
Re: Setting (and losing) a keyboard hook
#77
Author: Remy Lebeau (TeamB)
Date: Thu, 19 Jun 2008 23:42
65 lines
3628 bytes
"salamander1965" <salamander1@verizon.net> wrote in message 
news:485ac9db$1@newsgroups.borland.com...

> I played around with the message boxes a little more, having them
> also report the hMod parameter that came in to the DllEntryPoint()
> function and which was subsequently used in the call to
> SetWindowsHookEx().

The DLL is loaded separately into every running process.  The hMod passed 
into DllEntryPoint() should be different for each DLL instance, as each DLL 
instance is mapped into a different address space.

> The value of hMod is the same for each of the app instances that
> are started

I don't see how that can be.

> By putting a MessageBox() call in the hook procedure itself, though

You might consider using OutputDebugString() instead.  Then you can use an 
external log viewer, such as SysInternals' DebugView, to see the messages in 
real-time without having to pause to read and dismiss individual popup 
dialogs.  You could output the calling process name, hook reference count, 
etc in a simple text format for easy reading and logging.

> I found that it's not the termination of the first app instance that 
> deactivates
> the hook, but instead the detaching of Explorer.exe from the DLL that
> immediately follows the detaching of the first app instance, even though
> it was in the context of the first app process that the hook was set.

But you have your DLL code set up to allow any DLL instance to release the 
unhook, not the first instance exclusively.  That would suggest your hook 
data's reference count is somehow falling to 0, causing one of the DLL 
instances to call UnhookWindowsHookEx() even though other apps are still 
running.

> If you have any idea what's going on as things stand now, I'd like
> to hear it.

I have no clue.

> In the meantime, though, I'm going to try the approach you suggested
> (for which, thanks).  Just working it out on paper at this point, I think
> I see how to go about it -- something like this:
<snip>

Yes.

> the instances of the app somehow need to 'know' if they are the last to 
> terminate so they can terminate hook.exe (which will unload the
> DLL) before closing.

You can use window messages or other IPC to communicate with hook.exe.  Each 
instance could register/unregister itself with hook.exe so it knows how many 
apps are running.  Or just broadcast a custom window message and see if any 
apps respond to it.

> Since there's really no reason for hook.exe to have a message loop

That depends on which communication model you choose to use, if any.


Gambit 
Re: Setting (and losing) a keyboard hook
#79
Author: salamander1965
Date: Fri, 20 Jun 2008 13:15
93 lines
4671 bytes
Remy Lebeau (TeamB) wrote:
> The DLL is loaded separately into every running process.  The hMod passed 
> into DllEntryPoint() should be different for each DLL instance, as each DLL 
> instance is mapped into a different address space.
> 
> But you have your DLL code set up to allow any DLL instance to release the 
> unhook, not the first instance exclusively.  That would suggest your hook 
> data's reference count is somehow falling to 0, causing one of the DLL 
> instances to call UnhookWindowsHookEx() even though other apps are still 
> running.

How have I missed the OutputDebugString() all this time -- it is much 
easier than using MessageBox().  I'm not going to keep beating a horse 
that won't work, but here is the edited logfile from DebugView for your 
viewing pleasure.  It does a much better job of showing what's happening 
than I've been able to do so far.  I started 3 instances (1, 2, 3) of 
the app (THWin32.exe) and then closed them closed them in the order 3, 
1, 2.  The PID is in brackets, the DLLInstance is the parameter passed 
to DllEntryPoint() and the RefCount is what I've been maintaining in the 
shared memory.

=====> start app 1
00 [516] Reason = Attach hook	
01 [516] DLLInstance = 14811136	
02 [516] Ref Count = 0	
03 [516] C:\...\THWin32.exe	

04 [516] Reason = DLL_ATTACH	
05 [516] DLLInstance = 14811136	
06 [516] Ref Count = 1	
07 [516] C:\...\THWin32.exe	

08 [1652] Reason = DLL_ATTACH	
09 [1652] DLLInstance = 15204352	
10 [1652] Ref Count = 2	
11 [1652] C:\WINNT\Explorer.EXE	

====> start app 2
12 [2496] Reason = DLL_ATTACH	
13 [2496] DLLInstance = 14811136	
14 [2496] Ref Count = 3	
15 [2496] C:\...\THWin32.exe	

=====> start app 3
16 [3884] Reason = DLL_ATTACH	
17 [3884] DLLInstance = 14811136	
18 [3884] Ref Count = 4	
19 [3884] C:\...\THWin32.exe	

=====> close app 3
20 [3884] Reason = DLL_DETACH	
21 [3884] DLLInstance = 14811136	
22 [3884] Ref Count = 3	
23 [3884] C:\...\THWin32.exe	

=====> close app 1
24 [516] Reason = DLL_DETACH	
25 [516] DLLInstance = 14811136	
26 [516] Ref Count = 2	
27 [516] C:\...\THWin32.exe	

28 [1652] Reason = DLL_DETACH	
29 [1652] DLLInstance = 15204352	
30 [1652] Ref Count = 1	
31 [1652] C:\WINNT\Explorer.EXE
=====> hook stops working at this point	
=====> close app 2
32 [2496] Reason = DLL_DETACH	
33 [2496] DLLInstance = 14811136	
34 [2496] Ref Count = 0	
35 [2496] C:\..\THWin32.exe	

36 [2496] Reason = Detach hook	
37 [2496] DLLInstance = 14811136	
38 [2496] Ref Count = 0	
39 [2496] C:\..\THWin32.exe

> You can use window messages or other IPC to communicate with hook.exe.  Each 
> instance could register/unregister itself with hook.exe so it knows how many 
> apps are running.

I think your suggestion to use messages for IPC between the app(s) and 
hook.exe is a better idea than my using an event and shared memory to 
keep a reference count of the number of apps running.  This means that 
hook.exe needs to be able to monitor messages.  Does this also mean 
hook.exe needs to have a window (that's never shown), or is there a way 
for a program to monitor messages with a message loop and process 
relevant messages in the loop rather than using DispatchMessage() to 
send the message to a window procedure?  The HWnd member of the message 
would have to be something other than a valid window handle, so would 
the message ever reach message queue for hook.exe?

Thanks -- Steve
Re: Setting (and losing) a keyboard hook
#80
Author: Alan Bellingham
Date: Fri, 20 Jun 2008 13:24
11 lines
1528 bytes
salamander1965 <salamander1@verizon.net> wrote:

>How have I missed the OutputDebugString() all this time -- it is much 
>easier than using MessageBox().

It's also much better at not mucking up redraw code!

Alan Bellingham
-- 
Team Browns
ACCU Conference 2009: to be announced
Thread Navigation

This is a paginated view of messages in the thread with full content displayed inline.

Messages are displayed in chronological order, with the original post highlighted in green.

Use pagination controls to navigate through all messages in large threads.

Back to All Threads