F# System.Runtime.InteropServices 对 SendInput 的本机库调用在 .net 框架中有效,但在 .net 核心中无效

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

我正在将我为键绑定编写的一个小应用程序移植到 .net 核心,我已经 运行 跨越一个实例,其中 the same code behaves differently. I'm calling the SendInput function 在 F# 中使用此声明

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

相同的代码适用于我在 visual studio 中创建的 .net Framework 应用程序。现在,Marshal.GetLastWin32ErrorCode() 的输出是 87,这显然意味着 ERROR_INVALID_PARAMETER——不是很有帮助。我是 .net 和 F# 的新手,所以我不确定在这种情况下会有什么不同。我承认,即使获得此绑定代码也主要是反复试验。

如果有任何信息可以帮助我进行调试,我将不胜感激。

更新:我有一个我不满意的解决方法。我还不能解释为什么这会起作用——我需要阅读更多关于封送处理如何工作的信息。有了这个,Marshal.GetLastWin32ErrorCode()5,访问被拒绝。它仍然发送密钥,所以我不确定该错误是什么意思。就是说,就在这里。将联合从我正在使用的结构拆分为专用联合类型,使该联合类型成为 LayoutKind.Explicit,并使至少一个字段 FieldOffset(1)(但不是我关心的字段)得到按键工作。字段偏移的其他组合会导致某些东西可以工作但实际上不会按键,我认为这意味着它的编组方式导致看不到按键。

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

我最后打开了一个 bug。

https://github.com/dotnet/runtime/issues/1515

这在 .net 中不是问题。显然,我的 .net 框架应用程序是 运行 作为 32 位。我已将 dwExtraInfo 字段声明为 int,这对 32 位来说没问题。我的 .net 核心应用程序是 运行 作为 64 位,我应该使用 UIntPtr 来处理该字段长度的平台之间的差异。更新到此代码并且它有效。

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

主要区别在于 dwExtraInfo 字段定义。此外,联合现在有一个显式类型,而不是将其全部合并到一个 LPINPUT 中。这让我不必为 3 个可选字段指定字段偏移量,这也因平台而异。