低级键盘挂钩上的 ExecutionEngineException
ExecutionEngineException on LowLevel Keyboard Hook
我目前正在尝试使用 wpf 使 Global LowLevel 键盘挂钩在 .net 5 中工作,但一段时间后我总是得到 ExecutionEngineException
并且我的应用程序崩溃了。 According to the docs,这个异常根本不应该再发生,但对我来说它确实发生了。它也不是特定于我的项目,因为最小的 WPF .net 5 项目也会在一些关键混搭后崩溃。
有没有人知道如何解决或绕过这个问题?
主要示例代码 Window:
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);
[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);
private IntPtr _hook;
public bool Hook() {
if (_hook == IntPtr.Zero) {
var modPtr = LoadLibrary("user32.dll");
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, Handler, 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();
}
}
我得到的调用栈:
WindowsBase.dll!System.Windows.Threading.Dispatcher.GetMessage(ref System.Windows.Interop.MSG msg, System.IntPtr hwnd, int minMessage, int maxMessage)
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame)
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame)
WindowsBase.dll!System.Windows.Threading.Dispatcher.Run()
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore)
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window)
PresentationFramework.dll!System.Windows.Application.Run()
EngineExecutionError.dll!EngineExecutionError.App.Main()
正如评论所指出的,您不应将 lambda 或委托直接传递给 Win32 Api 调用(或任何 C api 调用)。解决方法是创建一个包含对函数的引用的静态变量
private static LowLevelKeyboardProc _handler = Handler;
[...]
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _handler, modPtr, 0);
或者固定委托,这样 CLR 就不会移动它,这是我选择的。
private LowLevelKeyboardProc _handler;
private GCHandle _gcHandler;
public KeyboardHook() {
_handler = Handler;
_gcHandler = GCHandle.Alloc(_handler);
}
[...]
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _handler, modPtr, 0);
对于低级挂钩,您似乎可以将 modPtr
设置为 LoadLibrary("user32.dll")
、GetModuleHandle(null)
或 IntPtr.Zero
。似乎与所有这些一起工作。
我目前正在尝试使用 wpf 使 Global LowLevel 键盘挂钩在 .net 5 中工作,但一段时间后我总是得到 ExecutionEngineException
并且我的应用程序崩溃了。 According to the docs,这个异常根本不应该再发生,但对我来说它确实发生了。它也不是特定于我的项目,因为最小的 WPF .net 5 项目也会在一些关键混搭后崩溃。
有没有人知道如何解决或绕过这个问题?
主要示例代码 Window:
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);
[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);
private IntPtr _hook;
public bool Hook() {
if (_hook == IntPtr.Zero) {
var modPtr = LoadLibrary("user32.dll");
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, Handler, 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();
}
}
我得到的调用栈:
WindowsBase.dll!System.Windows.Threading.Dispatcher.GetMessage(ref System.Windows.Interop.MSG msg, System.IntPtr hwnd, int minMessage, int maxMessage)
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame)
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame)
WindowsBase.dll!System.Windows.Threading.Dispatcher.Run()
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore)
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window)
PresentationFramework.dll!System.Windows.Application.Run()
EngineExecutionError.dll!EngineExecutionError.App.Main()
正如评论所指出的,您不应将 lambda 或委托直接传递给 Win32 Api 调用(或任何 C api 调用)。解决方法是创建一个包含对函数的引用的静态变量
private static LowLevelKeyboardProc _handler = Handler;
[...]
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _handler, modPtr, 0);
或者固定委托,这样 CLR 就不会移动它,这是我选择的。
private LowLevelKeyboardProc _handler;
private GCHandle _gcHandler;
public KeyboardHook() {
_handler = Handler;
_gcHandler = GCHandle.Alloc(_handler);
}
[...]
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _handler, modPtr, 0);
对于低级挂钩,您似乎可以将 modPtr
设置为 LoadLibrary("user32.dll")
、GetModuleHandle(null)
或 IntPtr.Zero
。似乎与所有这些一起工作。