Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

System Wide Hooking for the WM_CHAR Message

0.00/5 (No votes)
4 Jan 2010 1  
How to hook the WM_CHAR message using the WH_GETMESSAGE hook.

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"  


//these variables will be shared among all processes to which this dll is linked
#pragma data_seg("Shared")
//our hook handle which will be returned by calling SetWindowsHookEx function
HHOOK hkKey = NULL;
#pragma data_seg() //end of our data segment

#pragma comment(linker,"/section:Shared,rws")
// Tell the compiler that Shared section can be read,write and shared

HINSTANCE hInstHookDll=NULL;  //our global variable to store the instance of our DLL

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:
        //we initialize our variable with the value that is passed to us
        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);
}

//remove the hook
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)
//this is the hook procedure
{
    //a pointer to hold the MSG structure that is passed as lParam
    MSG *msg;
    //to hold the character passed in the MSG structure's wParam
    char charCode;
    if(nCode >=0 && nCode == HC_ACTION)
    //if nCode is less than 0 or nCode
    //is not HC_ACTION we will call CallNextHookEx
    {
        //lParam contains pointer to MSG structure.
        msg=(MSG *)lParam;
        if(msg->message==WM_CHAR)
        //we handle only WM_CHAR messages
        {
            //For WM_CHAR message, 
            //wParam is the character code of the key pressed
            charCode = msg->wParam;
            if(IsCharLower(charCode))
            //we check if the character pressed is a small letter
            {
                //if so, make it to capital letter
                charCode -=32;
                msg->wParam=(WPARAM)charCode;
                //overwrite the msg structure's wparam 
                //with our new value. 
            }
        }
    }
    return CallNextHookEx(hkKey,nCode,wParam,lParam);
    //passing this message to target application
}

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); 
    // Parse the menu selections:
    switch (wmId)
    {
        case IDC_START_HOOK:           //load our hook dll
            hmodDll = LoadLibrary("hookDll.dll");
            if(hmodDll == NULL)
                return 0;
            //get the address of SetHook function
            ptrFunc = (pFunc)GetProcAddress(hmodDll,"SetHook");
            if(ptrFunc == NULL)
                return 0;
            ptrFunc();                //call sethook function
            break;
        case IDC_STOP_HOOK:
            if(hmodDll == NULL)
                return 0;
            //get the address of RemoveHook function
            ptrFunc = (pFunc)GetProcAddress(hmodDll,"RemoveHook");
            if(ptrFunc == NULL)
                return 0;
            ptrFunc();               //Call Rmove hook
            FreeLibrary(hmodDll);    //unload our hook dll
            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);
//function pointer to call sethook and removehook functions

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here