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

An All-Purpose Keyboard Hooker

0.00/5 (No votes)
30 May 2004 1  
An easy to use keyboard hooking DLL that is suitable for most applications.

Sample Image - Keyhook_Demo.gif

The Problem

Although installing and maintaining keyboard hooks are said to be simple because all we need to look into is the ::SetWindowsHookEx API, there are a couple of things that are worth at least some thinking. One issue is caused by the fact that if we need to hook all running threads, which is most likely the case, then the callback process-function must be located in a DLL. This rule by itself isn't the problem but the attenuation on the DLL reusability caused by which definitely is. Every application will process the captured keyboard events in its own ways, so why should we build a DLL that is only suitable for one program?

The Solution

The rule is not to be changed, so the function will have to be located in a DLL no matter what, however, we can try to maximize the reusability of the DLL so that it can be suitable for almost all kinds of applications, that is, make it an all-purpose keyboard hooking DLL.

To achieve the goal, some important points have been taken into account:

  • Synchronization

    The DLL is to be used by multiple processes, among which some data are shared, it is crucial that we assure synchronized access to those shared pieces of data. In this project, a mutex is used for this purpose.

  • Specific Keyboard Events Definition

    What kinds of keyboard events are to be captured? Under what conditions? Whom should be notified and how they should be notified when an appropriate keyboard event has been captured? All these specified information will help the applications to hook the right events. The KEYENTRY struct defined in this project wraps all the details, it is to be described later.

  • Notifying the Applications Effectively

    When a keyboard event is determined to match all criteria and conditions that are defined by one application, it could also match all criteria and conditions defined by other application(s), too, so multiple applications may need to be notified upon one event. Usually, we notify an application by either using a callback function, or send a message to the application's window via the Windows message system. Using callback functions is not safe here because we must wait until the function returns. If a poor-written application has an infinite loop inside the function, then we get messed up altogether. Using the Windows message system is an alternative and, obviously ::SendMessage should be avoided because of the same problem described above, so finally, ::PostMessage seemed to be the choice.

    There are some down-sides on using ::PostMessage, though. Firstly, it is not guaranteed to be up-to-time, a message sent today using ::PostMessage could be received tomorrow, a little exaggerated, I admit, but by the time the applications receive the notification, system information such as keyboard states, foreground windows, etc. could have changed already, so the DLL must try to send all those valuable information at the moment it captured the events. Secondly, an even worse limit, on Win32 platform, we can only send up to 64 bits of data to the application a time, 32 bits by the wParam and 32 bits by the lParam. I wouldn't even think of allocating data in the heap memory and sending pointers to the applications, the message may never be received and the memory will probably leak. In this project, the wParam is occupied by the handle to the window in which the captured keyboard event occurred, so we need to find a way to pack as much data as possible, into the lParam, while insuring the important portions (such as scan code, previous key state, up-down flag, etc.) of the original lParam remain intact.

  • Comprehensive Keyboard Event Information Retrieval

    As an all-purpose DLL, it is essential that as much detailed information of the captured keyboard events as possible, should be able to be retrieved by the applications. The KEYRESULT struct defined in this project wraps all the detailed information, it is to be described later.

Implementation

Defining the Keyboard Event

The KEYENTRY struct defines what kinds of events should be captured and how should the captured events be processed. It has the following members:

Type Name Comments
UINT nMessage The notification message that will be sent when appropriate key events are captured, must not be zero.
HWND hCallWnd Handle to the window to which the notification messages will be sent, must not be null.
HWND hHookWnd Handle to the window in which key events occur. If set to null, key events occurred in any window are to be captured.
BYTE iKeyEvent Types of the key events (key-down, key-up, key-repeat) to capture. If set to 0, all kinds of events are to be captured.
BYTE iMinVKCode The minimum virtual key code to capture, must not be greater than iMaxVKCode.
BYTE iMaxVKCode The maximum virtual key code to capture, must not be smaller than iMinVKCode.
BYTE iCombKeys Combination Key Flags (alt, ctrl, shift). If set to 0, combination key states do not matter.
BYTE iIndicators On/off states of keyboard indicators (caps-lock, num-lock, scroll-lock). If set to 0, indicator states do not matter.
DWORD dwReserved Reserved, do not use.

Values of iKeyEvent can be a combination of the following:

  • KH_KEY_DOWN,
  • KH_KEY_UP,
  • KH_KEY_REPEAT.

Values of iCombKeys can be a combination of the following:

  • KH_ALT_PRESSED,
  • KH_ALT_NOT_PRESSED,
  • KH_CTRL_PRESSED,
  • KH_CTRL_NOT_PRESSED,
  • KH_SHIFT_PRESSED,
  • KH_SHIFT_NOT_PRESSED,

Values of iIndicators can be a combination of the following:

  • KH_CAPSLOCK_ON,
  • KH_CAPSLOCK_OFF,
  • KH_NUMLOCK_ON,
  • KH_NUMLOCK_OFF,
  • KH_SCRLOCK_ON,
  • KH_SCRLOCK_OFF,

Retrieving the Captured Keyboard Event Information

The KEYRESULT struct is used to retrieve detailed information of the captured keyboard events. It has the following members:

Type Name Comment
HWND hOccurredInWnd Handle to the window in which the key event occurred.
BYTE iKeyEvent The captured key event type (key-down, key-up, or key-repeat).
BYTE iVKCode The virtual key code produced by the captured key-stroke.
BYTE iCombKeys Combination Key Flags (alt, ctrl, shift) when the key event was captured.
BYTE iIndicators On/off states of keyboard indicators (caps-lock, num-lock, scroll-lock) when the key event was captured.
TCHAR chPrintableChar The printable character produced by the captured key-stroke, 0 if not printable.
TCHAR szKeyName[32] Name of the key. E.g.: "Shift".

Note: KEYRESULT is a typedef of either tagKeyResultW or tagKeyResultA, depending on whether UNICODE is defined. chPrintableChar and szKeyName[32] are either wchar_t or char.

Values of iKeyEvent, iVKCode, iCombKeys and iIndicators are similar with those members of the KEYENTRY struct except that this struct is used to retrieve data only, so those values will represent actual event data. For example, in a KEYENTRY struct, its iKeyEvent member could be KH_KEY_DOWN | KH_KEY_REPEAT to indicate that the user wants to capture key-down and key-repeat events, whereas in a KEYRESULT struct, the iKeyEvent will never be such a combination because if a captured event was a key-down, then it was not a key-repeat.

DLL Exported Functions

Functions exported by the DLL are few in number and easy to use, below are description of some essential functions. For a complete list of functions and return codes, please download and take a look at KeyHook.h.


LRESULT __declspec(dllexport) InstallKeyHook();

Return Values

The function returns KH_OK if succeeded, an error code otherwise.

Remarks

Registers current process to the hook. Applications must call this function before using the hook.


LRESULT __declspec(dllexport) UninstallKeyHook();

Return Values

The function returns KH_OK if succeeded, an error code otherwise.

Remarks

Un-registers current process from the hook. Applications should call this function as soon as they finish using the hook, to release all occupied global resources.


LRESULT __declspec(dllexport) AddKeyEntry(LPCKEYENTRY lpEntry);

Parameters

  • lpEntry

    [in] Address of a KEYENTRY struct which contains information of the key event to be captured.

Return Values

The function returns KH_OK if succeeded, an error code otherwise.

Remarks

Adds a new key entry to the hook entry list, o that key events match the conditions defined in lpEntry will be captured.


LRESULT __declspec(dllexport) RemoveKeyEntry(LPCKEYENTRY lpEntry);

Parameters

  • lpEntry

    [in] Address of a KEYENTRY struct which contains information of the key entry to be removed from the hook entry list.

Return Values

The function returns KH_OK if succeeded, an error code otherwise.

Remarks

Removes a specified key entry from the hook entry list, so that the specified key event will no longer be captured. Key entries that were added by other processes are not accessible and will not be affected.


LRESULT __declspec(dllexport) RemoveAllKeyEntries();

Return Values

The function returns KH_OK if succeeded, an error code otherwise.

Remarks

Removes all key entries that were added by current process from the hook entry list. Key entries that were added by other processes are not accessible and will not be affected.


LRESULT __declspec(dllexport) GetKeyEventResult(WPARAM wParam, LPARAM lParam, LPKEYRESULT lpKeyResult, UINT nMask = KH_MASK_ALL);

Parameters

  • wParam

    [in] The WPARAM that was received with the notification message.

  • lParam

    [in] The LPARAM that was received with the notification message.

  • lpKeyResult

    [out] Address of a KEYRESULT struct which will receive detailed information of the captured key event.

  • nMask

    [in] Specifies which member(s) of the KEYRESULT struct must be filled in.

Return Values

The function returns KH_OK if succeeded, an error code otherwise.

Remarks

When a predefined key event is captured, the message defined in KEYENTRY::nMessage will be sent to the target window defined in KEYENTRY::hCallWnd, along with a wParam and a lParam, which contain detailed information of the captured key event. This function parses those parameters and extracts the information.

Values of nMask can be a combination of the following. If you do not need all the information, then specify as few flags as possible to reduce computing time.

Symbol Meaning
KH_MASK_ALL All members must be filled in.
KH_MASK_OCCURREDWND The hOccurredInWnd member must be filled in.
KH_MASK_EVENTTYPE The iKeyEvent member must be filled in.
KH_MASK_VKCODE The iVKCode member must be filled in.
KH_MASK_COMBKEYS The iCombKeys member must be filled in.
KH_MASK_INDICATORS The iIndicators member must be filled in.
KH_MASK_PRINTABLECHAR The chPrintableChar member must be filled in.
KH_MASK_KEYNAME The szKeyName member must be filled in.

Todo

To use the DLL, you may follow these easy steps:

  1. Copy KeyHook.h, KeyHook.dll, Keyhook.lib into your project directory, add KeyHook.lib into your workspace by "Project - Add to Project - Files", and include KeyHook.h where needed.
  2. Call InstallKeyHook to install the hook.
  3. Call AddKeyEntry to register key entries which consist of information on what kinds of keyboard events should be captured and whom should be notified.
  4. Call GetKeyEventResult to retrieve information of the captured key events after your application window receives the notification messages, with values of the wParam and lParam unaltered.
  5. Call UninstallKeyHook to release resources as soon as you've finished using the hook.

That's it! Really simple, isn't it? Now, it's time for some code sample.

Code Sample

In this sample, we will be writing a key logger which records key-strokes occurring in any window, and no, I am by no means abetting password stealing.

///////////////////////////////////////////////////////
// MyDlg.h
///////////////////////////////////////////////////////

class CMyDlg : public CDialog
{
    // ... ...

protected:

    // Add a function to handle the notification message
    LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
}


///////////////////////////////////////////////////////
// MyDlg.cpp
///////////////////////////////////////////////////////

// ID of the notification message that will be sent to us
#define WM_MY_MESSAGE (WM_APP + 100)

BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
 //{{AFX_MSG_MAP(CMyDlg)
 // ... ... 
 //}}AFX_MSG_MAP
 ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage) // Maps our message
END_MESSAGE_MAP()

BOOL CMyDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // ... ...

    // TODO: Add extra initialization here

    LRESULT lRes = InstallKeyHook(); // Install the hook
    ASSERT(lRes == KH_OK);

    // Prepare the KEYENTRY struct
    KEYENTRY ke;
    
    ke.nMessage = WM_MY_MESSAGE; // Our message ID
    ke.hCallWnd = m_hWnd; // Send message to this window
    ke.hHookWnd = 0; // Capture key-strokes occurred in any windows
    ke.iCombKeys = 0; // Combination key states do not matter
    ke.iIndicators = 0; // Caps-lock, Num-lock,
                       // Scroll-lock on/off states do not matter
    ke.iKeyEvent = 
      KH_KEY_DOWN | KH_KEY_REPEAT; // Capture key-down and key-repeat events
    ke.iMinVKCode = 0; // Capture all keys regardless of their virtual key codes
    ke.iMaxVKCode = 255;

    // Add the entry to the hook
    lRes = AddKeyEntry(&ke);
    ASSERT(lRes == KH_OK);

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CMyDlg::OnDestroy() 
{
    CDialog::OnDestroy();

    // TODO: Add your message handler code here
    UninstallKeyHook(); // Uninstall the hook to cleanup.
}

// Handler of WM_MY_MESSAGE notification
LRESULT CMyDlg::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
    // In this sample we will display the event types and
    // characters that the captured key events produced.

    KEYRESULT kr; // The struct to receive information

    // Information that we need to extract are event type and printable character
    UINT nMask = KH_MASK_EVENTTYPE | KH_MASK_PRINTABLECHAR;
 
    // This function extracts event details
    LRESULT lRes = GetKeyEventResult(wParam, lParam, &kr, nMask);
    ASSERT(lRes == KH_OK);

    // We only display key-strokes that produce printable characters
    if (kr.chPrintableChar != 0)
    {
        CString sEvent;
        if (kr.iKeyEvent == KH_KEY_DOWN)
            sEvent = _T("Key Down");
        else if (kr.iKeyEvent == KH_KEY_UP)
            sEvent = _T("Key Up");
        else if (kr.iKeyEvent == KH_KEY_REPEAT)
            sEvent = _T("Key Repeat");
        else
            ASSERT(FALSE); // will never happen

        CString sMsg;
        sMsg.Format(_T("Event: %s\nCharacter:%c"), sEvent, kr.chPrintableChar);
        MessageBox(sMsg); // display the result
    }

    return (LRESULT)0;
}

Limitations

There are limits on maximum number of processes that can use the hook simultaneously and maximum number of entries that can be registered by all processes. If those limits are reached, subsequent operations may fail. Current version supports up to 256 unique processes to add up to 1024 key entries in total.

This DLL is not a low-level keyboard hooker so it does not intercept low-level system key events, such as Alt-Tab, Ctrl-Alt-Del, etc. To capture those events, you will need to specify WH_KEYBOARD_LL instead of WH_KEYBOARD, as the 2nd parameter of ::SetWindowsHookEx. Please check SetWindowsHookEx on MSDN for details of low-level keyboard hooking.

History

v1.00 -- May 06, 2004

  • Initial release.

v1.01 -- May 14, 2004

  • Fixed an error on verifying combination keys and keyboard indicators.
  • Updated the demo project, so now it demonstrates the use of the hook more clearly.

v1.02 -- May 26, 2004

  • Fixed some minor problems on events translation.
  • Removed unnecessary combo-key flags and indicator flags.

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