eydelber
asked on
Windows keyboard hook as Windows Service
Hello,
I just bought an IBM Thinkpad T-42, and found a couple of useless "Web Forward" and "Web Backwards" that I wrote a program to remap to Music Forwards and Music Backwards, something I use much more. The program sets a keyboard hook with SetWindowsHookEx. The standalone program set the hook, then popped up a message box, during which everything worked fine. Once you clicked on the message box, the procedure would unhook and then the program finished. Great for debugging and testing, now to make something useful...
So I went ahead and modified the program to be a Windows Service. The actual keyboard hook is in a DLL. Everything service-wise is working fine (i.e. Starting, Stopping, etc.), but the hook no longer works! I did not change the code of how the hook was set (into a DLL), so I'm confused why this part no longer works. The reason I know it doesn't work, is that I have some logging code, and I added a log to the top of the Keyboard Hook function that traps the keys to just say that the function was called... and it never gets called.
Any ideas?
Here's some code (parts taken out that I thought were irrelevant).
First, the DLL with the hook procedure:
[code]
BYTE vk_next = VK_MEDIA_NEXT_TRACK,
vk_prev = VK_MEDIA_PREV_TRACK;
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
Log("DllMain: called");
return TRUE;
}
...
LRESULT CALLBACK LowLevelKeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
Log("LowLevelKeyboardProc: Called");
BOOL bSuppress = FALSE;
...
return (bSuppress ? TRUE : CallNextHookEx(NULL, nCode, wParam, lParam));
}
[/code]
And then the service:
[code]
#define DLL_LOCATION "ibmremaplib.dll"
#define SERVICE_NAME "IBM Keyboard Remapper"
HOOKPROC hkprcSysMsg;
static HINSTANCE hinstDLL;
static HHOOK hhookSysMsg;
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
HHOOK Start();
BOOL Stop();
void GetErrorString(LPVOID lpMsgBuf);
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
HHOOK Start()
{
hhookSysMsg = SetWindowsHookEx(WH_KEYBOA RD_LL, hkprcSysMsg, hinstDLL, 0);
return hhookSysMsg;
}
BOOL Stop()
{
return UnhookWindowsHookEx(hhookS ysMsg);
}
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE prevInstance, PSTR pszCmdLine, int iCmdShow)
{
LPVOID lpMsgBuf = NULL;
hinstDLL = LoadLibrary((LPCTSTR) DLL_LOCATION);
if(!hinstDLL)
{
GetErrorString(&lpMsgBuf);
//MessageBox(NULL, (LPTSTR)lpMsgBuf, TEXT("Error1"), 0);
Log((PTSTR) lpMsgBuf);
return S_FALSE;
}
hkprcSysMsg = (HOOKPROC)GetProcAddress(h instDLL, "LowLevelKeyboardProc");
if(!hkprcSysMsg)
{
GetErrorString(&lpMsgBuf);
Log((PTSTR)lpMsgBuf);
return S_FALSE;
}
Log("WinMain: called");
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceN ame = SERVICE_NAME;
ServiceTable[0].lpServiceP roc = (LPSERVICE_MAIN_FUNCTION)S erviceMain ;
ServiceTable[1].lpServiceN ame = NULL;
ServiceTable[1].lpServiceP roc = NULL;
// Start the control dispatcher thread for our service
StartServiceCtrlDispatcher (ServiceTa ble);
LocalFree(lpMsgBuf);
return S_OK;
}
void GetErrorString(void *lpMsgBuf)
{
FormatMessage(FORMAT_MESSA GE_ALLOCAT E_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM ,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) lpMsgBuf,
0,
NULL
);
}
void ServiceMain(int argc, char** argv)
{
ServiceStatus.dwServiceTyp e = SERVICE_WIN32;
ServiceStatus.dwCurrentSta te = SERVICE_START_PENDING;
ServiceStatus.dwControlsAc cepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitC ode = 0;
ServiceStatus.dwServiceSpe cificExitC ode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hStatus = RegisterServiceCtrlHandler (
SERVICE_NAME,
(LPHANDLER_FUNCTION)Contro lHandler);
if(hStatus == (SERVICE_STATUS_HANDLE)0)
{
// Registering Control Handler failed
Log(TEXT("Registering Control Handler failed."));
return;
}
// We report the running status to SCM.
ServiceStatus.dwCurrentSta te = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
if(!Start())
{
Log(TEXT("ServiceMain: Could not hook!"));
}
else
{
TCHAR msg[50];
Log(TEXT("ServiceMain: Started"));
sprintf(msg, "%0.8X", hhookSysMsg);
Log(msg);
}
while(ServiceStatus.dwCurr entState == SERVICE_RUNNING)
{
Sleep(5000);
}
}
void ControlHandler(DWORD request)
{
switch(request)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
if(!Stop())
{
Log(TEXT("ControlHandler: Could not unhook!"));
}
else Log(TEXT("ControlHandler: Stopped."));
ServiceStatus.dwWin32ExitC ode = 0;
ServiceStatus.dwCurrentSta te = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;
}
// Report current status
SetServiceStatus (hStatus, &ServiceStatus);
}
[/code]
Thanks for any help!
Kevin Grigorenko
I just bought an IBM Thinkpad T-42, and found a couple of useless "Web Forward" and "Web Backwards" that I wrote a program to remap to Music Forwards and Music Backwards, something I use much more. The program sets a keyboard hook with SetWindowsHookEx. The standalone program set the hook, then popped up a message box, during which everything worked fine. Once you clicked on the message box, the procedure would unhook and then the program finished. Great for debugging and testing, now to make something useful...
So I went ahead and modified the program to be a Windows Service. The actual keyboard hook is in a DLL. Everything service-wise is working fine (i.e. Starting, Stopping, etc.), but the hook no longer works! I did not change the code of how the hook was set (into a DLL), so I'm confused why this part no longer works. The reason I know it doesn't work, is that I have some logging code, and I added a log to the top of the Keyboard Hook function that traps the keys to just say that the function was called... and it never gets called.
Any ideas?
Here's some code (parts taken out that I thought were irrelevant).
First, the DLL with the hook procedure:
[code]
BYTE vk_next = VK_MEDIA_NEXT_TRACK,
vk_prev = VK_MEDIA_PREV_TRACK;
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
Log("DllMain: called");
return TRUE;
}
...
LRESULT CALLBACK LowLevelKeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
Log("LowLevelKeyboardProc:
BOOL bSuppress = FALSE;
...
return (bSuppress ? TRUE : CallNextHookEx(NULL, nCode, wParam, lParam));
}
[/code]
And then the service:
[code]
#define DLL_LOCATION "ibmremaplib.dll"
#define SERVICE_NAME "IBM Keyboard Remapper"
HOOKPROC hkprcSysMsg;
static HINSTANCE hinstDLL;
static HHOOK hhookSysMsg;
SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
HHOOK Start();
BOOL Stop();
void GetErrorString(LPVOID lpMsgBuf);
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
HHOOK Start()
{
hhookSysMsg = SetWindowsHookEx(WH_KEYBOA
return hhookSysMsg;
}
BOOL Stop()
{
return UnhookWindowsHookEx(hhookS
}
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE prevInstance, PSTR pszCmdLine, int iCmdShow)
{
LPVOID lpMsgBuf = NULL;
hinstDLL = LoadLibrary((LPCTSTR) DLL_LOCATION);
if(!hinstDLL)
{
GetErrorString(&lpMsgBuf);
//MessageBox(NULL, (LPTSTR)lpMsgBuf, TEXT("Error1"), 0);
Log((PTSTR) lpMsgBuf);
return S_FALSE;
}
hkprcSysMsg = (HOOKPROC)GetProcAddress(h
if(!hkprcSysMsg)
{
GetErrorString(&lpMsgBuf);
Log((PTSTR)lpMsgBuf);
return S_FALSE;
}
Log("WinMain: called");
SERVICE_TABLE_ENTRY ServiceTable[2];
ServiceTable[0].lpServiceN
ServiceTable[0].lpServiceP
ServiceTable[1].lpServiceN
ServiceTable[1].lpServiceP
// Start the control dispatcher thread for our service
StartServiceCtrlDispatcher
LocalFree(lpMsgBuf);
return S_OK;
}
void GetErrorString(void *lpMsgBuf)
{
FormatMessage(FORMAT_MESSA
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) lpMsgBuf,
0,
NULL
);
}
void ServiceMain(int argc, char** argv)
{
ServiceStatus.dwServiceTyp
ServiceStatus.dwCurrentSta
ServiceStatus.dwControlsAc
ServiceStatus.dwWin32ExitC
ServiceStatus.dwServiceSpe
ServiceStatus.dwCheckPoint
ServiceStatus.dwWaitHint = 0;
hStatus = RegisterServiceCtrlHandler
SERVICE_NAME,
(LPHANDLER_FUNCTION)Contro
if(hStatus == (SERVICE_STATUS_HANDLE)0)
{
// Registering Control Handler failed
Log(TEXT("Registering Control Handler failed."));
return;
}
// We report the running status to SCM.
ServiceStatus.dwCurrentSta
SetServiceStatus (hStatus, &ServiceStatus);
if(!Start())
{
Log(TEXT("ServiceMain: Could not hook!"));
}
else
{
TCHAR msg[50];
Log(TEXT("ServiceMain: Started"));
sprintf(msg, "%0.8X", hhookSysMsg);
Log(msg);
}
while(ServiceStatus.dwCurr
{
Sleep(5000);
}
}
void ControlHandler(DWORD request)
{
switch(request)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
if(!Stop())
{
Log(TEXT("ControlHandler: Could not unhook!"));
}
else Log(TEXT("ControlHandler: Stopped."));
ServiceStatus.dwWin32ExitC
ServiceStatus.dwCurrentSta
SetServiceStatus (hStatus, &ServiceStatus);
return;
}
// Report current status
SetServiceStatus (hStatus, &ServiceStatus);
}
[/code]
Thanks for any help!
Kevin Grigorenko
First thing I would do is in the Start() function, if SetWindowsHookEx() returns a 0, I would call GetLastError() or your GetErrorString() function, to see what the error is, and log it.
In fact, you should check the error code for every Windows API call. The error might not be in the Start() function.
In fact, you should check the error code for every Windows API call. The error might not be in the Start() function.
Hmmm, sounds like you did that already...
>>Everything service-wise is working fine (i.e. Starting, Stopping, etc.), but the hook no longer works!
Thats the exact problem. Hooks execute in *user* contexts, not in services' context and they do not even share the same window station. I'd rather recommend to start these hooks like 'Autorun' apps, which ensures the proper context. BTW, what's your program supposed to do exactly?
Thats the exact problem. Hooks execute in *user* contexts, not in services' context and they do not even share the same window station. I'd rather recommend to start these hooks like 'Autorun' apps, which ensures the proper context. BTW, what's your program supposed to do exactly?
ASKER
>>Thats the exact problem. Hooks execute in *user* contexts, not in services' context and they do not even share the same window station. I'd rather recommend to start these hooks like 'Autorun' apps, which ensures the proper context. BTW, what's your program supposed to do exactly?
Ahh, interesting. I wouldn't mind just running it in autorun, but how do I just let it sit there without a while(1) loop, which obviously doesn't work. Do I need to use a semaphore or something? I really just need to give my hook to windows and that's it. My program doesn't need to do anything else.
Here's the actual code, like I mentioned it maps a few keys on my thinkpad keyboard to different keys (in this case multimedia playlist forward and backward keys):
BOOL bSuppress = FALSE;
if(nCode == HC_ACTION) {
switch (wParam) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
bSuppress = NeedsRemap(p->vkCode);
if(bSuppress)
{
DWORD dwFlags = 0;
if(wParam == WM_KEYUP ||
wParam == WM_SYSKEYUP)
{
dwFlags |= KEYEVENTF_KEYUP;
}
// inject the appropriate logic:
// below is specific logic for the backward/forward
// buttons
if(p->scanCode != 0) /* quirk; see preamble */
{
if(p->vkCode == VK_IBM_MEDIA_PREV_TRACK)
{
// backwards key
keybd_event(vk_prev, 0, dwFlags, 0);
}
else if(p->vkCode == VK_IBM_MEDIA_NEXT_TRACK)
{
// forwards key
keybd_event(vk_next, 0, dwFlags, 0);
}
}
}
break;
}
}
Thanks for the help,
Kevin
Ahh, interesting. I wouldn't mind just running it in autorun, but how do I just let it sit there without a while(1) loop, which obviously doesn't work. Do I need to use a semaphore or something? I really just need to give my hook to windows and that's it. My program doesn't need to do anything else.
Here's the actual code, like I mentioned it maps a few keys on my thinkpad keyboard to different keys (in this case multimedia playlist forward and backward keys):
BOOL bSuppress = FALSE;
if(nCode == HC_ACTION) {
switch (wParam) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
bSuppress = NeedsRemap(p->vkCode);
if(bSuppress)
{
DWORD dwFlags = 0;
if(wParam == WM_KEYUP ||
wParam == WM_SYSKEYUP)
{
dwFlags |= KEYEVENTF_KEYUP;
}
// inject the appropriate logic:
// below is specific logic for the backward/forward
// buttons
if(p->scanCode != 0) /* quirk; see preamble */
{
if(p->vkCode == VK_IBM_MEDIA_PREV_TRACK)
{
// backwards key
keybd_event(vk_prev, 0, dwFlags, 0);
}
else if(p->vkCode == VK_IBM_MEDIA_NEXT_TRACK)
{
// forwards key
keybd_event(vk_next, 0, dwFlags, 0);
}
}
}
break;
}
}
Thanks for the help,
Kevin
ASKER
Actually, jkr, could you explain your answer a little bit more just for my knowledge? What if I run the service as Local System, or am I missing your point? Also, I thought the DLL was just sucked in (for lack of a better term) to the executable and would work just as when it was working in standalone?
Kevin
Kevin
>> I wouldn't mind just running it in autorun, but how do I just let it sit there without a while(1) loop,
>>which obviously doesn't work
That would ideed be a bad idea. All you need is a windowless app that installs the hook and the just waits for an event to terminate, e.g.
int PASCAL WinMain ( HANDLE hInstance,
HANDLE hPrevInst,
LPSTR lpszCmdLine,
int cmdShow
)
{
HANDLE hev;
if ( !( hev = CreateEvent ( NULL,
FALSE,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
if ( ERROR_ALREADY_EXISTS == GetLastError ())
{
if ( !( hev = OpenEvent ( EVENT_ALL_ACCESS,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
return ( -1);
}
}
else
{
return ( -2);
}
}
InstallHook();
WaitForSingleObject ( hev, INFINITE);
CloseHandle ( hev);
}
>>which obviously doesn't work
That would ideed be a bad idea. All you need is a windowless app that installs the hook and the just waits for an event to terminate, e.g.
int PASCAL WinMain ( HANDLE hInstance,
HANDLE hPrevInst,
LPSTR lpszCmdLine,
int cmdShow
)
{
HANDLE hev;
if ( !( hev = CreateEvent ( NULL,
FALSE,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
if ( ERROR_ALREADY_EXISTS == GetLastError ())
{
if ( !( hev = OpenEvent ( EVENT_ALL_ACCESS,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
return ( -1);
}
}
else
{
return ( -2);
}
}
InstallHook();
WaitForSingleObject ( hev, INFINITE);
CloseHandle ( hev);
}
ASKER
That's exactly what I was looking for!!! I'll try to get it working tonight.
And InstallHook() needs to be on its own thread, correct? Because I assume WaitForSingleObject is synchronous, and even if my hook call is in a separate DLL, the only thread of execution will be waiting, right?
Thanks,
Kevin
And InstallHook() needs to be on its own thread, correct? Because I assume WaitForSingleObject is synchronous, and even if my hook call is in a separate DLL, the only thread of execution will be waiting, right?
Thanks,
Kevin
ASKER
Okay, as per my last comment, I can't get it to work. If I try my code as before, be installing the hook, and popping up a message box, it works. But then if I take that out and let it run through to WaitForSingleObject, it no longer works (perhaps based on the hypothesis I mentioned in the previous post). I tried creating a new thread and put the WaitForSingleObject call there, but then main ended, and I guess it took that thread with it. Then I tried to put the Install Hook in a new thread, but that didn't work either. It seems to be a problem because the hook is installed as a callback but the main thread is synchronized and waiting?
Thanks,
Kevin
Thanks,
Kevin
>>But then if I take that out and let it run through to WaitForSingleObject, it no longer works (perhaps based on the
>>hypothesis I mentioned in the previous post).
Err, no - that function is supposed to reside in the hook DLL. Here's a sample taken out of one of my previous projects:
LONG __DYNLINK InstallHook ( void)
{
HANDLE hev;
if ( g_hhk) return ( ERROR_ALREADY_EXISTS);
g_hhk = SetWindowsHookEx ( WH_GETMESSAGE,
( HOOKPROC) HookProc,
g_hThisDll,
0
);
if ( !( hev = CreateEvent ( NULL,
FALSE,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
if ( ERROR_ALREADY_EXISTS == GetLastError ())
{
if ( !( hev = OpenEvent ( EVENT_ALL_ACCESS,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
return ( -1);
}
}
else
{
return ( -2);
}
}
WaitForSingleObject ( hev, INFINITE);
Sleep ( 5000);
return ( 0);
}
LONG __DYNLINK TerminateHook ( void)
{
HANDLE hev;
UnhookWindowsHookEx ( g_hhk);
Sleep ( 5000);
if ( !( hev = OpenEvent ( EVENT_ALL_ACCESS,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
return ( GetLastError ());
}
PulseEvent ( hev);
return ( 0);
}
>>hypothesis I mentioned in the previous post).
Err, no - that function is supposed to reside in the hook DLL. Here's a sample taken out of one of my previous projects:
LONG __DYNLINK InstallHook ( void)
{
HANDLE hev;
if ( g_hhk) return ( ERROR_ALREADY_EXISTS);
g_hhk = SetWindowsHookEx ( WH_GETMESSAGE,
( HOOKPROC) HookProc,
g_hThisDll,
0
);
if ( !( hev = CreateEvent ( NULL,
FALSE,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
if ( ERROR_ALREADY_EXISTS == GetLastError ())
{
if ( !( hev = OpenEvent ( EVENT_ALL_ACCESS,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
return ( -1);
}
}
else
{
return ( -2);
}
}
WaitForSingleObject ( hev, INFINITE);
Sleep ( 5000);
return ( 0);
}
LONG __DYNLINK TerminateHook ( void)
{
HANDLE hev;
UnhookWindowsHookEx ( g_hhk);
Sleep ( 5000);
if ( !( hev = OpenEvent ( EVENT_ALL_ACCESS,
FALSE,
MYAPP_EVENT_NAME
)
)
)
{
return ( GetLastError ());
}
PulseEvent ( hev);
return ( 0);
}
ASKER
I'm probably doing something wrong. I tried your code, but it's still not working. Here is all of the code.
First, the DLL:
>> The DLL header, "ibmremaplib.h":
#ifndef IBMREMAPLIB_H
#define IBMREMAPLIB_H
// A few #define's because these are only supported for
// Win2k and above
#ifndef VK_MEDIA_NEXT_TRACK
#define VK_MEDIA_NEXT_TRACK 0xB0
#endif
#ifndef VK_MEDIA_PREV_TRACK
#define VK_MEDIA_PREV_TRACK 0xB1
#endif
// The virtual key codes found on my T-42.
#define VK_IBM_MEDIA_PREV_TRACK 0xA6
#define VK_IBM_MEDIA_NEXT_TRACK 0xA7
#define MYAPP_EVENT_NAME "IBMKeyboardRemapper"
LONG InstallHook();
LONG TerminateHook();
#endif
>> The DLL source def file:
LIBRARY ibmremaplib
EXPORTS
LowLevelKeyboardProc
InstallHook
TerminateHook
>> The DLL source file, "ibmremaplib.cpp"
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include "ibmremaplib.h"
BYTE vk_next = VK_MEDIA_NEXT_TRACK,
vk_prev = VK_MEDIA_PREV_TRACK;
static HHOOK g_hhk = NULL;
static HINSTANCE g_hThisDll;
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
g_hThisDll = hInstance;
return TRUE;
}
/*
* Utility function to see if we will be dealing
* with this key.
*/
BOOL NeedsRemap(DWORD key)
{
if(key == VK_IBM_MEDIA_PREV_TRACK ||
key == VK_IBM_MEDIA_NEXT_TRACK)
{
return TRUE;
}
return FALSE;
}
/*
* The procedure we hook into Windows.
*/
LRESULT CALLBACK LowLevelKeyboardProc(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
// If this flag is set, the current key
// will be suppressed. (Except in the case
// that processing takes longer than the timeout
// values defined in HKEY_CURRENT_USER\Control Panel\Desktop)
BOOL bSuppress = FALSE;
if(nCode == HC_ACTION) {
switch (wParam) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
bSuppress = NeedsRemap(p->vkCode);
if(bSuppress)
{
DWORD dwFlags = 0;
if(wParam == WM_KEYUP ||
wParam == WM_SYSKEYUP)
{
dwFlags |= KEYEVENTF_KEYUP;
}
// inject the appropriate logic:
// below is specific logic for the backward/forward
// buttons
if(p->scanCode != 0) /* quirk; see preamble */
{
if(p->vkCode == VK_IBM_MEDIA_PREV_TRACK)
{
// backwards key
keybd_event(vk_prev, 0, dwFlags, 0);
}
else if(p->vkCode == VK_IBM_MEDIA_NEXT_TRACK)
{
// forwards key
keybd_event(vk_next, 0, dwFlags, 0);
}
}
}
break;
}
}
return (bSuppress ? TRUE : CallNextHookEx(NULL, nCode, wParam, lParam));
}
LONG InstallHook()
{
HANDLE hev;
if(g_hhk) return(ERROR_ALREADY_EXIST S);
g_hhk = SetWindowsHookEx(
WH_KEYBOARD_LL,
(HOOKPROC) LowLevelKeyboardProc,
g_hThisDll,
0
);
if(!(hev = CreateEvent(NULL, FALSE, FALSE, MYAPP_EVENT_NAME)))
{
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
if(!(hev = OpenEvent(EVENT_ALL_ACCESS , FALSE, MYAPP_EVENT_NAME)))
{
return(-1);
}
}
else
{
return(-2);
}
}
WaitForSingleObject(hev, INFINITE);
Sleep(5000);
return(0);
}
LONG TerminateHook()
{
HANDLE hev;
UnhookWindowsHookEx(g_hhk) ;
Sleep(5000);
if(!(hev = OpenEvent(EVENT_ALL_ACCESS , FALSE, MYAPP_EVENT_NAME)))
{
return(GetLastError());
}
PulseEvent(hev);
return(0);
}
And finally, the driver program, "ibmremap.cpp":
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include <process.h>
#include "ibmremaplib.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR pszCmdLine, int iCmdShow)
{
HANDLE hev;
if(!(hev = CreateEvent(NULL,
FALSE,
FALSE,
MYAPP_EVENT_NAME
)))
{
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
if(!(hev = OpenEvent(EVENT_ALL_ACCESS ,
FALSE,
MYAPP_EVENT_NAME
)))
{
return(-1);
}
}
else
{
return(-2);
}
}
InstallHook();
WaitForSingleObject(hev, INFINITE);
TerminateHook();
CloseHandle(hev);
return S_OK;
}
Thanks,
Kevin
First, the DLL:
>> The DLL header, "ibmremaplib.h":
#ifndef IBMREMAPLIB_H
#define IBMREMAPLIB_H
// A few #define's because these are only supported for
// Win2k and above
#ifndef VK_MEDIA_NEXT_TRACK
#define VK_MEDIA_NEXT_TRACK 0xB0
#endif
#ifndef VK_MEDIA_PREV_TRACK
#define VK_MEDIA_PREV_TRACK 0xB1
#endif
// The virtual key codes found on my T-42.
#define VK_IBM_MEDIA_PREV_TRACK 0xA6
#define VK_IBM_MEDIA_NEXT_TRACK 0xA7
#define MYAPP_EVENT_NAME "IBMKeyboardRemapper"
LONG InstallHook();
LONG TerminateHook();
#endif
>> The DLL source def file:
LIBRARY ibmremaplib
EXPORTS
LowLevelKeyboardProc
InstallHook
TerminateHook
>> The DLL source file, "ibmremaplib.cpp"
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include "ibmremaplib.h"
BYTE vk_next = VK_MEDIA_NEXT_TRACK,
vk_prev = VK_MEDIA_PREV_TRACK;
static HHOOK g_hhk = NULL;
static HINSTANCE g_hThisDll;
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
g_hThisDll = hInstance;
return TRUE;
}
/*
* Utility function to see if we will be dealing
* with this key.
*/
BOOL NeedsRemap(DWORD key)
{
if(key == VK_IBM_MEDIA_PREV_TRACK ||
key == VK_IBM_MEDIA_NEXT_TRACK)
{
return TRUE;
}
return FALSE;
}
/*
* The procedure we hook into Windows.
*/
LRESULT CALLBACK LowLevelKeyboardProc(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
// If this flag is set, the current key
// will be suppressed. (Except in the case
// that processing takes longer than the timeout
// values defined in HKEY_CURRENT_USER\Control Panel\Desktop)
BOOL bSuppress = FALSE;
if(nCode == HC_ACTION) {
switch (wParam) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
bSuppress = NeedsRemap(p->vkCode);
if(bSuppress)
{
DWORD dwFlags = 0;
if(wParam == WM_KEYUP ||
wParam == WM_SYSKEYUP)
{
dwFlags |= KEYEVENTF_KEYUP;
}
// inject the appropriate logic:
// below is specific logic for the backward/forward
// buttons
if(p->scanCode != 0) /* quirk; see preamble */
{
if(p->vkCode == VK_IBM_MEDIA_PREV_TRACK)
{
// backwards key
keybd_event(vk_prev, 0, dwFlags, 0);
}
else if(p->vkCode == VK_IBM_MEDIA_NEXT_TRACK)
{
// forwards key
keybd_event(vk_next, 0, dwFlags, 0);
}
}
}
break;
}
}
return (bSuppress ? TRUE : CallNextHookEx(NULL, nCode, wParam, lParam));
}
LONG InstallHook()
{
HANDLE hev;
if(g_hhk) return(ERROR_ALREADY_EXIST
g_hhk = SetWindowsHookEx(
WH_KEYBOARD_LL,
(HOOKPROC) LowLevelKeyboardProc,
g_hThisDll,
0
);
if(!(hev = CreateEvent(NULL, FALSE, FALSE, MYAPP_EVENT_NAME)))
{
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
if(!(hev = OpenEvent(EVENT_ALL_ACCESS
{
return(-1);
}
}
else
{
return(-2);
}
}
WaitForSingleObject(hev, INFINITE);
Sleep(5000);
return(0);
}
LONG TerminateHook()
{
HANDLE hev;
UnhookWindowsHookEx(g_hhk)
Sleep(5000);
if(!(hev = OpenEvent(EVENT_ALL_ACCESS
{
return(GetLastError());
}
PulseEvent(hev);
return(0);
}
And finally, the driver program, "ibmremap.cpp":
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include <process.h>
#include "ibmremaplib.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR pszCmdLine, int iCmdShow)
{
HANDLE hev;
if(!(hev = CreateEvent(NULL,
FALSE,
FALSE,
MYAPP_EVENT_NAME
)))
{
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
if(!(hev = OpenEvent(EVENT_ALL_ACCESS
FALSE,
MYAPP_EVENT_NAME
)))
{
return(-1);
}
}
else
{
return(-2);
}
}
InstallHook();
WaitForSingleObject(hev, INFINITE);
TerminateHook();
CloseHandle(hev);
return S_OK;
}
Thanks,
Kevin
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Hey, great, thanks; it worked.... kind of. I first tried it by copying your code verbatim, only changing the arguments to SetWindowsHookEx to WH_KEYBOARD_LL and my proc; however, it didn't work (it slowed down my keyboard inputs tremendously). So, I was about to come back and say that something was wrong. Before I did that, I tried putting your WH_GETMESSAGE back in and adding a proc that handles that type of hook - and it worked! However, this means that I can't suppress the key (the low level proc is needed). I am able now to catch it and do what I want, but the key still goes through. I would like to use WH_KEYBOARD_LL, but clearly there is something specific with that that is causing problems (although I know it works standalone). And I basically ported the code from to the WH_GETMESSAGE, so the code is the same. Do you have any ideas why the low level keyboard proc does not act like the getmessage proc?
Thanks so much! I'm halfway there.
Kevin Grigorenko
Thanks so much! I'm halfway there.
Kevin Grigorenko
>>However, this means that I can't suppress the key (the low level proc is needed)
Actually, you can remove a message in a WH_GETMESSAGE hook by using
MSG* pMsg = (MSG*) lParam;
MSG msg;
PeekMessage(&msg,pMsg->hWn d,0,0,PM_R EMOVE);
Actually, you can remove a message in a WH_GETMESSAGE hook by using
MSG* pMsg = (MSG*) lParam;
MSG msg;
PeekMessage(&msg,pMsg->hWn
ASKER
Two problems:
1. The PeekMessage remove doesn't work. Here is how my hook ends:
if(bSuppress)
{
MSG tempMsg;
PeekMessage(&tempMsg,msg-> hwnd,0,0,P M_REMOVE);
return 0;
}
else
return CallNextHookEx(NULL, nCode, wParam, lParam);
The actual key injection is working, so bSuppress is true, but the original key still goes through.
2. A more perplexing problem. The code works for the first 5 minutes or so, and then without any intervention stops working (the program is still running in the background, and debug messages are printed to a file, but the hook seems to somehow have been unhooked). When I restart, it works again, but again for only 5 minutes or so. Why is my hook getting unhooked? I have even left the computer as is for 5 minutes and it did the samet thing, so it is not under my intervention.
Thanks so much,
Kevin
1. The PeekMessage remove doesn't work. Here is how my hook ends:
if(bSuppress)
{
MSG tempMsg;
PeekMessage(&tempMsg,msg->
return 0;
}
else
return CallNextHookEx(NULL, nCode, wParam, lParam);
The actual key injection is working, so bSuppress is true, but the original key still goes through.
2. A more perplexing problem. The code works for the first 5 minutes or so, and then without any intervention stops working (the program is still running in the background, and debug messages are printed to a file, but the hook seems to somehow have been unhooked). When I restart, it works again, but again for only 5 minutes or so. Why is my hook getting unhooked? I have even left the computer as is for 5 minutes and it did the samet thing, so it is not under my intervention.
Thanks so much,
Kevin
That sounds odd to me, never experienced such a thing... let me do some research.
Sorry, I honestly cannot find a reason for the behaviour you outlined - it *should* work.
ASKER
It's okay, close enough, thanks so much for all of the help!
Kevin
Kevin
Garbage collector most likely disposed your hook pointer. Put GC.KeepAlive(HookPointer) somewhere at the end of your code so the Garbage collector knows not to dispose of that hook.
ASKER
Kevin