Hallo,
ich habe wieder ein Problem
Ich möchte gerne die Konsolenausgabe eines Programms in ein Memo umleiten - gut, das gab's hier schon oft. Allerdings brauche ich die Ausgabe schon, während das Programm noch läuft, und nicht erst am Ende. Dafür habe ich leider keine fertige Lösung gefunden, weshalb ich angefangen habe, selbst eine zu basteln, auf Basis des Codes aus der
CodeLib.
Zuerst habe ich in der Schleife entsprechende Ereignisse ausgelöst, was auch ganz gut funktionierte. Aber da ich eine allgemeingültige Lösung wollte, die auch StdErr mitauswertet, musste ich eine andere Lösung finden, da das Auslesen von StdErr immer das ganze Programm blockierte, wenn keine Fehler ausgegeben wurden. Die Idee war jetzt, die
API-Funktion ReadFile durch die asynchrone Funktion ReadFileEx zu ersetzen, um zu verhindern, dass eine Pipe die andere blockiert. Also habe ich alles, was für das asynchrone auslesen einer Pipe nötig ist, in die Klasse TPipeReader gekapselt. Soweit so gut.
Problem: Es funktioniert nicht. Um genau zu sein funktioniert es einmal, bei der ersten Ausgabe die das Programm tätigt... danach wird das Callback nicht mehr aufgerufen, obwohl der Puffer des Readers sich füllt.
Hier mal der zurechtgestutzte Code:
Delphi-Quellcode:
procedure TConsoleRedirector.Run(Command:
string);
var
CreationFlags: DWORD;
ProcessInfo: TProcessInformation;
SecurityAttr: TSecurityAttributes;
StartupInfo: TStartupInfo;
begin
{ ... }
// Pipe-Reader seine Lesepipe zuweisen und lauschen
FOutputReader.Pipe := PipeOutputRead;
FOutputReader.Listen;
// Mit diesen Zeilen friert das Programm leider komplett ein:
// FErrorReader.Pipe := PipeErrorsRead;
// FErrorReader.Listen;
// Schleife ausführen solange gestarteter Prozess läuft
// Der letzte Parameter gibt an, dass an dieser Stelle das Callback
// ausgeführt werden kann
while not (WaitForSingleObjectEx(ProcessInfo.hProcess, 0, True)
in
[WAIT_OBJECT_0, WAIT_ABANDONED])
do
begin
Application.ProcessMessages;
end;
{ ... }
end;
procedure PipeReadCallback(ErrorCode: dword; NumberOfBytesTransfered: dword;
Overlapped: POverlapped);
stdcall;
var
PipeReadBuffer: TPipeReader;
begin
WriteLn(Format('
PipeReadCallback %d, %d', [ErrorCode, NumberOfBytesTransfered]));
// In hEvent dürfen laut MSDN Zusatzdaten gespeichert werden, da es von ReadFileEx nicht berücksichtigt wird
PipeReadBuffer := TPipeReader(Overlapped^.hEvent);
PipeReadBuffer.FBytesRead := NumberOfBytesTransfered;
PipeReadBuffer.DoRead;
end;
{ TPipeBuffer }
procedure TPipeReader.DoRead;
begin
if Assigned(FOnRead)
then
FOnRead(Self, Data);
// Auf Folgedaten warten
Listen;
end;
procedure TPipeReader.Listen;
begin
if ReadFileEx(Pipe, Buffer, BufferSize, @FOverlapped, @PipeReadCallback)
then
WriteLn('
ReadFileEx ok')
else
WriteLn('
ReadFileEx NOT OK');
end;
end.
Wie ich bereits sagte funktioniert das ganze genau einmal. Wenn man jetzt z.B. das Kommando "cmd /K dir C:" ausführt, erhält man die erste ausgegebene Zeile, aber der Rest fehlt. Danach friert das Programm ein, weil es darauf wartet, dass das aufgerufene Programm beendet wird, aber das aufgerufene Programm wiederum darauf wartet, dass die Pipe geleert wird, was nicht passiert, da das Callback ja aus unerfindlichen Gründen nicht aufgerufen wird. Bei meinem Brainfuck-JIT-Compiler, wofür ich das ganze eigentlich geschrieben habe, kann ich so nur das erste Zeichen empfangen.
Was mache ich falsch?
Vielen Dank.