I'm not sure how your keyboard hooking code works in the background, but first up, Mr Chen blogged about the problem of using PostMessage
for this type of thing. As he says, if you want to send input that gets stuck into the keyboard queue, use SendInput
.
For example, one way to capture keypresses is to use SetWindowsHookEx
and write an event handler as part of a DLL loaded in via LoadLibrary
. This will then receive all the keyboard input you feed the system, and is one way for example to implement global hotkeys (as in, for the whole system).
The problem with this mechanism is that if the hook takes too long to process that input, Windows boots it out and never passes any more messages to it. Charming. It turns out there's actually a Windows 7 bug where the OS chucks hooks out for no good reason, too.
Anyway, OK, back on to task. It is possible, as one explanation, that your keylogger isn't showing any measurable performance difference because it's not actually installed. This is worth verifying.
Secondly, however you hook, you're going to incur a performance penalty, whatever the marketing speak says. There's extra instructions being executed whichever way you cut it, since if you're using a driver, Windows is clattering through an extra device in the driver stack and if you're using SetWindowsHook
, Windows is enumerating and calling an extra hook.
What might be going wrong is the measurement process. This is pretty hard to do, especially when you account for confounding factors like context switches, IO delay etc. Depending on how you attempt to measure input time, you might just be missing the difference.
It gets more technical than that too - for example, rtdsc
doesn't work all that well with multi-core systems these days, so if you're using that, it might not work. You need the appropriate API function to get a decent resolution timer.
Another point to note is the presence of a debugger. If you've ever debugged an application under say Visual Studio, you'll be well aware as DLLs load, debugging symbols get loaded too, introducing massive delays. I'm sure you've realised this, but worth repeating.
Here's how I'd approach this:
- Write a Windows application. Log
WM_KEYPRESS
messages - time and keypress.
- Have a thread.
- Have a button that triggers the thread to begin sending
SendInput
messages.
- Run this process for a while - you need to get enough data to negate any confounding factors.
- Stop.
- Repeat with keylogger loaded in.
Needless to say, I'd load this on a physical, not virtual machine, with as little running as possible.
Will you see anything? Honestly, I don't know. It looks like some have tried using this and other metrics, but I'm not sufficiently privileged to be allowed to read papers that are the advancement of human knowledge and that my taxes might have contributed to. If you can, do let me know if it's any good!
Added detail a context switch may occur whenever you make an API call, but does not necessarily occur. For example, if you call an IO-related function and the API must block, your process will then be suspended and the scheduling algorithm run to determine if any waiting processes are either starved of time, or have IO messages they need to handle. Depending on this, you, or another process, might well be woken up and run. The process then repeats.
If a switch between processes is merited, this is comparatively expensive (on the minute scale that we'd be measuring here) then the current register state is saved, your process is mapped out and another is mapped in. This is clearly more expensive than jumping back to user land.
Another potential gotcha are interrupts. On Linux (not sure about Windows), interrupts preempt everything when allowed to run, so a call may take more time simply because during that time, the kernel handled more interrupts than the previous time.
Interrupts don't always (in fact, probably shouldn't need) to switch process context, but there may be more or less of them at any given point, depending on what the hardware is doing. More of them means more CPU cycles between call and completion of that call.