Как передать делегат F # методу P / Invoke, ожидающему указатель на функцию? - PullRequest
9 голосов
/ 16 апреля 2011

Я пытаюсь настроить низкоуровневую перехват клавиатуры с помощью P / Invoke в приложении F #. Функция Win32 SetWindowsHookEx принимает HOOKPROC для своего второго аргумента, который я представлял как делегат (int * IntPtr * IntPtr) -> IntPtr, аналогично тому, как это будет обрабатываться в C #. При вызове метода я получаю MarshalDirectiveException о том, что параметр делегата нельзя маршалировать, потому что

Универсальные типы нельзя маршалировать

Я не уверен, как используются дженерики, так как все типы конкретно указаны. Может кто-нибудь пролить некоторый свет на это? Код следует.

EDIT

Это может иметь отношение к тому, как компилятор 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 ]

13 голосов
/ 16 апреля 2011

Ваше 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 ко всем extern ed-функциям, в документации которых говорится, что при вызове GetLastError при сбое(что относится к GetModuleHandle, SetWindowsHookEx и UnhookWindowsHookEx).Таким образом, если произойдет сбой (и вы должны проверять возвращаемые значения ...), вы можете просто вызвать Win32Exception или позвонить Marshal.GetLastWin32Error, чтобы получить правильную диагностику.

EDIT : просто для ясности, вот все сигнатуры 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
1 голос
/ 16 апреля 2011

Вы пытались использовать управляемый C ++ с этим.Это может сделать перевод довольно простым.Тогда вам не понадобится P / Invoke.

РЕДАКТИРОВАТЬ: Я хотел бы отметить одну довольно важную вещь: компилятор сделает больше проверки типов для вас.Я уверен, что вам нравится ваша проверка типов, так как вы используете F # для остальной части приложения (надеюсь).

...