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
I ended up opening a bug on this.
https://github.com/dotnet/runtime/issues/1515
It wasn't an issue in .net. Apparently, my .net framework app was running as 32bit. I had declared the dwExtraInfo
field as an int
, which was fine for 32bit. My .net core app was running as 64bit and I should have used a UIntPtr
to handle the differences between platforms for the length of that field. Updated to this code and it works.
[<StructLayout(LayoutKind.Sequential)>]
type private MOUSEINPUT = struct
val dx: int32
val dy:int32
val mouseData:uint32
val dwFlags: uint32
val time: uint32
val dwExtraInfo: UIntPtr
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: UIntPtr
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 InputUnion = struct
[<FieldOffset(0)>]
val mutable mi : MOUSEINPUT
[<FieldOffset(0)>]
val mutable ki : KEYBDINPUT
[<FieldOffset(0)>]
val mutable hi : HARDWAREINPUT
end
[<StructLayout(LayoutKind.Sequential)>]
type private LPINPUT = struct
val mutable ``type``: int // 1 is keyboard
val mutable u: InputUnion
end
The main differences are the dwExtraInfo
field definitions. Also, there is an explicit type for the union now instead of having it all rolled into a single LPINPUT
. That let's me avoid having to specify the field offset for the 3 optional fields which also differs by platform.