我正在尝试在F#应用程序中使用P / Invoke设置一个低级键盘钩子。 Win32函数SetWindowsHookEx
为其第二个参数获取HOOKPROC
,我将其表示为(int * IntPtr * IntPtr) -> IntPtr
的委托,类似于在C#中处理它的方式。调用方法时,我得到MarshalDirectiveException
表示委托参数无法封送,因为
通用类型无法编组
我不确定如何涉及泛型,因为具体指定了所有类型。任何人都可以对此有所了解吗?代码如下。
修改
这可能与F#编译器处理类型签名的方式有关 - Reflector指示委托LowLevelKeyboardProc
被实现为接受类型为Tuple<int, IntPtr, IntPtr>
的一个参数的方法 - 并且会有不可编组的泛型类型。有没有办法解决这个问题,或者F#函数是不是能够被编组到本机函数指针?
let WH_KEYBOARD_LL = 13
type LowLevelKeyboardProc = delegate of (int * IntPtr * IntPtr) -> IntPtr
[<DllImport("user32.dll")>]
extern IntPtr SetWindowsHookEx(int idhook, LowLevelKeyboardProc proc, IntPtr hMod, UInt32 threadId)
[<DllImport("kernel32.dll")>]
extern IntPtr GetModuleHandle(string lpModuleName)
let SetHook (proc: LowLevelKeyboardProc) =
use curProc = Process.GetCurrentProcess ()
use curMod = curProc.MainModule
SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curMod.ModuleName), 0u)
答案 0 :(得分:13)
您的LowLevelKeyboardProc
定义错误。改变
type LowLevelKeyboardProc = delegate of (int * IntPtr * IntPtr) -> IntPtr
到
type LowLevelKeyboardProc = delegate of int * IntPtr * IntPtr -> IntPtr
或更好
type LowLevelKeyboardProc = delegate of int * nativeint * nativeint -> nativeint
或更好
[<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
在上述所有情况中,proc
都需要使用curry形式而不是tupled形式。
另请注意,您应该将SetLastError = true
添加到所有extern
ed函数,这些函数的文档说明在失败时调用GetLastError
(GetModuleHandle
就是这种情况,{ {1}}和SetWindowsHookEx
)。这样,如果有任何失败(并且您应该检查返回值......),您只需提出UnhookWindowsHookEx
或致电Marshal.GetLastWin32Error
即可获得正确的诊断。
编辑:为了清楚起见,以下是我在本地成功测试的所有P / Invoke签名:
Win32Exception
另请注意,如果您更喜欢[<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)
的值语义,这也会同样有效:
KBDLLHOOKSTRUCT
答案 1 :(得分:1)
您是否尝试过使用托管C ++?它可以使很多翻译非常无缝。那时你不需要P / Invoke。
编辑:我想注意一件非常重要的事情:编译器会为你做更多的类型检查。我相信你喜欢你的类型检查,因为你在应用程序的其余部分使用F#(希望如此)。