首页 > 解决方案 > 每次击键都会调用 LowLevelKeyboardProc 两次

问题描述

我尝试按照此处所述执行 VSTO 密钥挂钩:Excel VSTO 密钥挂钩

我使用 Alex Butenko 的答案创建了这个类,它(应该)在每次按下键时调用 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

按一下空格会产生以下调试输出:

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

微软文档https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx说:“wParam 是 WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN 或 WM_SYSKEYUP”,但在我的情况在两个电话中都是一样的。

那么,我做错了什么?我错过了什么吗?

标签: c#keyboardvsto

解决方案


好的,似乎我发现了我的问题:

我混淆了 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 告诉我按键重复的频率。当我检查是否重复 = 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;
    }
}

推荐阅读