【问题标题】:How can I pass an F# delegate to a P/Invoke method expecting a function pointer?如何将 F# 委托传递给需要函数指针的 P/Invoke 方法?
【发布时间】:2011-04-15 22:24:15
【问题描述】:

我正在尝试在 F# 应用程序中使用 P/Invoke 设置低级键盘挂钩。 Win32 函数SetWindowsHookExHOOKPROC 作为其第二个参数,我将其表示为(int * IntPtr * IntPtr) -> IntPtr 的委托,类似于在C# 中的处理方式。调用该方法时,我收到一个MarshalDirectiveException 说明无法封送委托参数,因为

泛型类型不能被封送

我不确定泛型是如何涉及的,因为所有类型都是具体指定的。任何人都可以对此有所了解吗?代码如下。

编辑

这可能与 F# 编译器处理类型签名的方式有关 - 反射器表明委托 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)

【问题讨论】:

  • +1 用于尝试从 F# 调用/调用并传递委托。老实说,我不知道这是否可以做到。
  • 我认为结果类型应该只是int,而不是IntPtr。但我想这不会对错误消息产生影响......;建议:添加调用SetHook的代码,可能那里有问题?
  • @wmeyer :int 而不是 nativeint 的返回类型在 64 位平台上会失败。
  • @Frédéric Hamidi :这是将回调传递给所有 .NET 语言的本机代码的标准;为此使用 F# 没什么特别的。
  • @Frédéric Hamidi:我承认,一开始并不明显。 delegate of (int * int) -&gt; int 定义一个采用实际 Tuple&lt;int, int&gt; 的一元委托,而 delegate of int * int -&gt; int 定义采用两个 ints 的二元函数。但是,后者必须与柯里化函数一起使用,即使它似乎使用元组语法。无论如何,前一个版本的不明显语义是导致 Ben 错误的原因,因为 Tuple&lt;int, nativeint, nativeint&gt; 显然是一个泛型。

标签: winapi f# delegates pinvoke


【解决方案1】:

您的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 都需要使用柯里化形式而不是元组形式。

还请注意,您应该将SetLastError = true 添加到所有externed 函数,其文档说明在失败时调用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 的值语义,这也同样适用:

[<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

【讨论】:

  • 您通常如何测试您的 P/Invokes?
  • @GregC :取决于复杂程度。在这种情况下,我编写了一个小型 C++ 控制台应用程序来确定预期行为,然后验证 F# 应用程序具有相同的行为。在更复杂的情况下,我通常会编写一个 C++ .dll 来导出与我最终希望调用的函数具有相同签名的函数,并且让这些函数只是盲目地将数据来回转发到底层 WinAPI 函数,并在这样做时进行日志记录。然后我可以查看日志输出以确保编组行为是正常的。
【解决方案2】:

您是否尝试过使用托管 C++。它可以使很多翻译变得非常无缝。那么你就不需要 P/Invoke 了。

编辑:我想指出一件相当重要的事情:编译器会为你做更多的类型检查。我相信您喜欢您的类型检查,因为您在应用程序的其余部分使用 F#(希望如此)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-04
    • 2013-10-19
    • 1970-01-01
    • 1970-01-01
    • 2012-07-15
    • 1970-01-01
    相关资源
    最近更新 更多