Robert Snyder
Robert Snyder

Reputation: 2409

Nunit not getting SetWindowsHookEx events

I have a KeyboardHook that is in use in production. Works great. Yesterday I wanted to make a Unit test to test out an idea I had where I make a abstract class that has a virtual method that matches the HOOKPROC delegate. something like this..

public abstract class HookRuleBase : IDisposable
{
    private readonly IntPtr hookId;
    private static KeyboardProc thisDelegate;
    public HookRuleBase()
    {
        thisDelegate = new KeyboardProc(ProcessKey);
        IntPtr user = Kernel32.LoadLibrary("User32.dll");
        hookId = User32.SetWindowsHookEx(WH_KEYBOARD_LL, thisDelegate, user, 0);
        Console.WriteLine(hookId.ToInt32().ToString("X8"));
    }

    public void Dispose()
    {
        var disposed = User32.UnhookWindowsHookEx(hookId);
    }

    /// <summary>
    /// If your rule does not need to handle the key or the key's current state then call this base
    /// method.
    /// </summary>
    public virtual int ProcessKey(int code, System.UIntPtr wParam, ref KeyboardHookStruct lParam)
    {
        return User32.CallNextHookEx(hookId, code, wParam, ref lParam);
    }

    private const int WH_KEYBOARD_LL = 13;
}

What I plan on doing with that is replacing some of the static methods I have in my class called KeyboardHook. I think it follows the Strategy pattern... I think... Anywho. So I made a NUnit project. Made a very simple class that extends my HookRuleBase

    private class HookRuleTest : HookRuleBase
    {
        public override int ProcessKey(int code, UIntPtr wParam, ref KeyboardHookStruct lParam)
        {
            Console.WriteLine("ProcessKey:code:{0}, wParam:{1:X8}, {2}", code, wParam.ToUInt32(), lParam.ToString());
            return base.ProcessKey(code, wParam, ref lParam);
        }
    }

I tried a few things in my tests. First I did a Console beep (a audio que for myself.) then did a Thread sleep for 3 seconds... nothing shows up. so than i stole some code of mine to test a virtual keyboard class of mine. its nothing more than a Winforms form that is started in another thread. so i put in the on load event creating said hook rule, and on closing i disposed of it. Ran my test typed hello world and nothing in the console :/ so as a last resort I created a new winforms project. copied my code for the on load and on closing events, changed project to console project, then ran my code. Started typing and sure enough I started getting output to my console window. So this is driving me crazy. I have no clue why it is doing that. The code works, but just not in Nunit. Can someone guide me in the right direction as to why it isn't working. It acts like the hook is receiving any events, yet the SetWindowsHookEx returns a valid pointer everytime. So I dunno. Please help.

Upvotes: 1

Views: 308

Answers (1)

Hans Passant
Hans Passant

Reputation: 941635

You are making a common mistake, one that's often made with the low-level hooks. Windows cannot just arbitrarily make the callback to your ProcessKey() method. Your thread must be in a well-defined state where Windows is convinced that it is idle and making the call can be safe and doesn't cause re-entrancy problems.

This condition is best known as "pumping the message loop". A message loop is started by Application.Run() in a .NET program. And the thread that starts it cannot be otherwise occupied, it must have re-entered the loop, waiting for Windows to deliver the next message. Only then can the callback be made.

You do not get a message loop from NUnit, it occupies the thread by executing your test. You don't get it from "a Winforms form that is started in another thread", that's the wrong thread. You got it from your plain Winforms project, its UI thread always pumps a message loop and that thread spends 99.9% of its time waiting for a message.

So what you saw is entirely by design. You'll find sample code for a thread that pumps in this answer. Create your HookRuleTest object in an override for the Initialize() method.

Upvotes: 3

Related Questions