如何将F#委托传递给期望函数指针的P / Invoke方法?

时间:2011-04-15 22:24:16

标签: winapi f# delegates pinvoke

我正在尝试在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)

2 个答案:

答案 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函数,这些函数的文档说明在失败时调用GetLastErrorGetModuleHandle就是这种情况,{ {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#(希望如此)。