winapif#user32sendinput

F# System.Runtime.InteropServices native library call to SendInput works in .net framework but not in .net core


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

Solution

  • 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.