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
Author: salamander1965
Date: Wed, 18 Jun 2008 22:55
Date: Wed, 18 Jun 2008 22:55
169 lines
6303 bytes
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
Author: salamander1965
Date: Thu, 19 Jun 2008 13:15
Date: Thu, 19 Jun 2008 13:15
39 lines
3208 bytes
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
Author: Remy Lebeau (TeamB)
Date: Thu, 19 Jun 2008 16:59
Date: Thu, 19 Jun 2008 16:59
24 lines
2131 bytes
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
Author: salamander1965
Date: Thu, 19 Jun 2008 21:04
Date: Thu, 19 Jun 2008 21:04
62 lines
4459 bytes
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
Author: Remy Lebeau (TeamB)
Date: Thu, 19 Jun 2008 23:42
Date: Thu, 19 Jun 2008 23:42
65 lines
3628 bytes
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
Author: salamander1965
Date: Fri, 20 Jun 2008 13:15
Date: Fri, 20 Jun 2008 13:15
93 lines
4671 bytes
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
Author: Alan Bellingham
Date: Fri, 20 Jun 2008 13:24
Date: Fri, 20 Jun 2008 13:24
11 lines
1528 bytes
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