[英]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函數,這些函數的文檔說失敗時調用GetLastError
( GetModuleHandle
, SetWindowsHookEx
和UnhookWindowsHookEx
就是這種情況)。 這樣如果有任何失敗(你應該檢查返回值......),你可以簡單地引發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.