Umleiten von Konsolenausgabe

Hallo

Ich versuche gerade, in VBA 2008 eine Konsolenanwendung zu starten und die Ausgaben von „stderr“ auf eine Textbox „tbstderr“ umzuleiten. Den Aufruf der Anwendung habe ich folgendermaßen realisiert:

 Private Sub MainForm\_Loaded(ByVal sender As System.Object, \_
 ByVal e As System.EventArgs) \_
 Handles MyBase.Shown
 Dim myProcess As New Process()

 'Name und Parameter der Anwendung
 Dim psi As New ProcessStartInfo("progname", "parameter")

 'Konsole verstecken
 psi.CreateNoWindow = True
 psi.WindowStyle = ProcessWindowStyle.Hidden
 psi.UseShellExecute = False

 'stderr umleiten
 psi.RedirectStandardError = True
 AddHandler myProcess.ErrorDataReceived, AddressOf myStderr
 myProcess.EnableRaisingEvents = True

 'Prozess starten
 myProcess.StartInfo = psi
 myProcess.Start()

 'Asynchrones Lesen starten
 myProcess.BeginErrorReadLine()

 'Warten, bis Prozess beendet ist
 myProcess.WaitForExit()
 End Sub

Jedesmal, wenn die Anwendung eine Zeile auf „stderr“ schreibt, wird der folgende Handler aufgerufen:

 Private Sub myStderr(ByVal sendingProcess As Object, \_
 ByVal outLine As DataReceivedEventArgs)

 If Not String.IsNullOrEmpty(outLine.Data) Then

 Me.tbstderr.AppendText(outLine.Data)

 End If

 End Sub

Der Befehl „Me.tbstderr.AppendText(outLine.Data)“, mit dem ich die Daten in die Textbox schreiben will, erzeugt die Fehlermeldung:

„Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement tbstderr erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.“

Wenn dieser Befehl weggelassen wird läuft das Programm fehlerfrei (es wird natürlich nichts ausgegeben). Durch den Debuggger kann man sehen, daß die korrekten Texte im Parameter „outLine.Data“ enthalten sind.

Hallo,

Ab .NET 2.0 sind threadübergreifende Zugriffe auf Controls nicht mehr „einfach so“ möglich. Das Stichwort ist

InvokeRequired

. Ein ausführliches (wenn auch nicht sofort eingängiges) Beispiel findet sich hier:

http://msdn.microsoft.com/en-us/library/ms171728%28V…

Ich versuche mal das für Dein Beispiel umzusetzen, aber steinige mich bitte nicht, wenn es nicht sofort funktioniert ich mache das jetzt ohne VS.

AAAAlso:
Du benötigst ein Delegate mit der Signatur Deiner Funktion:

 ' This delegate enables asynchronous calls for setting
 ' the text property on a TextBox control.
 Delegate Sub SetTextCallback(sendingProcess As Object, \_
 outLine As DataReceivedEventArgs)

Und dann musst Du Deinen Aufruf wie folgt ergänzen:

 Private Sub myStderr(ByVal sendingProcess As Object, \_
 ByVal outLine As DataReceivedEventArgs)

 If Not String.IsNullOrEmpty(outLine.Data) Then

 ' InvokeRequired required compares the thread ID of the
 ' calling thread to the thread ID of the creating thread.
 ' If these threads are different, it returns true.
 If Me.tbstderr.InvokeRequired Then

 Dim d As New SetTextCallback(AddressOf myStderr)
 Me.Invoke(d, New Object() {sendingProcess, outLine})

 Else

 Me.tbstderr.AppendText(outLine.Data)

 End If

 End If

 End Sub

Das sollte es gewesen sein.

Grüße
Morrighan.

Danke, hat funktioniert. Ich mußte allerdings zwei kleine Änderungen vornehmen:

  • Das Warten auf das Ende des Prozesses „myProcess.WaitForExit()“ verhindert die Ausgabe auf die Textbox.
  • Das Zeilenende-Zeichen wird an die Callback-Funktion nicht mit übergeben, mußte daher wieder eingefügt werden (durch „Me.tbstderr.AppendText(outLine.Data & NewLine)“)

Ab .NET 2.0 sind threadübergreifende Zugriffe auf Controls
nicht mehr „einfach so“ möglich. Das Stichwort ist

InvokeRequired

. Ein ausführliches (wenn
auch nicht sofort eingängiges) Beispiel findet sich hier:

http://msdn.microsoft.com/en-us/library/ms171728%28V…

Ich versuche mal das für Dein Beispiel umzusetzen, aber
steinige mich bitte nicht, wenn es nicht sofort funktioniert
ich mache das jetzt ohne VS.

Die Umsetzung war echt hilfreich, der MS-Artikel alleine hätte nicht geholfen. Nachdem man die Sache verstanden hat, versteht man auch den Artikel. Um die Steinigung bist Du also nochmal rumgekommen.

Der Übersicht halber hänge ich den vollständigen Code in der Form an, in der er funktioniert:

 ' This delegate enables asynchronous calls for setting
 ' the text property on a TextBox control.
 Delegate Sub SetTextCallback(ByVal sendingProcess As Object, \_
 ByVal outLine As DataReceivedEventArgs)
 '
 ' Event: output to stderr
 '
 Private Sub myStderr(ByVal sendingProcess As Object, \_
 ByVal outLine As DataReceivedEventArgs)

 If Not String.IsNullOrEmpty(outLine.Data) Then

 ' InvokeRequired required compares the thread ID of the
 ' calling thread to the thread ID of the creating thread.
 ' If these threads are different, it returns true.
 If Me.tbstderr.InvokeRequired Then

 Dim d As New SetTextCallback(AddressOf myStderr)
 Me.Invoke(d, New Object() {sendingProcess, outLine})

 Else

 Me.tbstderr.AppendText(outLine.Data & NewLine)

 End If

 End If

 End Sub
 '
 ' Handle Event: "MainForm" Shown
 '
 Private Sub MainForm\_Loaded(ByVal sender As System.Object, \_
 ByVal e As System.EventArgs) \_
 Handles MyBase.Shown
 Dim myProcess As New Process()

 'Name und Parameter der Anwendung
 Dim psi As New ProcessStartInfo("progname", "parameter")

 'Konsole verstecken
 psi.CreateNoWindow = True
 psi.WindowStyle = ProcessWindowStyle.Hidden
 psi.UseShellExecute = False

 'stderr umleiten
 psi.RedirectStandardError = True
 AddHandler myProcess.ErrorDataReceived, AddressOf myStderr
 myProcess.EnableRaisingEvents = True

 'Prozess starten
 myProcess.StartInfo = psi
 myProcess.Start()

 'Asynchrones Lesen starten
 myProcess.BeginErrorReadLine()

 End Sub

Hallo Hubert,
das hier

Die Umsetzung war echt hilfreich, der MS-Artikel alleine hätte
nicht geholfen. Nachdem man die Sache verstanden hat, versteht
man auch den Artikel. Um die Steinigung bist Du also nochmal
rumgekommen.

beruhigt mich schon sehr :wink:

Danke auch für Deine Ergänzungen, hab ich auch was dazugelernt (war der Tag nicht umsonst)

lg
Morrighan.