c# WPF: System.ExecutionEngineException while using Windows keyboard hooks

问题描述 投票:0回答:1

我正在尝试在 Windows 挂钩上制作热键。它有效,但有时我使用此解决方案的 wpf 窗口抛出 System.ExecutionEngineException(当我快速按下很多按钮时经常发生这种情况。我发现这是因为消息循环被我的钩子破坏了。是否有一些避免它的方法?

附注我知道 RegisterHotKey 功能,但如果我的热键被激活,我需要中止按键压力的功能(以防止在其他程序中执行类似的热键)。

这是我的挂钩设置:

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
            LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

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

 private static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                    GetModuleHandle(curModule.ModuleName), 0);
            }
        }

这是我的钩子:

private static IntPtr HookCallback(
            int nCode, IntPtr wParam, IntPtr lParam)
        {
            bool isKeyProcessed = false;
            try
            {
                if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN))
                {
                    int vkCode = Marshal.ReadInt32(lParam);
                    isKeyProcessed = KeyDown?.Invoke((Keys)vkCode) ?? false;
                }
                else if (nCode >= 0 && (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP))
                {
                    int vkCode = Marshal.ReadInt32(lParam);
                
                    isKeyProcessed = KeyUp?.Invoke((Keys)vkCode) ?? false;
                }
            }
            catch (Exception ex)
            {
                isKeyProcessed = false;
                try
                {
                    ExceptionHappend?.Invoke(ex);
                }
                catch { }
            }

            if (!isKeyProcessed)
                return CallNextHookEx(_hookID, nCode, wParam, lParam);
            else
                return (IntPtr)1;
        }

有我的钩子事件处理程序(对于按键按下,在按键上我只是从当前键组合中删除向上按钮)

public bool HandleKeyDown(Keys key)
        {
            lock (_lock)
            {
                currentCombo.Keys.Add(key);
                if (hotkeys.ContainsKey(currentCombo))
                {
                    RunHandler(hotkeys[currentCombo]);
                    currentCombo.Keys.Clear();
                    return true;
                }
                return false;
            }
        }


private void RunHandler(Action handler)
        {
            _runningActions = _runningActions.Where(x=>x.Status == TaskStatus.Running).ToList();
            var task = new Task(handler);
            task.Start();
            _runningActions.Add(task);
        }

最后,我的热键,例子:

 public void ShowWorkedQ()
        {
            Dispatcher.Invoke(new Action(() =>
            {
                OutLabel.Text = "Pressed Alt+Q";
            }));
        }

感谢您的长时间阅读!

c# wpf windows keyboard-hook
1个回答
1
投票

看起来你没有让回调委托保持活动状态。您需要将它存储在一个字段中,以防止它被破坏。

当您将委托传递给 PInvoke 函数时,PInvoke 将创建一个小的本机函数,使回调能够跳回托管代码。但是 GC 无法正确跟踪此对象,除非您仍然持有对原始委托的引用。

static LowLevelKeyboardProc _proc;

private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
    using (Process curProcess = Process.GetCurrentProcess())
    using (ProcessModule curModule = curProcess.MainModule)
    {
        if (_proc != null) throw new Exception("Cannot set hook more than once");
        _proc = proc;  // NEW!
        return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
            GetModuleHandle(curModule.ModuleName), 0);
    }
}

还请注意,低级 KB 钩子中的

lParam
实际上表示指向
KBDLLHOOKSTRUCT
的指针。虽然您当前的代码 techincally 只能读取第一个值,但您应该真正编组整个结构。

[StructLayout(LayoutKind.Sequential)]
struct KBDLLHOOKSTRUCT
{
  int vkCode;
  int scanCode;
  int flags;
  int time;
  IntPtr dwExtraInfo;
}


delegate IntPtr LowLevelKeyboardProc (
  int    nCode,
  IntPtr wParam,
  [In]
  in KBDLLHOOKSTRUCT lParam
);
© www.soinside.com 2019 - 2024. All rights reserved.