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

Posted

技术标签:

【中文标题】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 【发布时间】:2020-04-25 16:20:46 【问题描述】:

我正在将我为键绑定编写的一个小型应用程序移植到 .net 核心,并且我遇到了一个 the same code 行为不同的实例。我用这个声明在 F# 中调用SendInput function

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 框架应用程序中工作。现在,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

【问题讨论】:

我根据您的代码构建了一个最小的重现。可以找到here。对不起,我没有更多的帮助。 我还使用 Csharp 构建了一个 repro。为此,我将您的 fsharp 代码翻译为 csharp。同样的事情适用于 dotnet 框架,但显示 dotnet 核心的错误代码 87。 很好,谢谢。我想这意味着这不是 F# 问题,而是 .net 核心问题。我确实设法让它与我不满意的解决方案一起工作。我将通过这项工作更新原始问题。 错误代码 5 可能与 UIPI 相关。您需要满足某些要求。 Here 是一个关于它的线程。 【参考方案1】:

我最终打开了一个错误。

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 个也因平台而异的可选字段指定字段偏移量。

【讨论】:

以上是关于F# System.Runtime.InteropServices 对 SendInput 的本机库调用在 .net 框架中有效,但在 .net 核心中无效的主要内容,如果未能解决你的问题,请参考以下文章

P/Invoke Struct with Pointers, C++ from C#

模板莫比乌斯反演

斐波那契数列性质

如何求解:f(n) = f(n-1) + 3*f(n-2) + 3*f(n-3) + f(n-4)

C# 练习用函数递归计算 f(n)=f(n-1)+f(n-2) f=2 f=3

C# 练习用函数递归计算 f(n)=f(n-1)+f(n-2) f=2 f=3