2016-10-01 12 views
0

이것은 이전에 만난 문제이지만 항상 문제를 해결하고 해결 방법을 찾았습니다. 오늘은 아니고 (잘하면).일부 응용 프로그램에서 일부 sendkeys를 허용하지 않는 이유는 무엇입니까?

나는 고전적인 Doom II를위한 봇을 만들려고 노력하고 있습니다. 내 봇이 이스케이프 키를 통해 액세스되는 기본 메뉴에 액세스 할 수있게하려고합니다. 당연히 나는 시도했다 :

sendkeys.send("{ESC}") 

No luck. 그러나 그때 이상한 일이 일어났습니다. 메뉴에 이미있을 때 실수로 코드를 실행했는데 메뉴를 닫았습니다 (메뉴에서 이탈을 누르면 정상입니다). 그래서 Doom II는 Sendkeys를 청취합니다.

나는 sendinput, postmessage 및 simulateinput을 시도했습니다. 아무 것도 작동하지 않았습니다 (모두 sendkeys에서 설명한 것과 동일한 동작을합니다).

누군가가 흰 말을 타고이 문제를 해결할 수있는 코드를 제공 할 수 있다면 좋겠지 만 그 외에는 아무도이 동작을 내게 설명 할 수 있습니까?

+1

아, 둠 II ... 옛날 고전 중 하나 ... 나는 그 게임을 정말 좋아합니다. :) - 주제 외에도 Doom II 또는 Windows 95 버전의 DOS 버전을 실행하고 있습니까? DOS 에뮬레이터를 사용하고 있다면 문제가 _ 생길 수 있습니다. –

+1

@visualVincent 정말 고전입니다. 정말 수치 스럽습니다! 실제로 Zandronum을 사용하여 실행하고 있습니다. 나는 내 인생에있어서 왜 탈출이 메뉴를 닫을 지 알 수는 없지만 그것을 열 수는 없다. – FraserOfSmeg

+1

Zandronum도 사용하기 때문에 좋습니다. DOS라고 말했 더라면 Doom95와 Zan 만 있기 때문에 도움이 더 어려웠을 것입니다. 나는'SendKeys'와'SendInput' 둘 다 시도해 볼 것입니다. 전체 화면 모드 또는 창 모드로 실행 중이십니까? –

답변

2

게임이 실행 중일 때 (일시 중지되지 않음) Zandronum이 가상 키를 받아들이지 않는 것 같습니다. 확실하지는 않지만 가상 키가 실제로 창 메시지일지도 모른다고 앤드류 모튼 (Andrew Morton)이 말했듯이 (또는 적어도 비슷한 것 ...). 이 문제를 해결하려면 virtual key code 대신 hardware scan code을 보내야합니다. 가상 키 코드 시스템이 스캔 코드 (reference)에서 해석 키 동안

하드웨어 스캔 코드이 키를 누를 때, 실제 키보드에 의해 전송 된 코드 것으로 보인다.

그래서 나는 몇 WinAPI를 함수를 사용하여 (전체 화면과 윈도우 모두) Zandronum에 키 입력을 보낼 관리 : 사용

  • SendInput() 실제 키보드 입력을 보낼 수 있습니다.
  • MapVirtualKeyEx() 키 코드를 스캔 코드로 변환하거나 그 반대로 변환하는 데 사용됩니다.
  • GetKeyboardLayout() 사용자의 현재 키보드 레이아웃을 가져 오는 데 사용됩니다 (예 : 스웨덴어 키보드 사용). 난 당신이 지금 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


를 어떻게 작동하는지 잘 알고 , 아마도 (이전 질문에 대해) 반드시 Enter 키를 누르기 전에 메뉴에서 New Game을 선택했는지 확인해야합니다.

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. 

전체 화면 모드에서 실행 중. 매력처럼 작동합니다.

+0

이것은 내가 찾고있는 것뿐만 아니라, 멋지고 깔끔한 구현입니다! 감사! – FraserOfSmeg

+0

이것은 내가 찾고 있었던 것이다! 그것은 완벽! 감사! – FraserOfSmeg

+0

나는 말했다 : "이것은 단지 * 단지 * 정확한 것이 아닙니다 ..."나는 내가 원하는 것을 정확하게 해주는 코드를 주었다는 것을 의미했다. 부수적으로 - 플레이어가 운명 2에서 코디네이트 할 수있는 방법을 알고 있습니까? – FraserOfSmeg