4

so, I've been developing a class to handle Kwyboard input in a VSTO add-in, so far I've been using Windows hooks to do so with relative success.

Having this code:

    //.....
    private const int WH_KEYBOARD = 2;
    private const int WH_MOUSE = 7;

    private enum WM : uint {
        KEYDOWN = 0x0100,
        KEYFIRST = 0x0100,
        KEYLAST = 0x0108,
        KEYUP = 0x0101,
        MOUSELEFTDBLCLICK = 0x0203,
        MOUSELEFTBTNDOWN = 0x0201,
        MOUSELEFTBTNUP = 0x0202,
        MOUSEMIDDBLCLICK = 0x0209,
        MOUSEMIDBTNDOWN = 0x0207,
        MOUSEMIDBTNUP = 0x0208,
        MOUSERIGHTDBLCLK = 0x0206,
        MOUSERIGHTBTNDOWN = 0x0204,
        MOUSERIGHTBTNUP = 0x0205
    }

    private hookProcedure proc;

    private static IntPtr hookID = IntPtr.Zero;

    //Enganches

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr SetWindowsHookEx(int hookId, hookProcedure proc, IntPtr hInstance, uint thread);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    private static extern bool unHookWindowsHookEx(int hookId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr CallNextHookEx(IntPtr hookId, int ncode, IntPtr wparam, IntPtr lparam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string name);

    [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetCurrentThreadId();

    public CPInputListener() {

        proc = keyBoardCallback;

        hookID = setHook(proc);
    }


    private IntPtr setHook(hookProcedure procedure){

        ProcessModule module = Process.GetCurrentProcess().MainModule;
        uint threadId = (uint)GetCurrentThreadId();

        return SetWindowsHookEx(WH_KEYBOARD, procedure, IntPtr.Zero, threadId);
    }

    public void stopListeningAll() {
        unHookWindowsHookEx(WH_KEYBOARD);//For now
    }


    private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) {

        if (ncode >= 0) {
            //LPARAM pretty useless

            Keys key = (Keys)wParam;

            KeyEventArgs args = new KeyEventArgs(key);

            onKeyDown(args);//for now

        }
        return CallNextHookEx(hookID, ncode, wParam, lParam);
    }
    //....

I do successfully receive keyboard input, but here is the big mistery; each time a key is pressed, no matter how fast it was, the event (onKeyDown) is called 10 times exactly, no more no less.

If the key is long pressed, the event keep being called but 10 by 10 times instead of calling just once.

So far I've tried

  1. Using wParam to call the required event on Key Up: Doesn't seem to work, in all codes I've seen dealing with Key down and up events, IntPtr wParam is used, but from that variable I can only retrieve the keycode which doesn't help.
  2. Using lParam or nCode: These vars are giving unconsistent values between those 10 calls, ncode tends to retrieve 0's and 3's and lParam some values which seem to be unmanaged memory adresses...

What do I expect

I do expect for onKeyDown to be called just once when the key is pressed or in the other hand being able to call the method by on key up which i do expect to be called just once per key releasing.

How to bypass this

If I can't find a reasonable answer, I was thinking on using a custom made timer to discard all those callings and use only the last one, would you recommend this if everything else fails?

Thanks a lot! Be happy and be kind! :D

1 Answers1

7

First you have to filter for the correct ncode to get only the keystrokes you are supposed to process. (For example, you are not supposed to process HC_NOREMOVE.)
Then you have to check if it was a KeyDown or KeyUp event using a flag in lParam.

If the key was long-pressed, multiple KeyDown events are already combined to one call by Win32, so you don't have to do anything special here. But if you want to get only the last KeyUp event then you have to check another flag in lParam as well.

So, here's the code you need to change:

private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam)
{
    // Feel free to move the const to a private field.
    const int HC_ACTION = 0;
    if (ncode == HC_ACTION)
    {
        Keys key = (Keys)wParam;
        KeyEventArgs args = new KeyEventArgs(key);

        bool isKeyDown = ((ulong)lParam & 0x40000000) == 0;
        if (isKeyDown)
            onKeyDown(args);
        else
        {
            bool isLastKeyUp = ((ulong)lParam & 0x80000000) == 0x80000000;
            if (isLastKeyUp)
                onKeyUp(args);
        }
    }
    return CallNextHookEx(hookID, ncode, wParam, lParam);
}

Edit as requested in the comment:
Unfortunately the documentation of these parameters is pretty sparse.

One "hint" not to process anything other then HC_ACTION can be found here, stating:

if (nCode < 0)  // do not process message
    return ...;

// ...
switch (nCode) 
{
    case HC_ACTION:
        // ... do something ...
        break;

    default:
        break;
}
// ...
return CallNextHookEx(...);

Another supporting statement is made here:
Why does my keyboard hook receive the same key-up and key-down events multiple times?

The content of the lParam is defined as follows:

typedef struct tagKBDLLHOOKSTRUCT {
    DWORD     vkCode;
    DWORD     scanCode;
    DWORD     flags;
    DWORD     time;
    ULONG_PTR dwExtraInfo;
}

(Just as a reminder: DWORD here is 4 bytes in size on x86 as well as on x64 platforms.)

The documentation of the lParam flags can be found here and here.
In this links it's described that

  • bit 30 (=0x40000000) is the previous key state
    (1 if the key was down and 0 if the key was up before the new key state that caused this call)
  • bit 31 (=0x80000000) is the transition state
    (0 on key press and 1 on key release now)

The term "previous key state" is rather confusing but effectively it's just the opposite of the current state (because there's only up or down and no third state).

The transition state is especially relevant when the "keyboard's automatic repeat feature" is activated, i.e. when the key is pressed down long enough.

Another sample (using VC7) can be found here:

if (HIWORD (lParam) & 0xC000)
    // Key up without autorepeat
else
    // Key down

Where 0xC000 just is 0x4000 || 0x8000 and defines that the key was released and has created a key up event.

All in all pretty confusing but nonetheless true.
Maybe there are other links out there that can describe this situation better, but I guess in times like these where new app development "should be done" in tiny sandboxes (like UWP) and VSTO is on its sure way to die to make way for newer Office add-ins that are written in HTML and JavaScript, nobody cares all that much about low-level hooks anymore.

Community
  • 1
  • 1
haindl
  • 3,111
  • 2
  • 25
  • 31
  • Indeed! it does work! Thanks a lot for your help! Now, for the sake of learning do you mind explaining me (or addressing any documentation) about how you did figure out those flags? Thanks a lot again! –  Nov 16 '16 at 19:29
  • @LuisMoyano Glad I could help you! For the sake of learning, I carried together all relevant documentation and missing explanations. I hope this helps. Now, just enjoy the gory details. ;-) – haindl Nov 17 '16 at 12:17