da hier immer öfters gefragt wird, wie man denn einen Text aus einem externen Programm (evtl. auch einigen Spielen) ausliest oder an diesen einen Text sendet, ohne dass dieser im Vordergrund ist, habe ich mich entschloßen ein kleines Tutorial zu schreiben. Programmieren werde ich das ganze in VB.NET, achja und ich werde hier keine Grundlagen erklären à la "nun doppelklick auf den Button, um in das Button_Click-Ereignis zu kommen", sowas müsstet ihr schon selbst wissen.
Eingeteilt ist das Tutorial in drei Bereiche, einmal das Auslesen, das Senden bzw. Schreiben eines Textes und das Anklicken eines Buttons.
Joa, und nun genug gequatscht, fangen wir mal an:
1. Teil - Auslesen
Was brauchen wir dafür?
- Microsoft Spy++ (enthalten im Visual Studio [um unsere TextBox zu finden])
- unser "Opfer"-Programm

Als Testprogramm, habe ich einfach mal schnell ein simples Programm in VB.NET erstellt, welche zwei TextBoxen besitzt. Damit das ganze nicht zu leicht wird und man die Vorgänge besser verdeutlichen kann, habe ich diese bewusst in einige Steuerelemente gepackt. D.h. die TextBoxen sind in einer GroupBox, welche sich wiederum in einem TabControl befindet. Das macht das ganze etwas schwieriger, aber auch realistischer beim späteren Umsetzen an anderen Programmen, da diese ja auch oft komplex aufgebaut sind.
Mein Programm sieht also so aus:

Nun kommen wir auch schon zu Spy++.
Dazu gehen wir in Visual Studio auf Extras -> Spy++.
Hier suchen wir nun nach unserem Testprogramm:

Klicken wir nun links auf das [+]-Zeichen bis alle Untereinträge geöffnet wurden.
Spy++ zeigt uns den Aufbau des Programms an, also welches Control (Steuerelement, in unserem Fall also die TextBoxen) wo untergeordnet ist und wie dieses heißt.
Aufgelistet werden die Einträge folgendermaßen: "Fenstertitel" Klassenname
Fenstertitel habe ich im folgenden Bild grün markiert, und den Klassennamen blau:

Nun haben wir auch schon alle Informationen, die wir brauchen und können mit dem Coding-Teil beginnen.
Um unsere beiden TextBoxen zu finden benutzen wir zwei Windows-API's:
- FindWindow (um das Hauptfenster zu finden)
- FindWindowEx (um die untergeordneten Controls zu finden)
PHP Code:
//FindWindow
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindow( _
ByVal lpClassName As String, _
ByVal lpWindowTitle As String) As IntPtr
End Function
//FindWindowEx
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function FindWindowEx(ByVal parentHandle As IntPtr, _
ByVal childAfter As IntPtr, _
ByVal lpClassName As String, _
ByVal lpWindowTitle As String) As IntPtr
End Function
Schauen wir uns mal den Aufbau der FindWindow-API an:
- FindWindow(lpClassName As String, lpWindowTitle As String) As IntPtr
Als 1. Parameter wird also der Klassenname und als 2. Parameter der Fenstertitel benötigt.
Nun gehen wir zurück zu Spy++ und nehmen die Werte raus.
Bei meinem Testprogramm ist der Klassenname WindowsForms10.Window.8.app.0.b7ab7b
und der Fenstertitel Form1.
Wir erstellen uns also eine IntPtr-Variable und suchen das Hauptfenster:
PHP Code:
Dim Hauptfenster As IntPtr = FindWindow("WindowsForms10.Window.8.app.0.b7ab7b", "Form1")
Gehen wir zurück zu Spy++ und schauen welches Control darunterliegt -> das TabControl.
Ab hier verwenden wir die FindWindowEx-API. Schauen wir uns mal den Aufbau an:
- FindWindowEx(parentHandle As IntPtr, childAfter As IntPtr, lpClassName As String, lpWindowTitle As String) As IntPtr
parentHandle = das übergeordnete Control. Gehen wir in Spy++ einen Eintrag über unserem TabControl nach oben und was sehen wir? Das Hauptfenster selbst. D.h. das TabControl liegt direkt auf dem Hauptfenster. Da wir diesen ja schon gefunden haben und ihn der Variable "Hauptfenster" übergeben haben, benutzen wir diese einfach auch.
childAfter = Rechtsklick auf den Eintrag (das TabControl) in Spy++ -> Eigenschaften -> oben im Reiter auf Fenster -> Vorheriges Fenster -> Beschriftung: *WERT*

Wie wir sehen steht da einfach nur "", also nichts. In VB.NET schreiben wir also "IntPtr.Zero" hin.
lpClassName = Klassenname, das blau markierte auf dem 3. Bild oder Rechtsklick auf den Eintrag -> Eigenschaften -> oben im Reiter auf Klasse -> Klassenname: *WERT*
Bei mir ist es WindowsForms10.SysTabControl32.app.0.b7ab7b.
lpWindowTitle = Fenstertitel, das grün markierte auf dem 3. Bild.
Bei mir steht wieder einfach nur "", hier benutzen wir in VB.NET "Nothing".
Wir erstellen also auch für das TabControl eine IntPtr-Variable und übergeben ihr die Informationen:
PHP Code:
Dim TabControl As IntPtr = FindWindowEx(Hauptfenster, IntPtr.Zero, "WindowsForms10.SysTabControl32.app.0.b7ab7b", Nothing)
PHP Code:
Dim TabPage1 As IntPtr = FindWindowEx(TabControl, IntPtr.Zero, "WindowsForms10.Window.8.app.0.b7ab7b", "TabPage1")
PHP Code:
Dim GroupBox As IntPtr = FindWindowEx(TabPage1, IntPtr.Zero, "WindowsForms10.Window.8.app.0.b7ab7b", "Test Group")
Unser Problem ist jedoch, dass beide den gleichen Klassennamen haben und der Fenstertitel ist einfach der Text der in der TextBox steht. Da wir diesen ja auslesen wollen und dieser sich ändert, können wir ihn schlecht der IntPtr-Variable übergeben. Was machen wir denn nun? Zum Glück gibt es ja den Parameter childAfter in der FindWindowEx-API. Wie bereits gesagt finden wir diesen, indem wir Rechtsklick auf den Eintrag in Spy++ machen -> Eigenschaften -> oben im Reiter auf Fenster -> Vorheriges Fenster -> Beschriftung: *WERT*
Die obere TextBox in Spy++ (welche komischerweise die 2. TextBox in meinem TestProgramm ist) hat als Wert: "", also nichts. D.h. wir benutzen hier wieder IntPtr.Zero. Bei der unteren TextBox haben wir jedoch den Wert: "second textbox" (der Textinhalt der 2. TextBox in meinem Testprogramm). D.h. hier benutzen wir nicht IntPtr.Zero, sondern die obere TextBox selbst:
PHP Code:
//da in Spy++ die 2. TextBox über der 1. ist, mache ich das hier genauso:
Dim TextBox2 As IntPtr = FindWindowEx(GroupBox, IntPtr.Zero, "WindowsForms10.EDIT.app.0.b7ab7b", Nothing)
Dim TextBox1 As IntPtr = FindWindowEx(GroupBox, TextBox2, "WindowsForms10.EDIT.app.0.b7ab7b", Nothing)
PHP Code:
//SendMessageByInt (um die Textlänge auszulesen)
Private Declare Function SendMessageByInt Lib "user32.dll" Alias "SendMessageA" _
(ByVal hWnd As IntPtr, _
ByVal uMsg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Integer
//SendMessageByString
Private Declare Function SendMessageByString Lib "user32.dll" Alias "SendMessageA" _
(ByVal hWnd As IntPtr, _
ByVal uMsg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As StringBuilder) As Integer
//Konstanten
Const WM_GETTEXT As Integer = &HD
Const WM_GETTEXTLENGTH As Integer = &HE
//Auslesen-Funktion:
Public Function GetText(ByVal hwnd As IntPtr) As String
If (Not hwnd.Equals(IntPtr.Zero)) Then
Dim length As Integer = SendMessageByInt(hwnd, WM_GETTEXTLENGTH, 0, 0)
If length > 0 Then
Dim SB As New StringBuilder(length)
Dim Result As Int32 = SendMessageByString(hwnd, WM_GETTEXT, length + 1, SB)
If Result <> 0 Then
Return SB.ToString()
Else
Return String.Empty
End If
Else
Return "Kein Text vorhanden"
End If
Else
Return "Handle nicht gefunden"
End If
End Function
PHP Code:
MessageBox.Show(GetText(TextBox2))
Mein Ergebnis:

Klappt also
So, nun kommen wir zum zweiten Teil - Text in die TextBox schreiben.
2. Teil - Schreiben/Senden
In diesem Teil werden wir nun versuchen, den Text aus der TextBox nicht auszulesen, sondern diesen zu ändern bzw. setzen. Da wir ja bereits im 1. Teil unsere beiden TextBoxen gefunden haben, müssen wir auch nicht mehr so viel tun.
Um den Text zu übergeben benötigen wir wieder die SendMessage-API, nun jedoch mit einem kleinen Unterschied was die Parameter angeht:
PHP Code:
//SendMessage
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
(ByVal handle As IntPtr, ByVal uMsg As Integer, _
ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
//Konstante
Const WM_SETTEXT As Integer = &HC
PHP Code:
//hiermit setze ich den Text der 1. TextBox in meinem Testprogramm auf "test123"
SendMessage(TextBox1, WM_SETTEXT, 0, "test123")
Vorher:

Nachher:

Klappt also auch!
Ja, das wars eigentlich auch schon mit dem 2. Teil. Etwas kurz, aber naja. Werde mich bemühen, die einzelnen Parameter nochmal genauer zu beschreiben, damit auch jeder versteht, warum der Aufruf denn so aussieht.
Achja, habe mich entschloßen, noch einen 3. Teil zu erstellen, indem gezeigt wird, wie man einen Button anklicken kann. Da ich gerade jedoch nicht viel Zeit habe, wird es etwas dauern, also einfach ein bisschen warten.
3. Teil - Button anklicken
So, nach sehr langer Zeit kommt nun auch der 3. Teil - einen Button anklicken.
Dieser Teil wird auch nicht besonders lang sein, da wir nur den Button, den wir anklicken wollen, finden müssen (wird bereits in Teil 1 bzw. 2 detailiert erklärt) und mit der SendMessage-API klicken lassen.
Die Deklarationen:
PHP Code:
//SendMessage-API, um den Button anzuklicken
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
(ByVal hWnd As IntPtr, _
ByVal uMsg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Int32
//Konstante
Const BM_Click As Integer = &HF5
PHP Code:
SendMessage(ButtonHandle, BM_Click, 0, 0)
Das war's auch schon mit dem 3. Teil!
4. Teil - ComboBox-Eintrag auswählen
Da mich einige Leute in Skype gefragt haben, wie man einen Eintrag aus einer ComboBox auswählen kann, mache ich hier nochmal kurz ein kleines Beispiel.
Deklarationen:
PHP Code:
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
(ByVal handle As IntPtr, ByVal uMsg As Integer, _
ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
Const CB_SETCURSEL As Integer = &H14E
PHP Code:
SendMessage(ComboBoxHandle, CB_SETCURSEL, 0, Nothing)
Mit freundlichen Grüßen
Kraizy






