为什么某些应用程序有时不接受某些sendkeys

时间:2016-10-01 16:44:25

标签: vb.net sendkeys postmessage sendinput

这是我之前遇到的一个问题,但我总是放弃解决问题并制定出一个解决方法。不是今天(希望如此)。

我正在尝试为经典的Doom II制作机器人。我希望我的机器人可以访问通过转义键访问的主菜单。当然我试过了:

sendkeys.send("{ESC}")

没有运气。但后来发生了一些奇怪的事情。当我已经在菜单上时,我意外地运行了代码......它关闭了菜单(如果你在菜单上按下escape,这是正常的)。很明显,Doom II听取了Sendkeys。

我已经尝试过sendinput,postmessage和simulateinput。没有工作(它们都具有与sendkeys描述的相同的行为)。

如果有人可以骑上白马并给我代码来解决这个问题,那会很棒,但除此之外,任何人都可以向我解释这种行为吗?

1 个答案:

答案 0 :(得分:2)

似乎Zandronum在游戏运行时(不暂停)不接受发送给它的虚拟键。我不确定,但似乎虚拟键实际上可能是窗口消息,就像Andrew Morton所说的那样(或者它们至少是类似的......)。解决方法是发送hardware scan code而不是virtual key code

硬件扫描代码似乎是按键时实际键盘发送的代码,而虚拟键代码是系统从中解释的密钥扫描代码(reference)。

所以我设法使用一些WinAPI函数向Zandronum发送击键(全屏和窗口):

通过使用我构建的下面的帮助程序类(或更正确的:包装程序),您现在可以以简单的方式发送击键(硬件与否),键的种类比SendKeys.Send()包含的更多。您可以使用System.Windows.Forms.Keys enumeration中的任意键。

这是用Zandronum测试的,完全可以使用:

InputHelper.PressKey(Keys.Escape, True) 'True = Send key as hardware scan code.

<强> InputHelper.vb:

Imports System.Runtime.InteropServices

Public NotInheritable Class InputHelper
    Private Sub New()
    End Sub

#Region "Methods"
#Region "PressKey()"
    ''' <summary>
    ''' Virtually presses a key.
    ''' </summary>
    ''' <param name="Key">The key to press.</param>
    ''' <param name="HardwareKey">Whether or not to press the key using its hardware scan code.</param>
    ''' <remarks></remarks>
    Public Shared Sub PressKey(ByVal Key As Keys, Optional ByVal HardwareKey As Boolean = False)
        If HardwareKey = False Then
            InputHelper.SetKeyState(Key, False)
            InputHelper.SetKeyState(Key, True)
        Else
            InputHelper.SetHardwareKeyState(Key, False)
            InputHelper.SetHardwareKeyState(Key, True)
        End If
    End Sub
#End Region

#Region "SetKeyState()"
    ''' <summary>
    ''' Virtually sends a key event.
    ''' </summary>
    ''' <param name="Key">The key of the event to send.</param>
    ''' <param name="KeyUp">Whether to push down or release the key.</param>
    ''' <remarks></remarks>
    Public Shared Sub SetKeyState(ByVal Key As Keys, ByVal KeyUp As Boolean)
        Key = ReplaceBadKeys(Key)

        Dim KeyboardInput As New KEYBDINPUT With {
            .wVk = Key,
            .wScan = 0,
            .time = 0,
            .dwFlags = If(KeyUp, KEYEVENTF.KEYUP, 0),
            .dwExtraInfo = IntPtr.Zero
        }

        Dim Union As New INPUTUNION With {.ki = KeyboardInput}
        Dim Input As New INPUT With {
            .type = INPUTTYPE.KEYBOARD,
            .U = Union
        }

        SendInput(1, New INPUT() {Input}, Marshal.SizeOf(GetType(INPUT)))
    End Sub
#End Region

#Region "SetHardwareKeyState()"
    ''' <summary>
    ''' Virtually sends a key event using the key's scan code.
    ''' </summary>
    ''' <param name="Key">The key of the event to send.</param>
    ''' <param name="KeyUp">Whether to push down or release the key.</param>
    ''' <remarks></remarks>
    Public Shared Sub SetHardwareKeyState(ByVal Key As Keys, ByVal KeyUp As Boolean)
        Key = ReplaceBadKeys(Key)

        Dim KeyboardInput As New KEYBDINPUT With {
            .wVk = 0,
            .wScan = MapVirtualKeyEx(CUInt(Key), 0, GetKeyboardLayout(0)),
            .time = 0,
            .dwFlags = KEYEVENTF.SCANCODE Or If(KeyUp, KEYEVENTF.KEYUP, 0),
            .dwExtraInfo = IntPtr.Zero
        }

        Dim Union As New INPUTUNION With {.ki = KeyboardInput}
        Dim Input As New INPUT With {
            .type = INPUTTYPE.KEYBOARD,
            .U = Union
        }

        SendInput(1, New INPUT() {Input}, Marshal.SizeOf(GetType(INPUT)))
    End Sub
#End Region

#Region "ReplaceBadKeys()"
    ''' <summary>
    ''' Replaces bad keys with their corresponding VK_* value.
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared Function ReplaceBadKeys(ByVal Key As Keys) As Keys
        Dim ReturnValue As Keys = Key

        If ReturnValue.HasFlag(Keys.Control) Then
            ReturnValue = (ReturnValue And Not Keys.Control) Or Keys.ControlKey 'Replace Keys.Control with Keys.ControlKey.
        End If

        If ReturnValue.HasFlag(Keys.Shift) Then
            ReturnValue = (ReturnValue And Not Keys.Shift) Or Keys.ShiftKey 'Replace Keys.Shift with Keys.ShiftKey.
        End If

        If ReturnValue.HasFlag(Keys.Alt) Then
            ReturnValue = (ReturnValue And Not Keys.Alt) Or Keys.Menu 'Replace Keys.Alt with Keys.Menu.
        End If

        Return ReturnValue
    End Function
#End Region
#End Region

#Region "WinAPI P/Invokes"
    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SendInput(ByVal nInputs As UInteger, <MarshalAs(UnmanagedType.LPArray)> ByVal pInputs() As INPUT, ByVal cbSize As Integer) As UInteger
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function MapVirtualKeyEx(uCode As UInteger, uMapType As UInteger, dwhkl As IntPtr) As UInteger
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function GetKeyboardLayout(idThread As UInteger) As IntPtr
    End Function

#Region "Enumerations"
    Private Enum INPUTTYPE As UInteger
        MOUSE = 0
        KEYBOARD = 1
        HARDWARE = 2
    End Enum

    <Flags()> _
    Private Enum KEYEVENTF As UInteger
        EXTENDEDKEY = &H1
        KEYUP = &H2
        SCANCODE = &H8
        UNICODE = &H4
    End Enum
#End Region

#Region "Structures"
    <StructLayout(LayoutKind.Explicit)> _
    Private Structure INPUTUNION
        <FieldOffset(0)> Public mi As MOUSEINPUT
        <FieldOffset(0)> Public ki As KEYBDINPUT
        <FieldOffset(0)> Public hi As HARDWAREINPUT
    End Structure

    Private Structure INPUT
        Public type As Integer
        Public U As INPUTUNION
    End Structure

    Private Structure MOUSEINPUT
        Public dx As Integer
        Public dy As Integer
        Public mouseData As Integer
        Public dwFlags As Integer
        Public time As Integer
        Public dwExtraInfo As IntPtr
    End Structure

    Private Structure KEYBDINPUT
        Public wVk As UShort
        Public wScan As Short
        Public dwFlags As UInteger
        Public time As Integer
        Public dwExtraInfo As IntPtr
    End Structure

    Private Structure HARDWAREINPUT
        Public uMsg As Integer
        Public wParamL As Short
        Public wParamH As Short
    End Structure
#End Region
#End Region
End Class

为了好玩,我还设法在MSDN上找到了扫描代码列表:https://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx

由于我自己是一个Doom粉丝而且熟悉它是如何工作的,也许你应该(根据你的旧问题)确保你在菜单中选择New Game然后按Enter键? / p>

Zandronum知道菜单项的名称,所以你只需要给它第一个字母,它就会跳转到以它开头的项目:

InputHelper.PressKey(Keys.Escape, True) 'Open the menu.
System.Threading.Thread.Sleep(100)      'Small delay to let the menu open.
InputHelper.PressKey(Keys.N, True)      'Jump to the "New Game" menu item.
InputHelper.PressKey(Keys.Enter, True)  'Go into the "New Game" menu.
InputHelper.PressKey(Keys.Enter, True)  'Start a new game.

我在游戏中测试了上述代码,以全屏模式运行。像魅力一样。