1

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!

Community
  • 1
  • 1
user3384674
  • 759
  • 8
  • 28
  • 1
    It is frankly amazing how many different broken variants of this program can be found. I think my code here works: http://stackoverflow.com/questions/25723807/execute-dos-program-and-get-output-dynamically – David Heffernan Jun 20 '15 at 14:06
  • David, thank you for your help. I tried your code and it works for several console applications except the one I need to use. The console applicationis is executed, the FileSize seem's to be correct but the AnsiBuffer contains only senseless information. Unfortunalety because of licensing and confidentialty reasons I cannot name or upload the console application. The console application is working fine in Wondows Commad Prompt. What can I try to do to further analyze or solve the problem? Thanks. – user3384674 Jun 22 '15 at 11:56
  • No idea. If you can't tell us about the app, it's going to be a bit tricky. – David Heffernan Jun 22 '15 at 11:58
  • David, I tried your code with the following: Command:='cmd.exe' and Parameters:='/c d:\ConsoleApp.exe /parameter > C:\test.txt'. I use the redirecting symbol ">" to write the output into a file. This works. (The command processor cmd.exe seems to be necessary so that ">" works.) There must be some problem with the redirection in the pipe. If you would have a private email address I could email you the console app. Maybe you could have a look at it? This would be very nice of you. Of course no problem if you do not have the time. Thank you very much! – user3384674 Jun 22 '15 at 17:08
  • No, I cannot do that. Sorry. Ask a question here if you want help. – David Heffernan Jun 22 '15 at 17:10
  • Thanks! The problem is the encoding of the console application's output. After `ReadFile` the AnsiBuffer contains `('ÿ', 'þ', 'S', #0, 't', #0, 'a', #0, 'r', #0, 't', #0, 'e', #0, 'd', #0, ...)` Also `OemToAnsi(AnsiBuffer, AnsiBuffer)` does not help, it just changes the first two elements `(' ', '¦', 'S', #0, 't', #0, 'a', #0, 'r', #0, 't', #0, 'e', #0, 'd', #0,...)`. The string conversion cuts then after the first #0. I searched intensively for a solution but could not solve the problem. It would be nice if you could guide me in the right direction. Thank you! – user3384674 Jun 22 '15 at 22:04
  • Console app is emitting UTF-16 – David Heffernan Jun 23 '15 at 03:53
  • Thanks for your answer. The question is how to handle it i.e. in Readfile what should be used as Buffer (what data type)? Should the OemToAnsi and the string coversion remain or should they be replace by more appropiate procedures? – user3384674 Jun 23 '15 at 09:12
  • You have to realise that you are now asking a very different question. I don't really want to do this in comments. If you have another question, ask it as a new question. You should also consider what to do with this question. – David Heffernan Jun 23 '15 at 09:14

2 Answers2

4

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.)

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
0

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.

user3384674
  • 759
  • 8
  • 28