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()
#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.