I'm porting a small application I wrote for keybindings to .net core and I've run across an instance where the same code behaves differently. I'm calling the SendInput function in F# with this declaration
open System.Runtime.InteropServices
[<StructLayout(LayoutKind.Sequential)>]
type private MOUSEINPUT = struct
val dx: int32
val dy:int32
val mouseData:uint32
val dwFlags: uint32
val time: uint32
val dwExtraInfo: int
new(_dx, _dy, _mouseData, _dwFlags, _time, _dwExtraInfo) = {dx=_dx; dy=_dy; mouseData=_mouseData; dwFlags=_dwFlags; time=_time; dwExtraInfo=_dwExtraInfo}
end
[<StructLayout(LayoutKind.Sequential)>]
type private KEYBDINPUT = struct
val wVk: uint16
val wScan: uint16
val dwFlags: uint32
val time: uint32
val dwExtraInfo:int
new(_wVk, _wScan, _dwFlags, _time, _dwExtraInfo) = {wVk =_wVk; wScan = _wScan; dwFlags = _dwFlags; time = _time; dwExtraInfo = _dwExtraInfo}
end
[<StructLayout(LayoutKind.Sequential)>]
type private HARDWAREINPUT = struct
val uMsg: uint32
val wParamL: uint16
val wParamH: uint16
new(_uMsg, _wParamL, _wParamH) = {uMsg = _uMsg; wParamL = _wParamL; wParamH = _wParamH}
end
[<StructLayout(LayoutKind.Explicit)>]
type private LPINPUT = struct
[<FieldOffset(0)>]
val mutable ``type``:int // 1 is keyboard
[<FieldOffset(4)>]
val mutable mi : MOUSEINPUT
[<FieldOffset(4)>]
val mutable ki : KEYBDINPUT
[<FieldOffset(4)>]
val mutable hi : HARDWAREINPUT
end
module private NativeMethods =
[<DllImport("user32.dll", SetLastError=true)>]
extern uint32 SendInput(uint32 nInputs, LPINPUT* pInputs, int cbSize)
let appSignature = 0xA8969
let private createPressInput (code: int) =
let mutable input = LPINPUT()
input.``type`` <- InputModes.INPUT_KEYBOARD
input.ki <- KEYBDINPUT(uint16 code, uint16 0, Dwords.KEYEVENTF_KEYDOWN, uint32 0, appSignature)
input
let pressKey (code: int) =
let input = createPressInput code
NativeMethods.SendInput(uint32 1, &&input, Marshal.SizeOf(input)) |> ignore
The same code works in a .net framework application that I created in visual studio. Now, the output of Marshal.GetLastWin32ErrorCode()
is 87
which apparently means ERROR_INVALID_PARAMETER
-- not very helpful. I'm new to .net and F# so I'm not sure what could be different in this context. I admit, even getting this binding code was mostly trial and error as well.
I'd appreciate any info that could help me debug this.
UPDATE: I have a workaround that I'm not satisfied with. I can't explain why this works just yet -- I need to read more about how marshaling works. With this, the Marshal.GetLastWin32ErrorCode()
is 5
, access denied. It still send the key so I'm not sure what that error is supposed to mean. That said, here it is. Splitting out the union from the struct that I was using into a dedicated union type, making that union type LayoutKind.Explicit
, and making at least one of the fields FieldOffset(1)
(but not the field I care about) gets key presses working. Other combinations of field offsets result in something that works but doesn't actually press keys, which I assume means that its marshaled in a way that results in no visible key presses.
[<StructLayout(LayoutKind.Explicit)>]
type private InputUnion = struct
[<FieldOffset(0)>]
val mutable ki : KEYBDINPUT
[<FieldOffset(1)>]
val mutable mi : MOUSEINPUT
[<FieldOffset(1)>]
val mutable hi : HARDWAREINPUT
end
[<StructLayout(LayoutKind.Sequential)>]
type private LPINPUT = struct
val ``type``:int // 1 is keyboard
val u: InputUnion
new(_type, _u) = {``type`` = _type; u = _u}
end