Home > Mobile >  ExecutionEngineException on LowLevel Keyboard Hook
ExecutionEngineException on LowLevel Keyboard Hook

Time:09-23

I'm currently trying to get Global LowLevel keyboard hooks working in .net 5 with wpf, but after some time I always get an ExecutionEngineException and my App crashes. According to the docs, this exception should never occur at all anymore, but for me it does. It is also not specific to my project, as a minimal WPF .net 5 project will also crash after some key mashing.

Has anyone any idea how to either fix it or get around this problem?

Example Code for the Main 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();
    }
}

The Callstack I get:

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()

CodePudding user response:

As pointed out by the comments, you should not pass a lambda or delegate directly into a Win32 Api call (or any C api call for that matter). The fix is either to make a static variable containing a reference to the function

private static LowLevelKeyboardProc _handler = Handler;
[...]
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _handler, modPtr, 0);

or to pin the delegate, so the CLR won't move it around, which is what I opted for.

private LowLevelKeyboardProc _handler;
private GCHandle _gcHandler;

public KeyboardHook() {
    _handler = Handler;
    _gcHandler = GCHandle.Alloc(_handler);
}

[...]

_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _handler, modPtr, 0);

For the low level hooks, you seem to be able to set modPtr to either LoadLibrary("user32.dll"), GetModuleHandle(null) or IntPtr.Zero. Seems to work with all of them.

  • Related