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)