C
C#5mo ago
Sebastian

Windows keyboard hook (SetWindowsHookExA) doesn't get called.

Simplified version:
KeyboardListener.KeyPress += (key) =>
{
Console.WriteLine(Enum.GetName(key));
};
KeyboardListener.Listen();

await Task.Delay(-1);

public static unsafe partial class KeyboardListener
{
private const string User32 = "user32";
private const string Kernel32 = "kernel32";
private const int KeyboardId = 13;
private const int KeyDown = 0x0100;

[LibraryImport(User32, SetLastError = true)]
private static partial nuint SetWindowsHookExA(int idHook, delegate* unmanaged<int, nuint, nint, nint> lpfn, nuint hmod, uint dwThreadId);

[LibraryImport(User32, SetLastError = true)]
private static partial nint CallNextHookEx(nuint hhk, int nCode, nuint wParam, nint lParam);

[LibraryImport(User32, SetLastError = true)]
private static partial int UnhookWindowsHookEx(nuint hhk);

private static nuint _handle = 0;

public static event Action<ConsoleKey>? KeyPress;

public static void Listen()
{
nuint hmod = (nuint)Marshal.GetHINSTANCE(typeof(KeyboardListener).Module);
_handle = SetWindowsHookExA(KeyboardId, &Hook, hmod, 0);
}

public static void Stop()
{
UnhookWindowsHookEx(_handle);
}

[UnmanagedCallersOnly]
private static nint Hook(int nCode, nuint wParam, nint lParam)
{
if (nCode >= 0 && wParam == KeyDown)
{
ConsoleKey key = (ConsoleKey)lParam;
KeyPress?.Invoke(key);
}

return CallNextHookEx(0, nCode, wParam, lParam);
}
}
KeyboardListener.KeyPress += (key) =>
{
Console.WriteLine(Enum.GetName(key));
};
KeyboardListener.Listen();

await Task.Delay(-1);

public static unsafe partial class KeyboardListener
{
private const string User32 = "user32";
private const string Kernel32 = "kernel32";
private const int KeyboardId = 13;
private const int KeyDown = 0x0100;

[LibraryImport(User32, SetLastError = true)]
private static partial nuint SetWindowsHookExA(int idHook, delegate* unmanaged<int, nuint, nint, nint> lpfn, nuint hmod, uint dwThreadId);

[LibraryImport(User32, SetLastError = true)]
private static partial nint CallNextHookEx(nuint hhk, int nCode, nuint wParam, nint lParam);

[LibraryImport(User32, SetLastError = true)]
private static partial int UnhookWindowsHookEx(nuint hhk);

private static nuint _handle = 0;

public static event Action<ConsoleKey>? KeyPress;

public static void Listen()
{
nuint hmod = (nuint)Marshal.GetHINSTANCE(typeof(KeyboardListener).Module);
_handle = SetWindowsHookExA(KeyboardId, &Hook, hmod, 0);
}

public static void Stop()
{
UnhookWindowsHookEx(_handle);
}

[UnmanagedCallersOnly]
private static nint Hook(int nCode, nuint wParam, nint lParam)
{
if (nCode >= 0 && wParam == KeyDown)
{
ConsoleKey key = (ConsoleKey)lParam;
KeyPress?.Invoke(key);
}

return CallNextHookEx(0, nCode, wParam, lParam);
}
}
It's doing "something" - when the application is running, key presses have some delay. What it's not doing however, is actually calling the Hook method. Any pointers?
3 Replies
jcotton42
jcotton425mo ago
what are you using hooks for? or rather, why hooks? @Sebastian
arion
arion5mo ago
Your application needs a message loop to get messages. Also check if the _handle is 0, that means it failed You can get more info from the GetLastError Like this https://github.com/JustArion/Win11_Tooltip_Fix/blob/master/src%2FTooltip_Fix%2FProgram.cs#L43-L50 Though that project used wineventhooks I'm pretty sure the same would apply for winhooks The unmanaged function might be a blocking call, other hooks might need to wait for your code to finish executing to continue so your delegate might need to be transferred to another thread. That would also solve the issue of exceptions causing CallNextHookEx not to be called Lastly the lParam is a pointer to the tagKBDLLHOOKSTRUCT struct, its not a ConsoleKey type (More info found here https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc) I personally prefer H.Hooks for their H.Hooks.LowLevelKeyboardHook wrapper
Sebastian
Sebastian5mo ago
Sorry for being inactive, life happens Thank you! (handle is fine, I just need the message loop)