I\'m attempting to set up a low-level keyboard hook using P/Invoke in an F# application. The Win32 function SetWindowsHookEx
takes a HOOKPROC
for
Have you tried to use managed C++ with this. It can make a lot of the translation pretty seamless. You won't need P/Invoke then.
EDIT: I'd like to note one fairly important thing: compiler will do more type-checking for you. I am sure you love your type-checking, since you use F# for the rest of the app (hopefully).
Your LowLevelKeyboardProc
definition is wrong. Change from
type LowLevelKeyboardProc = delegate of (int * IntPtr * IntPtr) -> IntPtr
to
type LowLevelKeyboardProc = delegate of int * IntPtr * IntPtr -> IntPtr
or better yet
type LowLevelKeyboardProc = delegate of int * nativeint * nativeint -> nativeint
or better yet
[<StructLayout(LayoutKind.Sequential)>]
type KBDLLHOOKSTRUCT =
val vkCode : uint32
val scanCode : uint32
val flags : uint32
val time : uint32
val dwExtraInfo : nativeint
type LowLevelKeyboardProc =
delegate of int * nativeint * KBDLLHOOKSTRUCT -> nativeint
In all of the above cases, proc
will need to use curried form rather than tupled form.
Also note that you should add SetLastError = true
to all of the extern
ed functions whose documentation says to call GetLastError
upon failure (which is the case for GetModuleHandle
, SetWindowsHookEx
, and UnhookWindowsHookEx
). That way if any fail (and you should be checking the return values...), you can simply raise a Win32Exception
or call Marshal.GetLastWin32Error to get proper diagnostics.
EDIT: Just for the sake of clarity, here are all the P/Invoke signatures I successfully tested locally:
[<Literal>]
let WH_KEYBOARD_LL = 13
[<StructLayout(LayoutKind.Sequential)>]
type KBDLLHOOKSTRUCT =
val vkCode : uint32
val scanCode : uint32
val flags : uint32
val time : uint32
val dwExtraInfo : nativeint
type LowLevelKeyboardProc = delegate of int * nativeint * KBDLLHOOKSTRUCT -> nativeint
[<DllImport("kernel32.dll")>]
extern uint32 GetCurrentThreadId()
[<DllImport("kernel32.dll", SetLastError = true)>]
extern nativeint GetModuleHandle(string lpModuleName)
[<DllImport("user32.dll", SetLastError = true)>]
extern bool UnhookWindowsHookEx(nativeint hhk)
[<DllImport("user32.dll", SetLastError = true)>]
extern nativeint SetWindowsHookEx(int idhook, LowLevelKeyboardProc proc, nativeint hMod, uint32 threadId)
Also note that this would work equally as well, if you prefer value semantics for KBDLLHOOKSTRUCT
:
[<Struct; StructLayout(LayoutKind.Sequential)>]
type KBDLLHOOKSTRUCT =
val vkCode : uint32
val scanCode : uint32
val flags : uint32
val time : uint32
val dwExtraInfo : nativeint
type LowLevelKeyboardProc = delegate of int * nativeint * byref<KBDLLHOOKSTRUCT> -> nativeint