locked
Cannot get SendInput to work in VB.net RRS feed

  • Question

  • I've given it my best, looked at all the WebSites I could find, and it's now time to ask for help! I'm writting a "Console Application" using Visual Basic (VB.net) 2008 Express Edition in a Windows XP SP3 environment, and I'm trying to get SendInput to work.

    I have been able to get SendKeys working, and I'll use it if I must, but there are some unpleasant control issues. Plus, it's incompatible with Vista, from everything I've heard.

    So, I'm including two attachments. The first is a quick-and-dirty working example of SendMessage (actually SendMessageString) which I've used to half-way convince myself that I know how to use the "user32.dll" API's. (For simplicity, I've stripped out all of the error-testing logic.)

    The second bit of code is my unsuccessful attempt to get SendInput to send some test keystrokes to Notepad. (I've heard some people say it can't be done in VB.net. Are they right?) To get a clean compile, I had to comment out 'CopyMemory' because I'm not experienced enough to figure out how to get it to work. Is it essential to the code?

    Tim

    Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
      (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
    Public Declare Auto Function FindWindowEx Lib "user32" _
      (ByVal hWnd1 As IntPtr, ByVal hWnd2 As IntPtr, ByVal lpsz1 As String, _
       ByVal lpsz2 As String) As IntPtr
    Public Declare Auto Function SendMessageString Lib "user32" Alias "SendMessage" _
      (ByVal hWnd As IntPtr, ByVal Msg As Integer, _
       ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
    Public Const WM_SETTEXT = &HC
    
    Sub SendMessage_example
      Dim application_name         As String
      Dim process_ID               As IntPtr
      Dim window_title             As String
      Dim window_handle            As IntPtr
      Dim child_handle             As IntPtr
      Dim test_msg                 As String
    
      application_name = "notepad.exe"
        ' Open a Notepad session:
      process_ID       = Shell(application_name, vbNormalFocus)
      window_title     = "Untitled - Notepad"
        ' Get the link to the main window:
      window_handle    = FindWindow(vbNullString, window_title)
        ' Get the link to Notepad's text edit area:
      child_handle     = FindWindowEx(window_handle, 0, "EDIT", vbNullString)
    	test_msg         = "Hello World"
        ' SendMessageString used instead of SendMessage because
        ' 'lParam' must be a string, not a numeric pointer:
    	SendMessageString(child_handle, WM_SETTEXT, vbNullString, test_msg)
    End Sub
    
    Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
        (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
    Public Declare Auto Function FindWindowEx Lib "user32" _
        (ByVal hWnd1 As IntPtr, ByVal hWnd2 As IntPtr, ByVal lpsz1 As String, _
         ByVal lpsz2 As String) As IntPtr
    Public Declare Auto Function SendMessageString Lib "user32" Alias "SendMessage" _
        (ByVal hWnd As IntPtr, ByVal Msg As Integer, _
         ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
    Public Declare Auto Function SendInput Lib "user32.dll" _
        (ByVal nInputs As Long, pInputs As GENERALINPUT, ByVal cbSize As Long) As Long
    
    Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
        (pDst As Integer, pSrc As Integer, ByVal ByteLen As Long)
    Public Declare Sub CopyMemoryByref Lib "kernel32.dll" Alias "RtlMoveMemory" _
        (ByRef dest As Integer, ByRef source As Integer, ByVal numBytes As Integer)
    
    ' KEYBDINPUT - User-Defined input type
    Public Structure KEYBDINPUT
      Public wVk         As Short
      Public wScan       As Short
      Public dwFlags     As Integer
      Public dwTime      As Integer
      Public dwExtraInfo As IntPtr
    End Structure
    
    ' MOUSEINPUT - User-Defined input type
    Public Structure MOUSEINPUT
      Public dx          As Integer     ' Mouse 'x' coordinate
      Public dy          As Integer     ' Mouse 'y' coordinate
      Public mouseData   As Integer
      Public dwFlags     As Integer
      Public dwTime      As Integer
      Public dwExtraInfo As IntPtr
    End Structure
    
    Public Structure GENERALINPUT
      Public dwType      As Long
      Public vi          As KEYBDINPUT
      Public xi          As MOUSEINPUT
    '	Public xi(0 To 23) As Byte     ' From a VB6 WebSite.  Won't work in VB.net.
    End Structure
    
    Public Const INPUT_KEYBOARD As Integer = 1
    Public Const INPUT_MOUSE    As UInt32 = 0
    
    Public Const VK_E     = 69
    Public Const VK_F     = 70
    Public Const VK_H     = 72
    Public Const VK_L     = 76
    Public Const VK_O     = 79
    Public Const VK_MENU  = &H12    ' <Alt> Key  ->  Hex 12
    Public Const KEYEVENTF_KEYUP = &H2
    
    Public Const WM_SETTEXT = &HC
    
    Sub SendMessage_example
      Dim application_name         As String
      Dim process_ID               As IntPtr
      Dim window_title             As String
      Dim window_handle            As IntPtr
      Dim child_handle             As IntPtr
      Dim test_msg                 As String
    
      application_name = "notepad.exe"
        ' Open a Notepad session:
      process_ID       = Shell(application_name, vbNormalFocus)
      window_title     = "Untitled - Notepad"
        ' Get the link to the main window:
      window_handle    = FindWindow(vbNullString, window_title)
        ' Get the link to Notepad's text edit area:
      child_handle     = FindWindowEx(window_handle, 0, "EDIT", vbNullString)
      test_msg         = "Hello World"
        ' SendMessageString used instead of SendMessage because
        ' 'lParam' must be a string, not a numeric pointer:
      SendMessageString(child_handle, WM_SETTEXT, vbNullString, test_msg)
    
    ' At this point "Hello World" can be seen in the 'Untitled - Notepad'
    ' session.  But, the following effort to send "HELLO", keystroke by
    ' keystroke, followed by an <Alt> 'F' command, doesn't work.
    
      SendKey(VK_H)
      SendKey(VK_E)
      SendKey(VK_L)
      SendKey(VK_L)
      SendKey(VK_O)
      SendKey(VK_MENU)        ' <Alt> Key
      SendKey(VK_F)
    
    End Sub
    
    Sub SendKey(bKey As Byte)
      Dim GInput(0 To 1)  As GENERALINPUT
      Dim KInput          As KEYBDINPUT
    
      KInput.wVk = bKey                   ' The key to be pressed
      KInput.dwFlags = 0                  ' Option to have the key pressed
        ' Copy the structure into the input array's buffer:
      GInput(0).dwType = INPUT_KEYBOARD   ' Indicate keyboard input
        ' Not sure what the following two statements do, but
        ' couldn't get either variation to work:
    '   CopyMemory(GInput(0).xi(0), KInput, Len(KInput))
    '   CopyMemoryByref(GInput(0).xi(0), KInput, Len(KInput))
        ' Do the same as above, but for releasing the key:
      KInput.wVk = bKey                   ' The key to be released
      KInput.dwFlags = KEYEVENTF_KEYUP    ' Option to release the key
      GInput(1).dwType = INPUT_KEYBOARD   ' Indicate keyboard input
    '   CopyMemoryByref(GInput(1).xi(0), KInput, Len(KInput))   ' <---<< Couldn't get this to work
        ' Send the input now:
      Call SendInput(2, GInput(0), Len(GInput(0)))   ' <---<< But, nothing shows up anywhere
    End Sub
    
    Thursday, May 21, 2009 9:10 PM

Answers

  • VB.NET console application:

    Imports System.Runtime.InteropServices
    
    Module Module1
    
       
        Private Const VK_H As Short = 72
        Private Const VK_E As Short = 69
        Private Const VK_L As Short = 76
        Private Const VK_O As Short = 79
    
        Private Const KEYEVENTF_KEYUP As Integer = &H2
        Private Const INPUT_MOUSE As Integer = 0
        Private Const INPUT_KEYBOARD As Integer = 1
        Private Const INPUT_HARDWARE As Integer = 2
    
        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 Short
            Public wScan As Short
            Public dwFlags As Integer
            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
    
        <StructLayout(LayoutKind.Explicit)> _
        Private Structure INPUT
            <FieldOffset(0)> _
            Public type As Integer
            <FieldOffset(4)> _
            Public mi As MOUSEINPUT
            <FieldOffset(4)> _
            Public ki As KEYBDINPUT
            <FieldOffset(4)> _
            Public hi As HARDWAREINPUT
        End Structure
    
        Private Declare Function SendInput Lib "user32" (ByVal nInputs As Integer, ByVal pInputs() As INPUT, ByVal cbSize As Integer) As Integer
        Private Declare Function AttachThreadInput Lib "user32" (ByVal idAttach As IntPtr, ByVal idAttachTo As IntPtr, ByVal fAttach As Boolean) As Boolean
        Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As IntPtr, ByVal lpwdProcessId As IntPtr) As IntPtr
        Private Declare Function GetCurrentThreadId Lib "kernel32" () As IntPtr
    
        Private Sub SendKey(ByVal bKey As Short)
            Dim GInput(1) As INPUT
    
            ' press the key
            GInput(0).type = INPUT_KEYBOARD
            GInput(0).ki.wVk = bKey
            GInput(0).ki.dwFlags = 0
    
            ' release the key
            GInput(1).type = INPUT_KEYBOARD
            GInput(1).ki.wVk = bKey
            GInput(1).ki.dwFlags = KEYEVENTF_KEYUP
    
            SendInput(2, GInput, Marshal.SizeOf(GetType(INPUT)))
    
        End Sub
    
    
        Sub Main()
            Dim notepad As Process = Process.Start("notepad.exe")
            If notepad.WaitForInputIdle() Then
    
                Dim hNotePad As IntPtr = notepad.MainWindowHandle
                Dim hNoteThread As IntPtr = GetWindowThreadProcessId(hNotePad, IntPtr.Zero)
    
                If hNoteThread <> IntPtr.Zero Then
                    If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then
                        SendKey(VK_H)
                        SendKey(VK_E)
                        SendKey(VK_L)
                        SendKey(VK_L)
                        SendKey(VK_O)
    
                        AttachThreadInput(GetCurrentThreadId(), hNotePad, False)
                    End If
                End If
            End If
        End Sub
    End Module
    
    HTH

    Tom Shelton
    • Marked as answer by Tim_Atlanta Friday, May 22, 2009 3:44 PM
    Thursday, May 21, 2009 11:10 PM
  • As far as structures, go...  Your basically correct, though I wouldn't define it as an attribute.  It is a new type -  it can have properties and constructors (though it can't have a default constructor).  It can have methods.

    As for the <StructLayout()>,  etc.  Those are called attributes :)  They are used for  a lot of things in .NET - but, in this case they are used to pass additional infomration to the VB.NET marshaller (basically the code that translates VB types to native types when your doing p/invoke calls).

    See the native declaration of the INPUT structure is:

    typedef struct tagINPUT { 
      DWORD type; 
      union {MOUSEINPUT mi; 
                KEYBDINPUT ki;
                HARDWAREINPUT hi;
               };
      }INPUT, *PINPUT;

    Basically, it's a structure that contains a union.  A union in C/C++ is a special type of structure where all of it's members occupy the same memory.

    Often times in VB, you'll see people create byte arrays or use CopyMemory to move stuff in and out of the field.  I believe this is a hold over from VB6 where this sort of thing was necessary.  In VB.NET not only is it not necessary, it's dangerous if done improperly :)

    The reason it is unnecessary in VB.NET is because the marshaller can convert your structure to a real union when told to do so, and that is what the applied attributes do:

       ' Tell the marshaller that we will specify the byte position
       ' where each field of the structure should start.
        <StructLayout(LayoutKind.Explicit)> _
        Private Structure INPUT
            ' this field starts at byte o
            <FieldOffset(0)> _
            Public type As Integer
    
            ' the rest of the fields begin at byte 4, immediately
            ' following the type field.
            <FieldOffset(4)> _
            Public mi As MOUSEINPUT
            <FieldOffset(4)> _
            Public ki As KEYBDINPUT
            <FieldOffset(4)> _
            Public hi As HARDWAREINPUT
        End Structure
    
    

    I hope that made things a little clearer.  Really as far as learning this stuff, check the documentation on Attributes to get an idea of what they are.  They are a generally useful construct that are used all over in .NET.


    Tom Shelton
    • Marked as answer by Tim_Atlanta Friday, May 22, 2009 3:44 PM
    Friday, May 22, 2009 3:27 PM
  • I see what's happening... the window in the foreground is the ide.  You need to force the notepad window to the front:

    Option Strict On
    Option Explicit On
    
    Imports System.Runtime.InteropServices
    
    Module Module1
    
    
        Private Const VK_H As Short = 72
        Private Const VK_E As Short = 69
        Private Const VK_L As Short = 76
        Private Const VK_O As Short = 79
    
        Private Const KEYEVENTF_KEYUP As Integer = &H2
        Private Const INPUT_MOUSE As Integer = 0
        Private Const INPUT_KEYBOARD As Integer = 1
        Private Const INPUT_HARDWARE As Integer = 2
    
        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 Short
            Public wScan As Short
            Public dwFlags As Integer
            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
    
        <StructLayout(LayoutKind.Explicit)> _
        Private Structure INPUT
            <FieldOffset(0)> _
            Public type As Integer
            <FieldOffset(4)> _
            Public mi As MOUSEINPUT
            <FieldOffset(4)> _
            Public ki As KEYBDINPUT
            <FieldOffset(4)> _
            Public hi As HARDWAREINPUT
        End Structure
    
        Private Declare Function SendInput Lib "user32" (ByVal nInputs As Integer, ByVal pInputs() As INPUT, ByVal cbSize As Integer) As Integer
        Private Declare Function AttachThreadInput Lib "user32" (ByVal idAttach As IntPtr, ByVal idAttachTo As IntPtr, ByVal fAttach As Boolean) As Boolean
        Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As IntPtr, ByVal lpwdProcessId As IntPtr) As IntPtr
        Private Declare Function GetCurrentThreadId Lib "kernel32" () As IntPtr
        Private Declare Auto Function FindWindow Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
        Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As IntPtr) As Boolean
    
        Private Sub SendKey(ByVal bKey As Short)
            Dim GInput(1) As INPUT
    
            ' press the key
            GInput(0).type = INPUT_KEYBOARD
            GInput(0).ki.wVk = bKey
            GInput(0).ki.dwFlags = 0
    
            ' release the key
            GInput(1).type = INPUT_KEYBOARD
            GInput(1).ki.wVk = bKey
            GInput(1).ki.dwFlags = KEYEVENTF_KEYUP
    
            SendInput(2, GInput, Marshal.SizeOf(GetType(INPUT)))
    
        End Sub
    
    
        Sub Main()
            'Dim notepad As Process = Process.Start("notepad.exe")
            'If notepad.WaitForInputIdle() Then
    
            'Dim hNotePad As IntPtr = notepad.MainWindowHandle
            Dim hNotePad As IntPtr = FindWindow(Nothing, "Untitled - Notepad")
    
            Dim hNoteThread As IntPtr = GetWindowThreadProcessId(hNotePad, IntPtr.Zero)
    
            If hNoteThread <> IntPtr.Zero Then
                If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then
    
                    SetForegroundWindow(hNotePad)
                    SendKey(VK_H)
                    SendKey(VK_E)
                    SendKey(VK_L)
                    SendKey(VK_L)
                    SendKey(VK_O)
    
                    AttachThreadInput(GetCurrentThreadId(), hNotePad, False)
                End If
            End If
            'End If
        End Sub
    End Module
    
    

    HTH
    Tom Shelton
    • Marked as answer by Tim_Atlanta Friday, May 29, 2009 1:59 PM
    Wednesday, May 27, 2009 8:28 PM

All replies

  • VB.NET console application:

    Imports System.Runtime.InteropServices
    
    Module Module1
    
       
        Private Const VK_H As Short = 72
        Private Const VK_E As Short = 69
        Private Const VK_L As Short = 76
        Private Const VK_O As Short = 79
    
        Private Const KEYEVENTF_KEYUP As Integer = &H2
        Private Const INPUT_MOUSE As Integer = 0
        Private Const INPUT_KEYBOARD As Integer = 1
        Private Const INPUT_HARDWARE As Integer = 2
    
        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 Short
            Public wScan As Short
            Public dwFlags As Integer
            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
    
        <StructLayout(LayoutKind.Explicit)> _
        Private Structure INPUT
            <FieldOffset(0)> _
            Public type As Integer
            <FieldOffset(4)> _
            Public mi As MOUSEINPUT
            <FieldOffset(4)> _
            Public ki As KEYBDINPUT
            <FieldOffset(4)> _
            Public hi As HARDWAREINPUT
        End Structure
    
        Private Declare Function SendInput Lib "user32" (ByVal nInputs As Integer, ByVal pInputs() As INPUT, ByVal cbSize As Integer) As Integer
        Private Declare Function AttachThreadInput Lib "user32" (ByVal idAttach As IntPtr, ByVal idAttachTo As IntPtr, ByVal fAttach As Boolean) As Boolean
        Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As IntPtr, ByVal lpwdProcessId As IntPtr) As IntPtr
        Private Declare Function GetCurrentThreadId Lib "kernel32" () As IntPtr
    
        Private Sub SendKey(ByVal bKey As Short)
            Dim GInput(1) As INPUT
    
            ' press the key
            GInput(0).type = INPUT_KEYBOARD
            GInput(0).ki.wVk = bKey
            GInput(0).ki.dwFlags = 0
    
            ' release the key
            GInput(1).type = INPUT_KEYBOARD
            GInput(1).ki.wVk = bKey
            GInput(1).ki.dwFlags = KEYEVENTF_KEYUP
    
            SendInput(2, GInput, Marshal.SizeOf(GetType(INPUT)))
    
        End Sub
    
    
        Sub Main()
            Dim notepad As Process = Process.Start("notepad.exe")
            If notepad.WaitForInputIdle() Then
    
                Dim hNotePad As IntPtr = notepad.MainWindowHandle
                Dim hNoteThread As IntPtr = GetWindowThreadProcessId(hNotePad, IntPtr.Zero)
    
                If hNoteThread <> IntPtr.Zero Then
                    If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then
                        SendKey(VK_H)
                        SendKey(VK_E)
                        SendKey(VK_L)
                        SendKey(VK_L)
                        SendKey(VK_O)
    
                        AttachThreadInput(GetCurrentThreadId(), hNotePad, False)
                    End If
                End If
            End If
        End Sub
    End Module
    
    HTH

    Tom Shelton
    • Marked as answer by Tim_Atlanta Friday, May 22, 2009 3:44 PM
    Thursday, May 21, 2009 11:10 PM
  • Tom,

    I've been getting paid to write computer programs for 36 years.  So one of these days I'm going to go out and have a nervous breakdown; I've worked hard for it, and I deserve it!

    For two weeks, I've about gone crazy putting together scraps of code that I've found in myriad WebSites, but nothing solved my problem.  I've seen most of the components you used, but nowhere were they assembled into one contigous piece of working VB.net code.  Thank you for putting it all together!!!

    The example worked just as advertised, without the need to add any Imports or fix any keystroke errors.  What's more, when I added SendKey commands for <Alt> 'F' 'X' and 'N', Notepad obligingly shut down without saving the changes.  Your solution is much more simple and elegant than anything I had tried.  I couldn't be happier.

    While I have your attention, I'd like to ask a couple of other questions:

    Am I correct in assuming that setting up a "Private / Public Structure..." for something like XYZ is a way of defining a new "attribute" (for lack of a better word) that can be used by a DIM field name?  For example, if I said "Dim ABC As Long", then ABC would become an area which could hold a large, signed number, because that's the meaning of 'Long'.  So, if I say "Private Structure XYZ" and set aside within it an EmpNbr for six bytes and EmpName for 25 bytes, then I've established an attribute that can be used in "Dim EmpData As XYZ" to allow EmpData to be treated as a 31 byte storage area in memory.  In Cobol terms for the old-timers out there, it would seem like saying:
      02 EmpData.
        03 EmpNbr Pic 9(6).
        03 EmpName Pic x(25).
    Am I on-track so far?

    If so, here's my bigger question:  What is the purpose of "<StructLayout(LayoutKind.Explicit)>" and "<FieldOffset(0)>", etc.?  They weren't being used at most of the WebSites.  Are they instructions that tell the VB.net compiler to set aside certain amounts of memory (or storage area), starting at specific, relative locations, for the "attribute" and its components?  Are the '<' and '>' symbols used for boundaries for instructions that, in the old days, used to be called "compiler directing instructions"?  Are there any good resources for learning more about these kinds of instructions?

    Tim

    Friday, May 22, 2009 2:54 PM
  • As far as structures, go...  Your basically correct, though I wouldn't define it as an attribute.  It is a new type -  it can have properties and constructors (though it can't have a default constructor).  It can have methods.

    As for the <StructLayout()>,  etc.  Those are called attributes :)  They are used for  a lot of things in .NET - but, in this case they are used to pass additional infomration to the VB.NET marshaller (basically the code that translates VB types to native types when your doing p/invoke calls).

    See the native declaration of the INPUT structure is:

    typedef struct tagINPUT { 
      DWORD type; 
      union {MOUSEINPUT mi; 
                KEYBDINPUT ki;
                HARDWAREINPUT hi;
               };
      }INPUT, *PINPUT;

    Basically, it's a structure that contains a union.  A union in C/C++ is a special type of structure where all of it's members occupy the same memory.

    Often times in VB, you'll see people create byte arrays or use CopyMemory to move stuff in and out of the field.  I believe this is a hold over from VB6 where this sort of thing was necessary.  In VB.NET not only is it not necessary, it's dangerous if done improperly :)

    The reason it is unnecessary in VB.NET is because the marshaller can convert your structure to a real union when told to do so, and that is what the applied attributes do:

       ' Tell the marshaller that we will specify the byte position
       ' where each field of the structure should start.
        <StructLayout(LayoutKind.Explicit)> _
        Private Structure INPUT
            ' this field starts at byte o
            <FieldOffset(0)> _
            Public type As Integer
    
            ' the rest of the fields begin at byte 4, immediately
            ' following the type field.
            <FieldOffset(4)> _
            Public mi As MOUSEINPUT
            <FieldOffset(4)> _
            Public ki As KEYBDINPUT
            <FieldOffset(4)> _
            Public hi As HARDWAREINPUT
        End Structure
    
    

    I hope that made things a little clearer.  Really as far as learning this stuff, check the documentation on Attributes to get an idea of what they are.  They are a generally useful construct that are used all over in .NET.


    Tom Shelton
    • Marked as answer by Tim_Atlanta Friday, May 22, 2009 3:44 PM
    Friday, May 22, 2009 3:27 PM
  • Tom,

    You've been a great help.  Thank you for your prompt and very clear and understandable answers to my questions.

    Tim
    Friday, May 22, 2009 3:46 PM
  • Tom,

    The following snippet of code from your above example works really well when needing to open a new Notepad file and link to it in preparation for using SendKey()
                <snip>
      notepad = Process.Start("notepad.exe")
      If notepad.WaitForInputIdle() Then
        hNotePad = notepad.MainWindowHandle
        hNoteThread = GetWindowThreadProcessId(hNotePad, IntPtr.Zero)
        If hNoteThread <> IntPtr.Zero Then
          If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then
            SendKey(VK_H)
                <snip>

    So, I thought it would be quite easy to build on that example when linking to an already open Notepad window.  But, I can't get the following code to work.  Where in the world is my error / misunderstanding?

      Dim ProcessName As String = "notepad"
      Dim WindowName As String = "Existing_Data.txt - Notepad"
      Dim WindowHandle As New IntPtr
      WindowHandle = FindWindow(ProcessName, WindowName)
      hNoteThread = GetWindowThreadProcessId(WindowHandle, IntPtr.Zero)
      If hNoteThread <> IntPtr.Zero Then
        If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then
          SendKey(VK_H)
            (etc.)

    Where 'FindWindow' is defined as follows:
      Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
        (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr

    Tim

    Wednesday, May 27, 2009 1:59 PM
  • Hi Tim,

    If I understand where your taking this, then I would write the code to look something like:

    Private Declare Auto Function FindWindow Lib "user32" ( _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As IntPtr
    
    ....
    
    
    hNotePad = FindWindow (Nothing, WindowName)
    
    From there, things should be basically the same.  Of course, this assumes you know the full title of the window...  Things get more interesting if you don't :)

    HTH

    Tom Shelton
    Wednesday, May 27, 2009 6:06 PM
  • Tom,

    I made the minor adjustments to 'FindWindow' that you suggested, and I put "Nothing" in place of "ProcessName".  I also tried doing a "build" and running the .EXE version, but nothing works.  I'm sure the value for WindowName is correct because I get a good WindowHandle value ('8653626') which, in turn, gives me what appears to be a valid hNoteThread of 5040 that's consistent with the GetCurrentThreadId() of 4392.

    I don't know if this is helpful, but, when I step through the VB.net program using <F8>, the "h" will appear in front of the 'SendInput' line of code.  Or, if I set a breakpoint in the Microsoft VB Express editor at the "AttachThreadInput(GetCurrentThreadId(), hNotePad, False)" line of code, the whole word "hello" will pop up there.  It's just that the doggone code won't send the keystrokes to the right thread.

    Tim

    Wednesday, May 27, 2009 7:46 PM
  • I see what's happening... the window in the foreground is the ide.  You need to force the notepad window to the front:

    Option Strict On
    Option Explicit On
    
    Imports System.Runtime.InteropServices
    
    Module Module1
    
    
        Private Const VK_H As Short = 72
        Private Const VK_E As Short = 69
        Private Const VK_L As Short = 76
        Private Const VK_O As Short = 79
    
        Private Const KEYEVENTF_KEYUP As Integer = &H2
        Private Const INPUT_MOUSE As Integer = 0
        Private Const INPUT_KEYBOARD As Integer = 1
        Private Const INPUT_HARDWARE As Integer = 2
    
        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 Short
            Public wScan As Short
            Public dwFlags As Integer
            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
    
        <StructLayout(LayoutKind.Explicit)> _
        Private Structure INPUT
            <FieldOffset(0)> _
            Public type As Integer
            <FieldOffset(4)> _
            Public mi As MOUSEINPUT
            <FieldOffset(4)> _
            Public ki As KEYBDINPUT
            <FieldOffset(4)> _
            Public hi As HARDWAREINPUT
        End Structure
    
        Private Declare Function SendInput Lib "user32" (ByVal nInputs As Integer, ByVal pInputs() As INPUT, ByVal cbSize As Integer) As Integer
        Private Declare Function AttachThreadInput Lib "user32" (ByVal idAttach As IntPtr, ByVal idAttachTo As IntPtr, ByVal fAttach As Boolean) As Boolean
        Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As IntPtr, ByVal lpwdProcessId As IntPtr) As IntPtr
        Private Declare Function GetCurrentThreadId Lib "kernel32" () As IntPtr
        Private Declare Auto Function FindWindow Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
        Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As IntPtr) As Boolean
    
        Private Sub SendKey(ByVal bKey As Short)
            Dim GInput(1) As INPUT
    
            ' press the key
            GInput(0).type = INPUT_KEYBOARD
            GInput(0).ki.wVk = bKey
            GInput(0).ki.dwFlags = 0
    
            ' release the key
            GInput(1).type = INPUT_KEYBOARD
            GInput(1).ki.wVk = bKey
            GInput(1).ki.dwFlags = KEYEVENTF_KEYUP
    
            SendInput(2, GInput, Marshal.SizeOf(GetType(INPUT)))
    
        End Sub
    
    
        Sub Main()
            'Dim notepad As Process = Process.Start("notepad.exe")
            'If notepad.WaitForInputIdle() Then
    
            'Dim hNotePad As IntPtr = notepad.MainWindowHandle
            Dim hNotePad As IntPtr = FindWindow(Nothing, "Untitled - Notepad")
    
            Dim hNoteThread As IntPtr = GetWindowThreadProcessId(hNotePad, IntPtr.Zero)
    
            If hNoteThread <> IntPtr.Zero Then
                If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then
    
                    SetForegroundWindow(hNotePad)
                    SendKey(VK_H)
                    SendKey(VK_E)
                    SendKey(VK_L)
                    SendKey(VK_L)
                    SendKey(VK_O)
    
                    AttachThreadInput(GetCurrentThreadId(), hNotePad, False)
                End If
            End If
            'End If
        End Sub
    End Module
    
    

    HTH
    Tom Shelton
    • Marked as answer by Tim_Atlanta Friday, May 29, 2009 1:59 PM
    Wednesday, May 27, 2009 8:28 PM
  • Tom

    'SetForegroundWindow(hNotePad)' was the final, missing piece of the puzzle that I've needed in order to get my project off the ground.

    I, and the 8,000+ other folks who have been following this thread for the past week, owe you a big "Thank You!" for providing the first working SendMessage() example for VB.net that I've been able to find anywhere on the Internet.

    I didn't immediately respond to your Wednesday evening answer because I wanted to first test the SendKey logic on my unique application, which is a mainframe terminal emulator, called "myEXTRA! Enterprise", that's written by the Attachmate Corporation.

    In the Notepad example we've been using, all we needed to do to close the window (without saving the data) was to submit an <Alt> 'F' 'X' 'N' series of keystrokes (VK_MENU, VK_F, VK_X, VK_N), and Notepad would close down without a whimper.

    But, in Attachmate Extra, the logic lost focus (for lack of a better word) after submitting the 'X' to "Exit" from the session.  I found that including another 'SetForegroundWindow()' command just before the 'Y' would re-establish the focus onto the pop-up window so I could say "Yes" to the question of whether or not I really wanted to "disconnect the session".  I've included the "Sub Main()" portion of the code as an example of exactly how I did it.

    Again, thank you very much, Tom, for your excellent working examples!

    Tim

    Private Const VK_MENU          As Short = &H12  ' <Alt> Key  -  Hex 12
    Private Const VK_F             As Short = 70
    Private Const VK_X             As Short = 88
    
    Sub Main()
      Dim hExtra As IntPtr = FindWindow(Nothing, "INVISION RCO - myEXTRA! Enterprise")
      Dim hExtraThread As IntPtr = GetWindowThreadProcessId(hExtra, IntPtr.Zero)
      If hExtraThread <> IntPtr.Zero Then
        If AttachThreadInput(GetCurrentThreadId(), hExtraThread, True) Then
          SetForegroundWindow(hExtra)  ' Bring the "INVISION RCO" window to the foreground
          SendKey(VK_H)
          SendKey(VK_E)
          SendKey(VK_L)
          SendKey(VK_L)
          SendKey(VK_O)
          SendKey(VK_MENU)             ' <Alt> Key
          SendKey(VK_F)
          SendKey(VK_X)
          SetForegroundWindow(hExtra)  ' Change focus to the "exit" pop-up window
          SendKey(VK_Y)                ' "Y" - Yes, disconnect the session
          AttachThreadInput(GetCurrentThreadId(), hExtra, False)
        End If
      End If
    End Sub
    
    Friday, May 29, 2009 1:58 PM
  • I see what's happening... the window in the foreground is the ide.  You need to force the notepad window to the front:

    Option Strict On
    Option Explicit On
    
    Imports System.Runtime.InteropServices
    
    Module Module1
    
    
      Private Const VK_H As Short = 72
      Private Const VK_E As Short = 69
      Private Const VK_L As Short = 76
      Private Const VK_O As Short = 79
    
      Private Const KEYEVENTF_KEYUP As Integer = &H2
      Private Const INPUT_MOUSE As Integer = 0
      Private Const INPUT_KEYBOARD As Integer = 1
      Private Const INPUT_HARDWARE As Integer = 2
    
      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 Short
        Public wScan As Short
        Public dwFlags As Integer
        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
    
      <StructLayout(LayoutKind.Explicit)> _
      Private Structure INPUT
        <FieldOffset(0)> _
        Public type As Integer
        <FieldOffset(4)> _
        Public mi As MOUSEINPUT
        <FieldOffset(4)> _
        Public ki As KEYBDINPUT
        <FieldOffset(4)> _
        Public hi As HARDWAREINPUT
      End Structure
    
      Private Declare Function SendInput Lib "user32" (ByVal nInputs As Integer, ByVal pInputs() As INPUT, ByVal cbSize As Integer) As Integer
      Private Declare Function AttachThreadInput Lib "user32" (ByVal idAttach As IntPtr, ByVal idAttachTo As IntPtr, ByVal fAttach As Boolean) As Boolean
      Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As IntPtr, ByVal lpwdProcessId As IntPtr) As IntPtr
      Private Declare Function GetCurrentThreadId Lib "kernel32" () As IntPtr
      Private Declare Auto Function FindWindow Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
      Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As IntPtr) As Boolean
    
      Private Sub SendKey(ByVal bKey As Short)
        Dim GInput(1) As INPUT
    
        ' press the key
        GInput(0).type = INPUT_KEYBOARD
        GInput(0).ki.wVk = bKey
        GInput(0).ki.dwFlags = 0
    
        ' release the key
        GInput(1).type = INPUT_KEYBOARD
        GInput(1).ki.wVk = bKey
        GInput(1).ki.dwFlags = KEYEVENTF_KEYUP
    
        SendInput(2, GInput, Marshal.SizeOf(GetType(INPUT)))
    
      End Sub
    
    
      Sub Main()
        'Dim notepad As Process = Process.Start("notepad.exe")
        'If notepad.WaitForInputIdle() Then
    
        'Dim hNotePad As IntPtr = notepad.MainWindowHandle
        Dim hNotePad As IntPtr = FindWindow(Nothing, "Untitled - Notepad")
    
        Dim hNoteThread As IntPtr = GetWindowThreadProcessId(hNotePad, IntPtr.Zero)
    
        If hNoteThread <> IntPtr.Zero Then
          If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then
    
            SetForegroundWindow(hNotePad)
            SendKey(VK_H)
            SendKey(VK_E)
            SendKey(VK_L)
            SendKey(VK_L)
            SendKey(VK_O)
    
            AttachThreadInput(GetCurrentThreadId(), hNotePad, False)
          End If
        End If
        'End If
      End Sub
    End Module
    
    
    

    HTH
    Tom Shelton

    I've been trying to get a basic API sendinput going and found this thread that seems to get me close... but not quite there.

     

    I've manipulated the above code to fit into a Windows Form Application (other miscellaneous info: VB.NET 2008 on WinXP, framework 3.5, but configured for framework 2.0 as this is what is required for the eventual target system).

    It is basically a form with one command button on it.  Notepad is already running.

    When executed it brings Notepad to the foreground.... and that's it, no values are passed to notepad.

    Any help would be "Greatly" appreciated.

    Thanks in advance

    Option Strict On

    Option Explicit On

     

    Imports System.Runtime.InteropServices

     

    Public Class Form1

        Private Const VK_H As Short = 72

        Private Const VK_E As Short = 69

        Private Const VK_L As Short = 76

        Private Const VK_O As Short = 79

     

        Private Const KEYEVENTF_KEYUP As Integer = &H2

        Private Const INPUT_MOUSE As Integer = 0

        Private Const INPUT_KEYBOARD As Integer = 1

        Private Const INPUT_HARDWARE As Integer = 2

     

        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 Short

            Public wScan As Short

            Public dwFlags As Integer

            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

     

        <StructLayout(LayoutKind.Explicit)> _

        Private Structure INPUT

            <FieldOffset(0)> _

            Public type As Integer

            <FieldOffset(4)> _

            Public mi As MOUSEINPUT

            <FieldOffset(4)> _

            Public ki As KEYBDINPUT

            <FieldOffset(4)> _

            Public hi As HARDWAREINPUT

        End Structure

        Private Declare Function SendInput Lib "user32" (ByVal nInputs As Integer, ByVal pInputs() As INPUT, ByVal cbSize As Integer) As Integer

        Private Declare Function AttachThreadInput Lib "user32" (ByVal idAttach As IntPtr, ByVal idAttachTo As IntPtr, ByVal fAttach As Boolean) As Boolean

        Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As IntPtr, ByVal lpwdProcessId As IntPtr) As IntPtr

        Private Declare Function GetCurrentThreadId Lib "kernel32" () As IntPtr

        Private Declare Auto Function FindWindow Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr

        Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As IntPtr) As Boolean

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

            'Dim notepad As Process = Process.Start("notepad.exe")

            'If notepad.WaitForInputIdle() Then

     

            'Dim hNotePad As IntPtr = notepad.MainWindowHandle

            Dim hNotePad As IntPtr = FindWindow(Nothing, "Untitled - Notepad")

     

            Dim hNoteThread As IntPtr = GetWindowThreadProcessId(hNotePad, IntPtr.Zero)

     

            If hNoteThread <> IntPtr.Zero Then

                If AttachThreadInput(GetCurrentThreadId(), hNoteThread, True) Then

     

                    SetForegroundWindow(hNotePad)

                    SendKey(VK_H)

                    SendKey(VK_E)

                    SendKey(VK_L)

                    SendKey(VK_L)

                    SendKey(VK_O)

     

                    AttachThreadInput(GetCurrentThreadId(), hNotePad, False)

                End If

            End If

            'End If

        End Sub

     

        Private Sub SendKey(ByVal bKey As Short)

            Dim GInput(1) As INPUT

     

            ' press the key

            GInput(0).type = INPUT_KEYBOARD

            GInput(0).ki.wVk = bKey

            GInput(0).ki.dwFlags = 0

     

            ' release the key

            GInput(1).type = INPUT_KEYBOARD

            GInput(1).ki.wVk = bKey

            GInput(1).ki.dwFlags = KEYEVENTF_KEYUP

     

            SendInput(2, GInput, Marshal.SizeOf(GetType(INPUT)))

     

        End Sub

     

    End Class



    Aussie
    Friday, October 22, 2010 12:03 AM