The WndProc section of the article is primarily aimed at intermediate programmers; however I will attempt to explain the code in a step by step fashion so that raw beginners
should be able to follow it without any problems.
There are numerous reference hyperlinks (mainly Microsoft) included to cross reference the subject matter. All you newbie’s out there, please have a play around with this code
and have fun and learn.
Key loggers are a topical subject, for instance, questions like this arise; are they ethical? Are they legal? Well, this article will not delve into
these issues; I coded this to explore the newish (WinXP minimum) Raw Input Model offered by Microsoft to see how it works.
I hope this article will be the base for further discussion and exploration of the feature-rich
WM_INPUT messaging feature.
The majority of software based key loggers hook keyboard Windows APIs; the Operating System notifies the key logger when a key is pressed and the key logger then records it.
APIs such as
GetAsyncKeyState are quite often used to subscribe to keyboard events in the currently focused window.
These types of key loggers can cause problems due to constant polling of each key, they can cause a noticeable increase in CPU usage, and can also miss the occasional key.
The Raw Input Model overcomes these shortcomings.
What Microsoft says
The raw input model is different from the original Windows input model for the keyboard and mouse. In the original input model, an application receives device-independent
input in the form of messages that are sent or posted to its windows, such as
WM_APPCOMMAND. In contrast, for raw input,
an application must register the devices it wants to get data from. Also, the application gets the raw input through the
There are several advantages to the raw input model:
- An application does not have to detect or open the input device.
- An application gets the data directly from the device, and processes the data for its needs.
- An application can distinguish the source of the input even if it is from the same type of device. For example, two mouse devices.
- An application manages the data traffic by specifying data from a collection of devices or only specific device types.
- HID devices can be used as they become available in the marketplace, without waiting for new message types or an updated OS to have new commands in
I came across the Raw Input Model by happenstance whilst browsing CodeProject and read an excellent article by Emma Burrows,
Using Raw Input from C# to handle multiple keyboards and it inspired me to Google 'Raw Input MSDN',
and sure enough Microsoft explains it well here.
My choice of language in this article is based upon my experience. I have coded professionally in .NET, but I personally choose C, C++, or MASM32 (due to my device
driver programming background) when dealing with base Windows APIs. (Old habits die hard), however it may be translated to your preference, Visual Basic or C# for instance.
Using the code
The WinMain, skip this section if you are familiar with the subject
The entry point to a user Windows based application is the
int WINAPI WinMain(HINSTANCE hInstance,
hInstance parameter is the handle to this application when run.
hPrevInstance is a handle to a previous instance of this program, it is always
NULL and to keep this code simple,
I have chosen not to detect if a previous instance is already running; however, it certainly would be a good thing to do.
lpCmdLine is a pointer to a null terminated string command line for the application. We don't use it here, but to see it in action, open a command prompt and type in
explorer /select,c:\windows\. This will open Windows Explorer at the Windows directory.
nCmdShow controls how the window is to be shown. This value is actually passed from the Operating System. To see this in action, create a shortcut to Notepad on your
Desktop, then right click it to bring up properties and change Run from Normal window to Maximized. With this app, the default
SW_SHOWNORMAL (1) is passed in.
Creating the invisible message only window, how it's done
The next item to consider is the
WNDCLASS structure, its members set
the window class attributes. The attributes include style, background color, icon, cursor, menu, and window procedure.
Our program here only sets up three essential members as we are creating an invisible window.
wc.hInstance = hInstance; wc.lpszClassName = L"kl"; wc.lpfnWndProc = WndProc;
RegisterClass function registers the window class with the
WNDCLASS struct we just populated.
RegisterClass(&wc); hWnd = CreateWindow(wc.lpszClassName,NULL,0,0,0,0,0,HWND_MESSAGE,NULL,hInstance,NULL);
We only pass bare essentials to
CreateWindow namely the class name,
our app handle, and the
HWND_MESSAGE constant to create an invisible Message
The message loop, TranslateMessage not required
In general, Windows programs are essentially message handlers; applications, the OS, and hardware all generate Windows messages that an application listens for and reacts to.
GetMessage function dispatches incoming sent messages until a posted message
is available for retrieval. The
MSG struct receives the messages. The message loop code below will only exit on
WM_QUIT (0), otherwise it translates and dispatches messages continuously.
TranslateMessage normally translates virtual key messages into character messages that are retrieved next time through the loop by
In part of the raw input setup explained below, we suppress the messages that
TranslateMessage normally handles in the loop.
Registering interest in receiving raw data on the WM_CREATE event in WndProc
DispatchMessage dispatches messages to the window procedure (
WndProc) that we declared in the
WINDCLASS struct initially.
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
Unlike most window messages which are freely available, a Windows application does not receive the raw input message
WM_INPUT by default; to receive it, we must first register interest
RegisterRawInputDevices function. The first parameter points to the array of
RAWINPUTDEVICE structs. The second parameter sets the number of structs,
in our case 1. The last parameter is the size of the struct.
WM_CREATE, a log file is created and the current time and date
are logged. Interest in receiving keyboard raw input is registered with the
RIDEV_INPUTSINK flag set in the
RAWINPUTDEVICE struct so that we receive system wide keystrokes
and not just ones received in the focused window, which in our case is invisible anyway.
RIDEV_NOLEGACY flag is set so that
and other legacy key events are not generated for the message loop; how and where we get these messages from will become clear in the
WM_INPUT section shortly.
RAWINPUTDEVICE usUsagePage and usUsage
usUsagePage is a value for the type of device (this is a partial list below). We use 1 here as that stands for 'generic desktop controls' and covers all the usual input devices.
usUsage value specifies the device within the 'generic desktop controls' group.
- 1 - generic desktop controls // we use this
- 2 - simulation controls
- 3 - vr
- 4 - sport
- 5 - game
- 6 - generic device
- 7 - keyboard
- 8 - LEDs
- 9 - button
usUsage values when
usUsagePage is 1:
- 0 - undefined
- 1 - pointer
- 2 - mouse
- 3 - reserved
- 4 - joystick
- 5 - game pad
- 6 - keyboard // we use this
- 7 - keypad
- 8 - multi-axis controller
- 9 - Tablet PC controls
Upon receipt of
WM_INPUT messages, we call
GetRawInputData. Notice that we call it twice, once to determine how big the buffer should be,
and once again to utilize the buffer.
LPBYTE lpb=new BYTE[dwSize];
To explain this more fully, the
GetRawInputData first parameter is a handle
structure from the device, whose members are, due to
RAWINPUT handle is provided for us in the
lParam of the
WM_INPUT message. The second parameter we set to
RID_INPUT to get the devices raw data, it may also be set to
RID_HEADER to get the data header information; however, we do not use it here.
The third parameter is a void pointer to the buffer to be used. The fourth parameter is the address of a variable that receives the required size of the buffer.
The final parameter is the size of the
On the first call, we set the third parameter to
NULL as we are only interested in obtaining the size of the buffer that we need to create. On the next call,
we set it to point to the newly created buffer itself.
Next we obtain the virtual key code from
keyboard.VKey and translate it into a character, then we filter
keyboard.Message for the
so that we do not double up on logged keystrokes. Note, all messages are retrieved from
STRSAFE_MAX_CCH,TEXT(" Kbd: make=%04x Flags:%04x " +
"Reserved:%04x ExtraInformation:%08x, msg=%04x VK=%04x \n"),
Log to file
Finally we do a rudimentary filter for backspace, tab, and carriage return, and ignore all else below 'space' and above '~' just so that the log file reflects what was actually
keyed in (from Notepad for instance).
break; } }
How to test
Build and run kl.exe, then open Notepad and type in some stuff, then open kl.log to see the result.
All logged entries in the log file are upper case, no apologies for this, please find out how this can be resolved. This code is a starting point for your research,
please enjoy, and get back to me with your suggestions or questions.
Points of interest
I am running Bit Defender Antivirus Plus 2012 on my Win 7 development computer and to its credit, it picked up this code as being suspect and I had to permit it as an exception to run.
If you have similar issues with your anti virus program, allow it also.
If you wish to auto start this program on boot, create this Registry key 'kl' (at your own risk I might add). Don't do it if you are not experienced with Registry edits.
Modify the kl key to C:\kl.exe (or your drive letter if not C:).
Then copy kl.exe to your root directory.
This is my first article on CodeProject, I hope you like it.
P.S.: If you are capable with MASM32, try it too.