c# WPF:使用Windows键盘钩子时出现System.ExecutionEngineException

ogsagwnx  于 2023-04-22  发布在  Windows
关注(0)|答案(1)|浏览(248)

我尝试在windows钩子上创建热键。它是有效的,但有时wpf窗口,我使用这个解决方案的地方,抛出System.ExecutionEngineException(当我快速按下很多按钮时经常发生。我发现这是因为消息循环被我的钩子破坏了。有没有一些方法可以避免它?
我知道RegisterHotKey功能,但我需要的能力,以中止按键压力,如果我的热键激活(以防止执行类似的热键在其他程序)。
下面是我的hook设置:

[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";
            }));
        }

感谢您的长期阅读!

h9vpoimq

h9vpoimq1#

看起来您没有保持回调委托活动。您需要将其存储在字段中以防止其被销毁。
当你传递一个委托给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的指针。虽然您当前的代码 * 技术上 * 只读取第一个值,但您实际上应该封送整个结构。

[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
);

相关问题