每次击键都会调用 LowLevelKeyboardProc 两次

LowLevelKeyboardProc is called twice with every keystroke

我尝试按照此处所述进行 VSTO 键挂钩:

我使用 Alex Butenko 的答案创建了这个 class 每次按下一个键时(应该)调用 OnKeyPress。问题是,当我按下一个键时,OnKeyPress 被调用了两次:

static class ShortcutManager
{
    delegate int LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
    static readonly LowLevelKeyboardProc _proc = HookCallback;
    static IntPtr _hookID = IntPtr.Zero;
    const int WH_KEYBOARD = 2;
    const int HC_ACTION = 0;

    const int WM_KEYDOWN = 0x0100;


    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool UnhookWindowsHookEx(IntPtr idHook);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    static extern short GetKeyState(int nVirtKey);

    static bool _keyHookingStarted;
    public static void Start(IShortcutDistributor dist)
    {
        m_dist = dist;
        if (!_keyHookingStarted)
        {
#pragma warning disable 0618
            _hookID = SetWindowsHookEx(WH_KEYBOARD, _proc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId());
#pragma warning restore 0618
            _keyHookingStarted = true;
        }
    }
    public static void Stop()
    {
        m_dist = null;
        if (_keyHookingStarted)
        {
            UnhookWindowsHookEx(_hookID);
            _hookID = IntPtr.Zero;
            _keyHookingStarted = false;
        }
    }

    static IShortcutDistributor m_dist = null;

    static void OnKeyPress(uint keys)
    {
        var crtl = IsKeyDown(Keys.LControlKey) || IsKeyDown(Keys.RControlKey);
        Debug.WriteLine("Keys: "+ keys.ToString()+ " Crtl: "+ crtl.ToString());
        m_dist?.RaiseKeyPressedEvent(new KeyPressedEventArgs((Keys)keys, crtl));
    }
    static bool IsKeyDown(Keys keys)
    {
        return (GetKeyState((int)keys) & 0x8000) == 0x8000;
    }

    static int HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {

        if (nCode < 0)
        {
            return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
        if (nCode == HC_ACTION)
        {
            Debug.WriteLine("nCode: " + nCode.ToString() + " wParam:" + wParam.ToString());
            OnKeyPress((uint)wParam);
        }
        return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
    }
}

所以当我按下 crtl+q 时,调试输出是:

nCode: 0 wParam:81
Keys: 81 Crtl: True
nCode: 0 wParam:81
Keys: 81 Crtl: True

按一下 space 会产生此调试输出:

nCode: 0 wParam:32
Keys: 32 Crtl: False
nCode: 0 wParam:32
Keys: 32 Crtl: False

Microsoft 文档 https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx 说:"wParam is either WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN or WM_SYSKEYUP" 但在我的情况下,两次调用都相同。

所以,我做错了什么?我错过了什么吗?

好的,看来我找到问题所在了:

我混淆了 LowLevelKeyboardProc 和 KeyboardProc。

当我使用“SetWindowsHookEx(WH_KEYBOARD,...”时,函数是 KeyboardProc 而不是 LowLevelKeyboardProc。所以这是正确的微软文档:

https://msdn.microsoft.com/en-us/library/ms644984(v=vs.85).aspx

https://docs.microsoft.com/de-de/windows/desktop/inputdev/about-keyboard-input#_win32_Keystroke_Message_Flags

所以 lParam 是“击键消息标志”。这意味着标志 KF_REPEAT = 30 告诉我按键重复的频率。当我检查 repeat = 0 时,我刚接到第一个电话。所以这是我的(希望是正确的)代码:

static class ShortcutManager
{
    delegate int KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
    static readonly KeyboardProc _proc = HookCallback;
    static IntPtr _hookID = IntPtr.Zero;
    const int WH_KEYBOARD = 2;
    const int WH_KEYBOARD_LL = 13;
    const int HC_ACTION = 0;

    const int WM_KEYDOWN = 0x0100;


    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr SetWindowsHookEx(int idHook, KeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool UnhookWindowsHookEx(IntPtr idHook);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    static extern short GetKeyState(int nVirtKey);

    static bool _keyHookingStarted;
    public static void Start(IShortcutDistributor dist)
    {
        m_dist = dist;
        if (!_keyHookingStarted)
        {
#pragma warning disable 0618
            _hookID = SetWindowsHookEx(WH_KEYBOARD, _proc, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId());
#pragma warning restore 0618
            _keyHookingStarted = true;
        }
    }
    public static void Stop()
    {
        m_dist = null;
        if (_keyHookingStarted)
        {
            UnhookWindowsHookEx(_hookID);
            _hookID = IntPtr.Zero;
            _keyHookingStarted = false;
        }
    }

    

    static IShortcutDistributor m_dist = null;

    const int KF_REPEAT = 0x4000;

    static void OnKeyPress(uint keys)
    {
        var crtl = IsKeyDown(Keys.LControlKey) || IsKeyDown(Keys.RControlKey);
        Debug.WriteLine("Keys: "+ keys.ToString()+ " Crtl: "+ crtl.ToString());
        m_dist?.RaiseKeyPressedEvent(new KeyPressedEventArgs((Keys)keys, crtl));
    }
    static bool IsKeyDown(Keys keys)
    {
        return (GetKeyState((int)keys) & 0x8000) == 0x8000;
    }

    static int HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {

        if (nCode < 0)
        {
            return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
        if (nCode == HC_ACTION)
        {
            var repeat = (HiWord(lParam) & KF_REPEAT);
            Debug.WriteLine("nCode: " + nCode.ToString() + " wParam:" + wParam.ToString() + " repeat: "+ repeat.ToString());
            if (repeat == 0)
            {
                OnKeyPress((uint)wParam);
            }
        }
        return (int)CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

    private static ulong HiWord(IntPtr ptr)
    {
        if (((ulong)ptr & 0x80000000) == 0x80000000)
            return ((ulong)ptr >> 16);
        else
            return ((ulong)ptr >> 16) & 0xffff;
    }
}