使用低级键盘钩

时间:2016-04-15 23:41:20

标签: c# enums bit-manipulation keyboard-hook

我将非常极端地解释,因为我的另一篇文章似乎并不是很清楚。

我正在开发一个应用程序,你可以添加一个自定义热键和触发的uppon,它会做一些事情(无关紧要)。你可以说" RegisterHotKey"从头顶开始,但不幸的是,这根本不能帮助我。 User32 dll的RegisterHotKey不允许多个非修改键进入热键,例如:

RegisterHotKey(someWindowHandle, someHotkeyId, MOD_SHIFT, K_A); // Shift + A: ok!
RegisterHotKey(someWindowHandle, someHotkeyId, MOD_SHIFT | MOD_ALT, K_A); // Shift + Alt + A: 2 modifiers, ok!
RegisterHotKey(someWindowHandle, someHotkeyId, MOD_SHIFT, K_A | K_B); // Shift + A + B: not possible!

还有一些其他系统热键不能覆盖"但事实并非如此。

为了实现这一点,我创建了一个C ++ DLL来挂钩键盘并使用SendNotifyMessage()调用将数据发送到我的C#Windows窗体应用程序。数据以我的形式WndProc完美地发送和接收。

按键类如下:

using System.Collections.Generic;
using System.Windows.Input;

// Usage: Keystroke k = new Keystroke(Key.LShift, Key.A, Key.B)
public class Keystroke
{
    public List<Key> Keys { get; private set; };

    public Keystroke(params Key[] keys)
    {
        Keys = new List<Key>(keys);
    }
}

正如我们所看到的,这是一个非常简单的片段。基本上,热键/按键可以由任意组合键组成,即使只有修改键(例如Shift + Alt)。

键盘事件以下列方式在我的WndProc中调度:

public class MainForm : Form
{
    List<Keystroke> customHotkeys;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == LowLevelKeyboardHook.Message)
        {
            WinAPI.KBDLLHOOKSTRUCT keyboardHook = (WinAPI.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(m.LParam, typeof(WinAPI.KBDLLHOOKSTRUCT));

            switch (m.WParam.ToInt32())
            {
                case WinAPI.WM_KEYDOWN:
                case WinAPI.WM_SYSKEYDOWN:
                    DispatchKeyDownMessage(keyboardHook);
                    break;
                case WinAPI.WM_KEYUP:
                case WinAPI.WM_SYSKEYUP:
                    // ...
                    break;
            }
        }

        base.WndProc(ref m);
    }

    private void DispatchKeyDownMessage(WinAPI.KBDLLHOOKSTRUCT keyboardHook)
    {
        // Convert the key code into a System.Windows.Input.Key enum value
        Key key = KeyInterop.KeyFromVirtualKey(keyboardHook.vkCode);

        foreach (Keystroke keystroke in customHotkeys)
        {
            if (keystroke.Keys.Contains(key))
            {
                Console.WriteLine("Woot! Some hotkey wraps this key.");
            }
        }
    }
}

上述方法确实有效,但是,这似乎没有最好的表现。由于我们正在处理全局系统挂钩,因此必须非常快速地发送消息。为此,我考虑在击键和&#34; keystroke.Keys.Contains(键)&#34;中添加某种按位标志技术。不再需要,加快速度。有点 LIKE 这个:

public class Keystroke
{
    public List<Key> Keys { get; private set; } 
    public Key Flags { get; private set; }

    public Keystroke(params Key[] keys)
    {
        Keys = new List<Key>(keys);

        foreach (Key key in keys)
        {
            Flags |= key;
        }
    }
}

// Usage: bool containsK = (someKeystroke.Flags & Key.W) == Key.W;

正如我们所知,System.Windows.Input.Key枚举没有[Flags]属性。但是,我可以使用System.Windows.Forms.Keys枚举,但仍然如文档中所述,我们无法使用组合按位运算的值,因为它们不是互斥的:documentation。< / p>

为了解决标记问题,我创建了这个类:

public class Flagger<T> where T : struct
{
    private static Dictionary<int, ulong> dictionary = new Dictionary<int, ulong>();

    static Flagger()
    {
        int indexer = 0;

        // Since values can be duplicated, we use names instead
        foreach (String name in Enum.GetNames(typeof(T)))
        {
            ulong value = 1UL << indexer++; // 0, 1, 2, 4, 8, 16...

            Console.WriteLine("{0} key now corresponds to the value {1}", name, value);

            dictionary.Add(name.GetHashCode(), value); 
        }
    }

    private ulong flags;

    public void Add(T value)
    {
        // Create hash only once for both checkup and storation
        int hash = value.ToString().GetHashCode();

        if (Check(hash))
        {
            ulong flag = dictionary[hash];

            flags &= flag;
        }
    }

    public void Remove(T value)
    {
        // Create hash only once for both checkup and storation
        int hash = value.ToString().GetHashCode();

        if (Check(hash))
        {
            ulong flag = dictionary[hash];

            flags &= ~flag;
        }            
    }

    /// <summary>
    /// Tests whether a value has already been added or not
    /// </summary>
    public bool Check(T value)
    {
        int hash = value.ToString().GetHashCode();

        return Check(hash);
    }

    /// <summary>
    /// Quick checkup because no hash needs to be computed
    /// </summary>
    private bool Check(int hash)
    {
        if (dictionary.ContainsKey(hash))
        {
            ulong flag = dictionary[hash];

            return (flags & flag) == flag;
        }

        return false;
    }
}

最后,我们现在停留在&#34; ulong值= 1UL&lt;&lt;索引++&#34;部分。由于Key枚举包含200个以上的值,因此我们会得到&#34; ulong value = 1UL&lt;&lt; 200&#34;对于第200次迭代,这会产生超出ulong限制的超巨长数,从而导致重复值。运行下面的代码,你会注意到它:

for (int i = 0; i < 200; i++)
{
    Console.WriteLine("{0} generated {1}", i, 1UL << i);
}

我怎样才能解决上述问题?谢谢你的时间。

0 个答案:

没有答案