Delphi XE8: problems running an external console application, waiting for its results and capturing its results

922 views Asked by At

In Delphi XE8 under Windows, I am trying to call an external console application and capture its output. I use the following code, as described in Capture the output from a DOS (command/console) Window and also Getting output from a shell/dos app into a Delphi app:

procedure TForm1.Button1Click(Sender: TObject) ;

  procedure RunDosInMemo(DosApp:String;AMemo:TMemo) ;
  const
    ReadBuffer = 2400;
  var
    Security : TSecurityAttributes;
    ReadPipe,WritePipe : THandle;
    start : TStartUpInfo;
    ProcessInfo : TProcessInformation;
    Buffer : Pchar;
    BytesRead : DWord;
    Apprunning : DWord;
    S: String;
  begin
    With Security do begin
      nlength := SizeOf(TSecurityAttributes) ;
      binherithandle := true;
      lpsecuritydescriptor := nil;
    end;
    if Createpipe (ReadPipe, WritePipe,
                   @Security, 0) then 
    begin
      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;

      S:=UniqueString(DosApp);
      if CreateProcess(nil,
              PChar(S),
              @Security,
              @Security,
              true,
              NORMAL_PRIORITY_CLASS,
              nil,
              nil,
              start,
              ProcessInfo) then
      begin
        repeat
          Apprunning := WaitForSingleObject
                        (ProcessInfo.hProcess,100) ;
          Application.ProcessMessages;
        until (Apprunning <> WAIT_TIMEOUT) ;
        Repeat
          BytesRead := 0;
          ReadFile(ReadPipe,Buffer[0], ReadBuffer,BytesRead,nil) ;
          Buffer[BytesRead]:= #0;
          OemToAnsi(Buffer,Buffer) ;
          AMemo.Text := AMemo.text + String(Buffer) ;
        until (BytesRead < ReadBuffer) ;
      end;
      FreeMem(Buffer) ;
      CloseHandle(ProcessInfo.hProcess) ;
      CloseHandle(ProcessInfo.hThread) ;
      CloseHandle(ReadPipe) ;
      CloseHandle(WritePipe) ;
    end;
  end;

begin {button 1 code}
  RunDosInMemo('cmd.exe /c dir',Memo1) ; //<-- this works
  RunDosInMemo('"c:\consoleapp.exe" "/parameter"',Memo1) //<-- this hangs in the repeat until (Apprunning <> WAIT_TIMEOUT) ; 
end;

It works for DOS commands, but it does not work for a console application. The console application starts and executes correctly but it hangs in the repeat until (Apprunning <> WAIT_TIMEOUT) loop. What could I try to solve the problem?

Thank you very much!

2

There are 2 answers

0
user3384674 On BEST ANSWER

To summarize: @David Heffernan's code in Execute DOS program and get output dynamically works. The problem is that the console application emits UTF-16.

0
Rob Kennedy On

The program you're running is either expecting input (like from the keyboard) or producing more output than will fit in the pipe's buffer. In either case, that program hangs waiting for further I/O operations, but your parent program is waiting for that child to terminate before processing any output, and never provides any input.

You need to process the output pipe while the program is still running. Otherwise, you risk the buffer filling up, and the child will block until more space becomes available. Likewise, if you don't plan to provide any input to the other process, you probably shouldn't give it a valid input handle. That way, if it tries to read input, it will fail, rather than block.

Furthermore, the input handle you've given to that program is attached to the output handle. If the program attempts to read, it will be reading its own output, like an I/O ouroboros. To handle input and output, you need two pipes.

(Note that this deadlock is exactly the problem I called out in the comments of the answer you used. The second code block in the same answer addresses the problem, as well as the ouroboros problem.)