Kommunikation mit Konsolenanwendungen in VB .NET
Veröffentlicht: 26. Jan 2003 | Aktualisiert: 18. Aug 2005
Von Mathias Schiffer
Mithilfe des .NET Frameworks können Sie auf die Kommunikationskanäle "Standard Input", "Standard Output" und "Standard Error" einer gestarteten Konsolenanwendung zugreifen, die für Eingaben, Ausgaben und Fehlermeldungen verwendet werden.
Beim Start einer Konsolenanwendung über ein Process-Objekt der Frameworkklasse System.Diagnostics.Process können Sie in der StartInfo-Struktur des Objekts diverse Angaben dazu machen, auf welche Weise die Fremdanwendung gestartet werden soll (vgl. MSDN Quickie "Prozesse starten, überwachen und beenden mit VB .NET").
Hier können Sie auch angeben, ob die von Konsolenanwendungen für Eingaben, Ausgaben und Fehlermeldungen genutzten Kommunikationskanäle "Standard Input", "Standard Output" und "Standard Error" umgeleitet werden sollen: Setzen Sie die StartInfo-Eigenschaften RedirectStandardInput, RedirectStandardOutput und RedirectStandardError hierfür einfach auf True. Zusätzlich ist erforderlich, die StartInfo-Eigenschaft UseShellExecute auf False zu setzen, da die Umleitung der Kommunikationskanäle zwingend auf die Verwendung der API-Funktion CreateProcess zum Start einer Anwendung angewiesen ist (Process.Start verwendet je nach Bedarf CreateProcess oder ShellExecute, um eine Anwendung oder eine Datei in einer zugehörigen Anwendung zu starten).
Als Beispiel für eine Kommandozeilenanwendung soll hier der Befehlszeileninterpreter verwendet werden (COMMAND.COM bzw. CMD.EXE), dessen Aufrufpfad sich über die Umgebungsvariable COMSPEC mithilfe der Environ-Funktion ermitteln lässt. Ziel der beispielhaften Kommunikation mit dieser Konsolenanwendung soll sein, Dateien und Verzeichnisse des Datenträgers C:\ zu ermitteln: Nachdem der Befehlszeileninterpreter gestartet wurde, soll also der Befehl "DIR C:\*.*" ausgeführt werden. Die daraufhin normalerweise im Konsolenfenster erfolgende Ausgabe soll von Visual Basic .NET in einer Stringvariablen entgegengenommen werden.
' Ein neues Process-Objekt erzeugen Dim Anwendung As System.Diagnostics.Process _ = New System.Diagnostics.Process() ' Die StartInfo-Struktur des Process- ' Objekts mit Informationen befüllen With Anwendung.StartInfo ' Befehlszeileninterpreter (COMMAND.COM/CMD.EXE): .FileName = Environ("COMSPEC") ' Kein Konsolenfenster erzeugen: .CreateNoWindow = True ' StandardInput, -Output und -Error umleiten: .RedirectStandardInput = True .RedirectStandardOutput = True .RedirectStandardError = True ' UseShellExecute MUSS für eine Umleitung ' von StdIn, StdOut und StdErr FALSE sein: .UseShellExecute = False End With ' Den Befehlszeileninterpreter starten: Anwendung.Start()
Der Befehlszeileninterpreter wurde nun mit umgeleiteten Standard-Kommunikationskanälen gestartet. Um lesenden bzw. schreibenden Zugriff auf diese Kanäle zu erhalten, benötigen Sie für den "Standard Input" ein StreamWriter-Objekt. Die Kanäle "Standard Output" und "Standard Error" können Sie mit einem StreamReader-Objekt auslesen.
' StreamReader- und Writer-Objekte für den ' Zugriff auf StdIn, StdOut und StdErr erzeugen ' Über ein StreamWriter-Objekt in StandardInput schreiben Dim StdIn As System.IO.StreamWriter = Anwendung.StandardInput StdIn.AutoFlush = True ' Puffer automatisch flushen ' Über ein StreamReader-Objekt aus StandardOutput lesen Dim StdOut As System.IO.StreamReader = Anwendung.StandardOutput ' Über ein StreamReader-Objekt aus StandardError lesen Dim StdErr As System.IO.StreamReader = Anwendung.StandardError
Nun können Sie die StreamReader-Methoden Write oder WriteLine verwenden, um Eingaben in die Konsolenanwendung vorzunehmen. Die Ausgaben der Kanäle können Sie auf Basis der erzeugten StreamWriter-Objekte über deren Funktionen Read, ReadLine oder ReadToEnd empfangen. Über die Funktion Peek können Sie ermitteln, wie viele Zeichen aktuell zur Abholung bereit liegen (bei leerem Puffer returniert Peek den Wert -1).
Beispielhaft sollen zunächst die ersten drei Zeilen ausgelesen (und damit aus dem "Standard Output"-Puffer entfernt) werden, die beim Öffnen eines Befehlszeileninterpreter-Konsolenfensters von Windows eingefügt werden:
Ausgabe nach Start des Befehlszeileninterpreters
Dim sTemp As String ' Hilfsvariable ' Standardausgabe beim Starten des Befehlszeileninterpreters ' auslesen und damit aus dem Output-Puffer entfernen: sTemp &= StdOut.ReadLine() & vbNewLine sTemp &= StdOut.ReadLine() & vbNewLine sTemp &= StdOut.ReadLine() & vbNewLine MessageBox.Show(sTemp, "Befehlszeileninterpreter")
Ergebnis des Auslesevorgangs in der MessageBox
Die Ausgabe des DIR-Befehls an den Interpreter erfolgt über die Methode WriteLine des StreamWriter-Objekts über den Kanal "Standard Input". Zuvor wird im Beispiel - aus rein kosmetischen Gründen - auf gleichem Wege ein ECHO OFF gesendet, um die Ausgabe des Arbeitspfades in der Befehlszeile zu unterdrücken:
' Echo-Ausgaben unterdrücken StdIn.WriteLine("ECHO OFF") ' Einen DIR-Befehl über StdIn absetzen: StdIn.WriteLine("DIR C:\*.*") ' Einen EXIT-Befehl über StdIn absetzen: StdIn.WriteLine("EXIT") ' "ECHO OFF" und Befehlszeile "DIR C:\*.*" ' beim Auslesen ignorieren: sTemp = StdOut.ReadLine() sTemp = StdOut.ReadLine()
Der Befehlszeileninterpreter listet nun die Datei- und Verzeichnisübersicht für das angefragte Verzeichnis auf, die er stückweise in den Kanal "Standard Output" schreibt. Um das Ende dieser Ausgabe nicht programmiertechnisch ermitteln zu müssen, kann über StdIn bereits jetzt der Befehl EXIT gesendet werden, um die Konsolenanwendung zu schließen: Vor Abarbeitung dieses Befehls ist die Ausgabe des Kommandos DIR komplett abgeschlossen worden.
Mithilfe der Funktion ReadToEnd kann nun über das StreamReader-Objekt StdOut die Information aus dem Kanal "Standard Output" abgeholt werden, bevor alle Kanäle mit der Close-Anweisung wieder geschlossen werden.
' Konsolenausgabe auslesen: Dim sOutput As String ' Konsolen-Ausgaben sOutput = StdOut.ReadToEnd ' Streams schließen StdIn.Close() StdOut.Close() StdErr.Close()
Durch das Auslesen des Streams bis zu seinem aktuellen Ende ist auch die übergebene Anweisung EXIT noch Teil der in sOutput ausgelesenen Informationen. Dieser Teil kann nun noch entfernt werden. Weiterhin ist für Ausgabezwecke eine Umsetzung der ASCII-Ausgabe des Konsolenfensters in den Ansi-Zeichensatz vorzunehmen: Hier hilft die Klasse System.Text.Encoding durch eine Umwandlung in die Ansi-Codepage (Nr. 850).
' Abschließendes 'vbNewLine & "EXIT"' entfernen: sOutput = Strings.Left(sOutput, _ sOutput.IndexOf(vbNewLine & "EXIT")) ' Umwandlung der ASCII-Zeichen in Ansi-Codepage (Nr. 850): sOutput = System.Text.Encoding.GetEncoding(850).GetString( _ System.Text.Encoding.Default.GetBytes(sOutput)) ' Ausgabe des Ergebnisses: MessageBox.Show(sOutput, "Ausgabe der Konsolenanwendung")
Als letzter Schritt empfiehlt es sich sicherzustellen, dass die Konsolenanwendung durch den Befehl EXIT auch tatsächlich beendet wurde. Hierfür kann die HasExited-Eigenschaft des Process-Objekts herangezogen werden, um ggf. über seine Kill-Methode der Anwendung den sicheren Garaus zu machen. Abschließend wird nur noch das Process-Objekt geschlossen.
' Befehlszeileninterpreter ggf. "abschießen": If Not Anwendung.HasExited Then Anwendung.Kill() End If ' Ressourcen des Process-Objekts freigeben Anwendung.Close()