Introduction
I have seen a lot of people asking about hooking WM_CHAR messages. There are a lot of articles on keyboard hooking using WH_KEYBOARD and WH_KEYBOARD_LL. But these hooks trap the WM_KEYDOWN, WM_KEYUP, WM_SYSKEYUP, and WM_SYSKEYDOWN messages. These messages send virtual key codes and scan codes. Anyone who wants to work with ASCII values has to convert these virtual key codes to ASCII values. However, not all ASCII values have corresponding virtual key codes defined. Moreover, there are no virtual key codes for extended ASCII values (codes greater than 127) and punctuation marks like ,. etc. So, how can a user output an extended character when key 'A' is pressed ? The solution is hooking WM_CHAR messages.
About the WH_GETMESSAGE hook
When a key is pressed, Windows sends a WM_KEYDOWN message followed by a WM_CHAR message. Not all WM_KEYDOWN messages are followed by a WM_CHAR message. There are other messages like WM_DEADCHAR, WM_SYSDEADCHAR, which Windows sends to applications. Anyway, I am not going to discuss about these messages. Now, coming to our subject, we can trap a WM_CHAR message using the WH_GETMESSAGE hook. The WH_GETMESSAGE hook enables an application to monitor messages about to be returned by the GetMessage() or PeekMessage() function. You can use the WH_GETMESSAGE hook to monitor mouse and keyboard input and other messages posted to the message queue. In the hook procedure, the message parameters can be changed, new messages can be sent, or the message can be blocked from reaching the application.
Hooks can be installed using the SetWindowsHookEx function and removed using the UnhookWindowsHooks function. The syntax for these functions are:
HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);
Parameters:
idHook - [in] - Specifies the type of hook procedure to be installed. We will be using WH_GETMESSAGE here. For other values, refer to MSDN.lpfn - [in] - Pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process.hMod - [in] - Handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.dwThreadId - [in] - Specifies the identifier of the thread with which the hook procedure is to be associated. If this parameter is zero, the hook procedure is associated with all existing threads running in the same desktop as the calling thread.
If the function succeeds, the return value is the handle to the hook procedure. If the function fails, the return value is NULL.
BOOL UnhookWindowsHookEx( HHOOK hhk);
Parameters:
hhk - [in] - Handle to the hook to be removed. This parameter is a hook handle obtained by a previous call to SetWindowsHookEx.
Using the code
Here, we will be developing a system wide hook for WH_GETMESSAGE. As most of you might know, a system wide hook must be implemented using a DLL. We will be developing a DLL which contains the hook procedure, and an application to install or remove the hook.
Create a Win32 DLL project and select 'A simple DLL project option' in step 1. Visual Studio will create the necessary files. Open the source file in which the function DllMain is defined. This is the function which is called by the system whenever this DLL is attached/detached to a process/thread. Add two global variables at the top of the code file, as shown below.
#include "stdafx.h"
#pragma data_seg("Shared")
HHOOK hkKey = NULL;
#pragma data_seg() //end of our data segment
#pragma comment(linker,"/section:Shared,rws")
HINSTANCE hInstHookDll=NULL;
Here, we have created two global variables. The variable hInstHookDll will store the hinstance of our DLL. The hinstance will be passed to us as a parameter to the DllMain function. The variable hkKey will be returned by calling SetWindowsHooksEx(). The hook procedure, which we will develop, and the UnhookWindowsEx() function will require this variable. Notice that this variable is stored in a new data segment, Shared. This is done because this variable will be shared between all the processes to which our DLL code is loaded.
We need to initialize our global variables. As said earlier, our DLL instance is passed as an argument to the DllMain() function. We will be initializing hInstHookDll when the DLL is attached to a process.
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved )
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hInstHookDll = (HINSTANCE)hModule;
break;
}
return TRUE;
}
We will be creating two functions, SetHook() and RemoveHook(), in which we will write code to set and remove our global hook.
void __stdcall SetHook()
{
if(hkKey == NULL)
hkKey = SetWindowsHookEx(WH_GETMESSAGE,procCharMsg,hInstHookDll,0);
}
void __stdcall RemoveHook()
{
if(hkKey !=NULL)
UnhookWindowsHookEx(hkKey);
hkKey = NULL;
}
Here, procCharMsg is our hook procedure that will be called by the system before the message is retrieved by the application using the GetMessage or PeekMessage functions. The code for the hook procedure goes like this:
LRESULT CALLBACK procCharMsg(int nCode,WPARAM wParam, LPARAM lParam)
{
MSG *msg;
char charCode;
if(nCode >=0 && nCode == HC_ACTION)
{
msg=(MSG *)lParam;
if(msg->message==WM_CHAR)
{
charCode = msg->wParam;
if(IsCharLower(charCode))
{
charCode -=32;
msg->wParam=(WPARAM)charCode;
}
}
}
return CallNextHookEx(hkKey,nCode,wParam,lParam);
}
If nCode is greater than or equal to 0 and is HC_ACTION, we will process the message. The lParam parameter is a pointer to the MSG structure. We will check the MSG structure's message data member. If it is a WM_CHAR message, we will process this message. The MSG structure's wParam is the character code. We will check if the character is lower case; if so, we will change it to upper case. We will pass this message to the next hook using the CallNextHokEx() function.
Lastly, we create a definition file (.def) to export our functions:
EXPORTS
SetHook
RemoveHook
procCharMsg
Finally, build the DLL.
Create a Win32 application and add two menus: 'Set Hook' and 'Remove Hook'. Make changes to the application's window procedure.
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDC_START_HOOK: hmodDll = LoadLibrary("hookDll.dll");
if(hmodDll == NULL)
return 0;
ptrFunc = (pFunc)GetProcAddress(hmodDll,"SetHook");
if(ptrFunc == NULL)
return 0;
ptrFunc(); break;
case IDC_STOP_HOOK:
if(hmodDll == NULL)
return 0;
ptrFunc = (pFunc)GetProcAddress(hmodDll,"RemoveHook");
if(ptrFunc == NULL)
return 0;
ptrFunc(); FreeLibrary(hmodDll); ptrFunc = NULL;
hmodDll = NULL;
break;
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
In the command handlers, we load our library and get the function address of SetHook/RemoveHook and store it into a function pointer. Then, we will call the function using the function pointer. We have to define the function pointer type and a variable.
Define the function pointer type at the top of the code file:
typedef void (__stdcall *pFunc)(void);
Define the static variables in the window procedure to store the value returned by the LoadLibrary() function and the pointer function variable.
static HMODULE hmodDll = NULL;
static pFunc ptrFunc;
Build the application and copy the DLL that was created above to the application's executable folder. Run the application and click the 'Set Hook' menu. Run the Notepad application and type something. You can see that all the characters typed will be in uppercase. Now, click 'Remove Hook' menu in our application. The uppercase behaviour in the Notepad application will be removed.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.