Tuesday, March 18, 2014

[ MFC ] - Using SetWindowsHookEx & WH_KEYBOARD to Set Keyboard Hook Filter Function

1. Introduction


A Windows Hook intercepts specific type of windows hardware event before it reaches its destination say target window. The intercepted hardware events are passed to a function and that function can modify the event and even it can discard it. Such a function is called as Filter function. There are different types of windows hook available. Below is the most used one (Taken from MSDN):

WH_KEYBOARD:
Installs a hook procedure that monitors keystroke messages.

WH_MOUSE:
Installs a hook procedure that intercepts the mouse hardware events.

WH_GETMESSAGE:
Installs a hook procedure that monitors messages posted to a message queue.

A hook should be attached to a Filter Function. For Example, when we attach the WH_MOUSE hook to a filter function, all the mouse hardware messages first go to the filter function before it reaches the target window. In this article, we are going to use the Keyboard hook. Note that the filter function can also be called as hook procedure as it is attached to a hook.

When more than one filter function attached to a windows hook, then this forms a Hook Chain. For example, let us say you have two filter functions for a WH_KEYBOARD, then this forms a hook chain. When a keyboard event triggers, it reaches to all the filter functions in the keyboard hook chain. Have a look at the below picture:





Hope you gone through the above picture. Let us say user pressed a numeric key ‘7’ to display that in the notepad application. The keyboard hook monitors the hardware event of pressing the keyboard key 7. So before this hardware event reaches the notepad application, the keyboard hook (WH_KEYBOARD) will direct that to the attached filter functions. The filter function(s) after processing the message sends that to the actual target window or application.

From the above picture, you can see four filter functions attached to the keyboard hook. Let us say the filter function D is added last. Now, the lastly added filter function receives the tapped keyboard event first and after processing it sends that to Filter function ‘C’. This way the event reaches the notepad application at last once the filter function ‘A’ processed the message. Ok, let us go with creating the example application.



2. About the Example


The example is a single document interface application. Under the file menu, two menu items called “Hook CAPS lock”, “Unhook CAPS Lock” are added. When the “Hook Caps Lock” menu items are clicked, the Keyboard hook is attached to the application and hooks the keyboard event. The “UnHook CAPS Lock“ event removes installed hook from the application.

Under view menu, two menu items called First Dialog and Second Dialog are added. The menu items bring two dialogs with text boxes in it. When a hook is installed, you can type only the capital letters in the dialogs. After performing the UnHook operation, you will not have upper case restriction in the dialogs. Let us jump to the explanation-based walk-through.





3. Create the Example


1) Create a MFC Single document Interface Project called WinHooks. While creating the project remove the support for Document/View architecture and Unicode libraries. Accept the defaults for all other settings. This is shown in the below video:

Video 1:  Creating the Example


2) Once the project is created, add two dialog templates to it. In each dialog add some text boxes. For each dialog add the class using the class wizard that can be accessed through the right-click context menu of the dialog template. Adding the dialog through resource and attaching a class to it is shown in the below video:

Video 2: Adding Dialog Templates



3) After adding the dialog templates to the project through the dialog editor, we need to add two menu items and handler functions. From these handler functions, we will launch our dialogs. Below video shows adding two menu items:

Video 3: Adding Menu Items


Like how we added the menu items for launching the dialogs, the same way add two menu items under the file menu to engage and disengage the hook for tapping the keyboard events. The display names of the menu items are shown in the previous picture:

4. Launching the Dialogs

1) In the MainFrm.cpp file, provide the #include pre-processor directive to include the dialog classes. Below are the two statements:

//Sample 01: Header files to access the dialogs
#include "Dialog1.h"
#include "Dialog2.h"

2) Next, in the menu items handler (Created in video 3) add the code that brings the dialog in front of the user. Note that we are launching the dialogs as Modal dialogs. Below is the code that displays the dialogs:

void CMainFrame::OnViewFirstdlg()
{
            //Sample 02: Open the First Dialog
            CDialog1 dlg;
            dlg.DoModal();
}
void CMainFrame::OnViewSeconddlg()
{
            //Sample 03: Open Second Dialog
            CDialog2 dlg;
            dlg.DoModal();
}

5. Hook Filter Function

I hope by this time you are aware of what is filter function. In this function we will add a filter function and this filter function will be attached to the windows hook that we are going to create in the coming section.

1) Add the Win32 handle for the Windows Hook as a global variable. Note that we are going to add a global filter function sooner and for convenience we are adding the win32 handle at a global level. Below is the Declaration:

//Sample 04: Win32 Handle to Keyboard Hook
HHOOK hKBHook;

2) The signature of the keyboard hook procedure is given below:

//Sample 05: A Global Filter function that will be hooked to the
//                                  Key board event
LRESULT CALLBACK KeyboardProc ( int code, WPARAM w, LPARAM l )

In the keyboard hook procedure, the code indicates how should a procedure respond. That is when we receive the negative code; the keyboard procedure will call the next hook filter function, which is in the hook chain. In our example, we are going the test “WPARAM” to know what key is pressed. The “LAPARAM” is useful to know repeat count of a button; say, for Example, a keyboard button Up Arrow pressed down for some 4 or 5 seconds. 

3) As already told the first parameter received is tested to see whether the filter function can process the message or not. When the code is lesser than zero we should not do anything the make a call to the CallNextHookEx to call the next filter function in the hook chain. Below is code, which does that:

//Sample 5.1: As Per MSDN, Do not intercept the message when
//                                    code is negative
if ( code < 0 )
            return CallNextHookEx ( hKBHook, code, w, l ) ;

4) Next using the WPARAM w, we test to see the CAPS Lock key is pressed. Have a look at the below depiction:


Note that each slot in the above picture denotes the unsigned char and hence each slot consumes 8 bits. The constant VK_CAPITAL denotes 20 and using the GetKeyBoardState function call we get all the key status in an array of 256 unsigned char slot. At the char location 20, we get the CAPS lock key status. When lower order bit is 1 then the CAPS Lock status is ON. So our filter function gets the keyboard status and always keeps the lower order bit of VK_CAPITAL as one as shown above. The net effect is, when the message reaches the application message queue, the CAPS is always ON. Below is the code which is taken from the filter function:

//Samle 5.2: Always keep the CAPS lock state to 1.
unsigned char state [256] ;                                                         
if ( w == VK_CAPITAL )
{
            GetKeyboardState(state);
            state [VK_CAPITAL] = 1 ;
            SetKeyboardState ( state ) ;
}

5) Once we are done dealing with the CAPS LOCK Toggle state, make a call to the next filter function as shown below:

//Sample 5.3: Call the Next Hook in Chain
return CallNextHookEx ( hKBHook, code, w,1 ) ;

The entire keyboard hook procedure (or) filter function is listed below:

//Sample 05: A Global Filter function that will be hooked to the
//                                  Key board event
LRESULT CALLBACK KeyboardProc ( int code, WPARAM w, LPARAM l )
{
            //Sample 5.1: As Per MSDN, Do not intercept the message when
            //                                    code is negative
            if ( code < 0 )
                        return CallNextHookEx ( hKBHook, code, w, l ) ;
            //Samle 5.2: Always keep the CAPS lock state to 1.
            unsigned char state [256] ;                                                         
            if ( w == VK_CAPITAL )
            {
                        GetKeyboardState(state);
                        state [VK_CAPITAL] = 1 ;
                        SetKeyboardState ( state ) ;
            }
            //Sample 5.3: Call the Next Hook in Chain
            return CallNextHookEx ( hKBHook, code, w,1 ) ;
}

6. Install the Hook

1) Remember, the filter function is ready in the previous section and now it is time to set the hook specifying the filter function. Before that we will set the initial caps lock state as ON. Below is the code called from the application’s InitInstance that you are already familiar in the previous section:

//Sample 06: Set the Keyboard state
unsigned char state [256] ;                           
GetKeyboardState(state);
if (state[VK_CAPITAL] == 0 )
       state [VK_CAPITAL] = 1 ;
SetKeyboardState ( state ) ;

2) The SetWindowsHookEx Win32 function installs the windows hook and attaches that to the specific filter function routine. Have a look at the below picture:


The first parameter passed to this function specifying the hook category. In our example, we are going to hook the keyboard events and hence the first parameter passed to this function is WH_KEYBOARD. The second parameter passed to this function specifies the filter function, which will receive all the intercepted keyboard messages. The third parameter specifies the module handle. Say for example, if the hook filter function is in the DLL module, this parameter specifies that dll module name. In our case, this parameter is null as same exe module is implementing the filter function. The final parameter species the thread handle linked to the hook procedure.
Below is the message map handler for the menu items, which will hook and unhook keyboard messages:

void CMainFrame::OnFileSetcapslockhook()
{
       //Sample 07: Set the Key Board Hook
       hKBHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());
}
void CMainFrame::OnFileUnhookcapslock()
{
       //Sample 08: Unset the Key Board Hook
       UnhookWindowsHookEx(hKBHook);
}

7. Running the Application

After setting the hook, launch the dialog. Once the dialogs are launched, you can observe that the dialog accepts only the Capital letters. You can see this in the below video:
Video 4: Running the Application
Source Code: Download

No comments:

Post a Comment

Leave your comment(s) here.

Like this site? Tell it to your Firend :)