2

I'm writing code that is part of an automation system. I wanted to add a keyboard hook to end the test prematurely, and I did this by using SetWindowHookEx.

My code looks pretty much like this: http://support.microsoft.com/kb/318804

Here's my SetWindowsHookEx call:

hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);

Now, when I run my automation, keypresses from within the automation system (from SendKeys) trigger the keyboard hook method, but when I hit the keyboard manually it isn't triggered.

I can share more code if that helps, but it's part of a much larger system. I'm pretty sure that either:

  1. My SetWindowsHookEx isn't correct, or
  2. Something in the automation system is bypassing my keyboard hook (I don't really know how to tell though).

I've written a test application that uses the sample code from microsoft.com to determine that my approach has merit (i.e., it works), but I'm having trouble integrating it with the automation system.

Any thoughts on how to determine where things are going wrong would be greatly appreciated.

Edit: There are no other instances of SetWindowsHookEx in the automation harness. I'm not too clear the nuances of global keyboard hooks w.r.t. threads and the desktop. If I add a global keyboard hook, should it matter from where it was added?

Jonathan Yee
  • 1,957
  • 2
  • 19
  • 21
  • I imagine something else running on the machine is "eating" the keystroke. What else is running? – Michael Todd Dec 30 '09 at 23:49
  • Your snippet is correct. That's all I can see. – Hans Passant Dec 30 '09 at 23:56
  • There's an entire automation stack that's running at the time (most, if not all, written in .NET). How can I tell if my keystrokes are being eaten? Remember, when the automation is running it's calling SendKeys to type characters into the application under test, and _these_ keys are registering with the keyboard hook. If I pound the keyboard while SendKeys is running, only the characters entered via SendKeys register. – Jonathan Yee Dec 30 '09 at 23:58
  • One more thing: The end goal is to give to users who are running the automation system the capability of exiting the test cleanly (the test never ends). Any other methods that can accomplish this are welcome. – Jonathan Yee Dec 31 '09 at 00:01
  • @Jonathan, If John's answer worked best for you, and is the best answer for your question, please don't hesitate to change your accepted answer, and accept his answer. At age 66, with my "fifteen-minutes of fame" as a programmer twenty-five years long-ago, believe me, I would not feel "slighted" in the least. It's not the length of the reply, or the research that went into it, but the accurcy and usefulness of the reply in the context of your question should be the only criteria, imho :) best, – BillW Jan 01 '10 at 03:45

2 Answers2

4

George Mamaladze's article Processing Global Mouse and Keyboard Hooks in C# which works if the application is "in the background" on CodeProject has been around since 2004, been through multiple revisions, and he's still supporting it and updating it : as I understand it, he started his project because he could not implement global hooks in .NET, that worked when the app was running in the background, but later discovered you could hook certain "lower level" events : formerly Q318804 : now MSDN article revised (?) that says you can hook WH_KEYBOARD_LL.

Perhaps, since George's code has been field-tested by so many C# programmers, over so many years, and extensively revised against bugs or problems : there's some possible value in his code for you ? In his article, in the Version 1 "FAQ" he shows code that will make the hook application specific, rather than global.

The MSDN article cited above mentions ... in the context of the allowed hooking of low level events, as you are doing :

"Low-level hook procedures are called on the thread that installed the hook. Low-level hooks do not require that the hook procedure be implemented in a DLL."

Hypothesis : could threading be related to what you are observing ?

I'm assuming you've already been through and considered all the details on : MSDN : LowLevelKeyboardProc Function

BillW
  • 3,415
  • 4
  • 27
  • 46
  • Thanks Bill. I ended up using GetAsyncKeyState (see John's answer), since I wasn't able to get my keyboard hook working. I looked at George's code earlier and couldn't see anything obviously different than what I was already doing. Thanks for the help though. – Jonathan Yee Dec 31 '09 at 22:10
1

Responding to your comment -

If you just need to test whether a key is down in order to exit your test, you could just poll on GetAsyncKeyState(), this will tell you if a particular key is down regardless of who currently has keyboard focus.

You user would have to hold a key or set of keys down long enough for your polling to notice, which means either they hold it down for a few seconds, or you have to poll more frequently than a second.

But that would be a lot less intrusive than a global keyboard hook.

Global hooks supposedly serialize parts of the kernel that normally would be async from each other, so they also harm system performance.

John Knoeller
  • 33,512
  • 4
  • 61
  • 92
  • I ended up solving my problem using GetAsyncKeyState, which worked pretty much exactly as I needed it to. I also didn't need to poll GetAsyncKeyState, since it returns a value if the key has been pressed since the last GetAsyncKeyState, so I'm able to check it at key points where I would have been exiting the test anyway (if I were to poll). I'm marking BillW's as the correct answer, since I likely missed something regarding threading, but your answer helped the most :) – Jonathan Yee Dec 31 '09 at 22:09