.NET 5 WPF全局低级键盘钩子触发ExecutionEngineException崩溃如何解决
问题根因
崩溃的核心原因是托管回调委托被GC意外回收。
你调用SetWindowsHookEx时直接传入Handler方法,编译器会隐式生成一个LowLevelKeyboardProc委托实例,但这个实例没有被你的代码持有任何强引用,GC执行回收时会直接清理该无引用的委托。后续系统触发钩子回调时,会访问已被释放的托管内存,最终触发ExecutionEngineException异常。
修复方案
- 将回调委托作为类的成员变量显式持有,避免GC回收
- 补充钩子卸载逻辑,避免资源泄漏
修复后的完整代码
public partial class MainWindow : Window { private const int WH_KEYBOARD_LL = 13; private const int WM_KEYDOWN = 0x0100; private const int WM_SYSKEYDOWN = 0x0104; private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); // 显式持有委托实例,防止GC回收 private LowLevelKeyboardProc _keyboardProc; [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)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)] static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName); // 补充卸载钩子的API [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); private IntPtr _hook; public bool Hook() { if (_hook == IntPtr.Zero) { // 先给成员委托赋值,再传入API _keyboardProc = Handler; var modPtr = LoadLibrary("user32.dll"); _hook = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardProc, modPtr, 0); } return _hook != IntPtr.Zero; } private IntPtr Handler(int code, IntPtr param, IntPtr lParam) { return CallNextHookEx(_hook, code, param, lParam); } public MainWindow() { InitializeComponent(); Hook(); // 窗口关闭时卸载钩子 Closing += (s, e) => { if (_hook != IntPtr.Zero) { UnhookWindowsHookEx(_hook); _hook = IntPtr.Zero; } }; } }
修改后可稳定复现的崩溃问题会完全消失,快速多次按键也不会再触发异常。
内容的提问来源于stack exchange,提问作者CShark
相关产品推荐
相关产品推荐

