Mega Code Archive

 
Categories / Delphi / Examples
 

Capture Output of a Console Application Revised

Title: Capture Output of a Console Application - Revised Question: How do you start a DOS or Console Application and capture the output while it is running ? For example, how do you capture the output of the FileCompare (FC) Command ? Answer: There are already two articles about this problem, http://www.delphi3000.com/articles/article_2112.asp http://www.delphi3000.com/articles/article_2298.asp but they get the output when the process is finished. There are also two Microsoft Articles about Redirection of DOS Applications : Microsoft Knowledge Base Article - Q190351 HOWTO: Spawn Console Processes with Redirected Standard Handles http://support.microsoft.com/default.aspx?scid=kb;en-us;Q190351 Microsoft Knowledge Base Article - Q150956 INFO: Redirection Issues on Windows 95 MS-DOS Applications http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q150956 The basic idea is to start the console application with CreateProcess and redirect the input and output with pipes, enabling the calling process to access the output of the console application. It is important to capture the output while the process is still running. If the output-pipe is blocked by an overflow, new information can not be written from the console app to the output-pipe, and the program stops. In this case we have a classic deadlock situation: the user (parent process) is waiting for the child-process to finish, and the child-process is waiting for the user to clear the buffer. procedure CaptureConsoleOutput(DosApp : string;AMemo : TMemo); const ReadBuffer = 1048576; // 1 MB Buffer var Security : TSecurityAttributes; ReadPipe,WritePipe : THandle; start : TStartUpInfo; ProcessInfo : TProcessInformation; Buffer : Pchar; TotalBytesRead, BytesRead : DWORD; Apprunning,n, BytesLeftThisMessage, TotalBytesAvail : integer; begin with Security do begin nlength := SizeOf(TSecurityAttributes); binherithandle := true; lpsecuritydescriptor := nil; end; if CreatePipe (ReadPipe, WritePipe, @Security, 0) then begin // Redirect In- and Output through STARTUPINFO structure Buffer := AllocMem(ReadBuffer + 1); FillChar(Start,Sizeof(Start),#0); start.cb := SizeOf(start); start.hStdOutput := WritePipe; start.hStdInput := ReadPipe; start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW; start.wShowWindow := SW_HIDE; // Create a Console Child Process with redirected input and output if CreateProcess(nil ,PChar(DosApp), @Security,@Security, true ,CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS, nil ,nil, start ,ProcessInfo) then begin n:=0; TotalBytesRead:=0; repeat // Increase counter to prevent an endless loop if the process is dead Inc(n,1); // wait for end of child process Apprunning := WaitForSingleObject(ProcessInfo.hProcess,100); Application.ProcessMessages; // it is important to read from time to time the output information // so that the pipe is not blocked by an overflow. New information // can be written from the console app to the pipe only if there is // enough buffer space. if not PeekNamedPipe(ReadPipe ,@Buffer[TotalBytesRead], ReadBuffer ,@BytesRead, @TotalBytesAvail,@BytesLeftThisMessage) then break else if BytesRead 0 then ReadFile(ReadPipe,Buffer[TotalBytesRead],BytesRead,BytesRead,nil); TotalBytesRead:=TotalBytesRead+BytesRead; until (Apprunning WAIT_TIMEOUT) or (n 150); Buffer[TotalBytesRead]:= #0; OemToChar(Buffer,Buffer); AMemo.Text := AMemo.text + StrPas(Buffer); end; FreeMem(Buffer); CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); CloseHandle(ReadPipe); CloseHandle(WritePipe); end; end;