Following my previous post (Capturing console output with Delphi 2010/XE) and with all the great comments received on it, I have decided to publish the new solution provided by Lübbe Onken which solves the hanging issue when capturing the output for different kind of commands like ping, netstat, etc. The problem occurs on the last ReadFile which this solution will fix.
Here I'm summarizing Lübbe Onken comments on this issue:
"The current implementation assumes that if the external process is not finished yet, there must be something available to be read from the read pipe. This is not necessarily the case. If we use PeekNamedPipe to check for available data before entering the internal repeat loop, everything is fine.
I also put the CloseHandle into try ... finally and moved Application.ProcessMessages behind the internal read loop, because IMHO the screen update is better handled after processing the callback than before. But this is just cosmetic."
Here you can find the source code:
//Anonymous procedure approach by Lars Fosdal type TArg<T> = reference to procedure(const Arg: T); procedure TForm1.CaptureConsoleOutput(const ACommand, AParameters: String; CallBack: TArg<PAnsiChar>); const CReadBuffer = 2400; var saSecurity: TSecurityAttributes; hRead: THandle; hWrite: THandle; suiStartup: TStartupInfo; piProcess: TProcessInformation; pBuffer: array [0 .. CReadBuffer] of AnsiChar; dBuffer: array [0 .. CReadBuffer] of AnsiChar; dRead: DWORD; dRunning: DWORD; dAvailable: DWORD; begin saSecurity.nLength := SizeOf(TSecurityAttributes); saSecurity.bInheritHandle := true; saSecurity.lpSecurityDescriptor := nil; if CreatePipe(hRead, hWrite, @saSecurity, 0) then try FillChar(suiStartup, SizeOf(TStartupInfo), #0); suiStartup.cb := SizeOf(TStartupInfo); suiStartup.hStdInput := hRead; suiStartup.hStdOutput := hWrite; suiStartup.hStdError := hWrite; suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; suiStartup.wShowWindow := SW_HIDE; if CreateProcess(nil, PChar(ACommand + ' ' + AParameters), @saSecurity, @saSecurity, true, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess) then try repeat dRunning := WaitForSingleObject(piProcess.hProcess, 100); PeekNamedPipe(hRead, nil, 0, nil, @dAvailable, nil); if (dAvailable > 0) then repeat dRead := 0; ReadFile(hRead, pBuffer, CReadBuffer, dRead, nil); pBuffer[dRead] := #0; OemToCharA(pBuffer, dBuffer); CallBack(dBuffer); until (dRead < CReadBuffer); Application.ProcessMessages; until (dRunning <> WAIT_TIMEOUT); finally CloseHandle(piProcess.hProcess); CloseHandle(piProcess.hThread); end; finally CloseHandle(hRead); CloseHandle(hWrite); end; end;
CaptureConsoleOutput('java -version', '', procedure(const Line: PAnsiChar) begin Memo1.Lines.Add(String(Line)); end );
I want to say thanks to everyone who spend time looking at this issue and for making the community work and grow.