簡體   English   中英

如何將F#委托傳遞給期望函數指針的P / Invoke方法?

[英]How can I pass an F# delegate to a P/Invoke method expecting a function pointer?

我正在嘗試在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)

您的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函數,這些函數的文檔說失敗時調用GetLastErrorGetModuleHandleSetWindowsHookExUnhookWindowsHookEx就是這種情況)。 這樣如果有任何失敗(你應該檢查返回值......),你可以簡單地引發Win32Exception或調用Marshal.GetLastWin32Error來獲得適當的診斷。

編輯 :為了清楚起見,這里是我在本地成功測試的所有P / Invoke簽名:

[<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值語義,這也同樣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

您是否嘗試過使用托管C ++? 它可以使很多翻譯非常無縫。 那么你不需要P / Invoke。

編輯:我想注意一個相當重要的事情:編譯器會為你做更多的類型檢查。 我相信你喜歡你的類型檢查,因為你在應用程序的其余部分使用F#(希望如此)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM