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
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
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:
- 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.
- Call
InstallKeyHook
to install the hook. - Call
AddKeyEntry
to register key entries which consist of information on what kinds of keyboard events should be captured and whom should be notified. - 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. - 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.
class CMyDlg : public CDialog
{
protected:
LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
}
#define WM_MY_MESSAGE (WM_APP + 100)
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage)
END_MESSAGE_MAP()
BOOL CMyDlg::OnInitDialog()
{
CDialog::OnInitDialog();
LRESULT lRes = InstallKeyHook();
ASSERT(lRes == KH_OK);
KEYENTRY ke;
ke.nMessage = WM_MY_MESSAGE;
ke.hCallWnd = m_hWnd;
ke.hHookWnd = 0;
ke.iCombKeys = 0;
ke.iIndicators = 0;
ke.iKeyEvent =
KH_KEY_DOWN | KH_KEY_REPEAT;
ke.iMinVKCode = 0;
ke.iMaxVKCode = 255;
lRes = AddKeyEntry(&ke);
ASSERT(lRes == KH_OK);
return TRUE;
}
void CMyDlg::OnDestroy()
{
CDialog::OnDestroy();
UninstallKeyHook();
}
LRESULT CMyDlg::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
KEYRESULT kr;
UINT nMask = KH_MASK_EVENTTYPE | KH_MASK_PRINTABLECHAR;
LRESULT lRes = GetKeyEventResult(wParam, lParam, &kr, nMask);
ASSERT(lRes == KH_OK);
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);
CString sMsg;
sMsg.Format(_T("Event: %s\nCharacter:%c"), sEvent, kr.chPrintableChar);
MessageBox(sMsg);
}
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
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.