3

I have VCL application written in Delphi XE2 that needs to execute a command-line program (also written in Delphi XE2) and obtain the text output by it. I am currently using the following code, which is based on that found here: Getting output from a shell/dos app into a Delphi app

function GetDosOutput(ACommandLine : string; AWorkingDirectory : string): string;
var
  SecurityAttributes : TSecurityAttributes;
  StartupInfo : TStartupInfo;
  ProcessInformation: TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite: THandle;
  WasOK: Boolean;
  Buffer: array[0..255] of AnsiChar;
  BytesRead: Cardinal;
  Handle: Boolean;
begin
  Result := '';
  SecurityAttributes.nLength := SizeOf(TSecurityAttributes);
  SecurityAttributes.bInheritHandle := True;
  SecurityAttributes.lpSecurityDescriptor := nil;
  CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SecurityAttributes, 0);
  try
    FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
    StartupInfo.cb := SizeOf(TStartupInfo);
    StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    StartupInfo.wShowWindow := SW_HIDE;
    StartupInfo.hStdInput := StdOutPipeRead;
    StartupInfo.hStdOutput := StdOutPipeWrite;
    StartupInfo.hStdError := StdOutPipeWrite;
    FillChar(ProcessInformation, SizeOf(ProcessInformation), 0);
    Handle := CreateProcess(
      nil,
      PChar(ACommandLine),
      nil,
      nil,
      True,
      0,
      nil,
      PChar(AWorkingDirectory),
      StartupInfo,
      ProcessInformation
    );
    CloseHandle(StdOutPipeWrite);
    if Handle then
      try
        repeat
          WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
          if BytesRead > 0 then
          begin
            Buffer[BytesRead] := #0;
            Result := Result + Buffer;
          end;
        until not WasOK or (BytesRead = 0);
        WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
      finally
        CloseHandle(ProcessInformation.hThread);
        CloseHandle(ProcessInformation.hProcess);
      end;
  finally
    CloseHandle(StdOutPipeRead);
  end;
end;

This works fine on most versions of Windows. Unfortunately it has recently come to our attention that it does not work on Windows XP. The call to WaitForSingleObject simply never returns. I tried replacing the second parameter INFINITE with a smaller value (e.g. 15000) but that doesnt't seem to make any difference. In Task Manager I can see that, after calling GetDosOutput, the command-line program is actually running. If I end the VCL application, the command-line program then seems to complete its work successfully (as evidenced by the fact that it outputs the files I was expecting it to). I've also noticed that if I remove STARTF_USESTDHANDLES from StartupInfo.dwFlags, the command-line program runs normally and WaitForSingleObject returns promptly; however I am then obviously unable to obtain the text returned by the program.

Does anybody have a suggestion as to how I can get this working on Windows XP?

Community
  • 1
  • 1
Tim
  • 375
  • 1
  • 4
  • 18
  • 2
    You can find correct code in answer to following task: http://stackoverflow.com/questions/9119999/getting-output-from-a-shell-dos-app-into-a-delphi-app/9120103#9120103 – Andrei Galatyn Sep 26 '13 at 06:22
  • 6
    I enjoyed reading this bit: Handle: Boolean – David Heffernan Sep 26 '13 at 07:10
  • 1
    @DavidHeffernan For people with a strongly typed background the BOOL type of the winAPI can be confusing. – mg30rg Sep 26 '13 at 07:27
  • @mg30rg C is a strongly typed language. It just has not had a boolean type until recent times. – David Heffernan Sep 26 '13 at 07:48
  • 1
    Maybe this helps too http://stackoverflow.com/questions/16123427/how-to-determine-the-proper-order-buffer-for-stdout-and-stderror – Jan Doggen Sep 26 '13 at 08:06
  • @HughJones It's not using cmd. – David Heffernan Sep 26 '13 at 08:47
  • @AndreiGalatyn Thanks for the hint :) Actually I had seen that post (I even linked to it in my original question), and I *thought* I had read it carefully enough. But it turns out there is a subtle difference between the two code snippets in Uwe Raabe's answer: in the first, WaitForSingleObject is called before ReadFile, in the second, it is the other way around (as in my code). So I swapped these around in my code, and now it works on both Windows XP and Windows 7! – Tim Sep 26 '13 at 08:58
  • @JanDoggen Interesting link :) I've got my code working now (as mentioned above), but thanks for the help. – Tim Sep 26 '13 at 09:03
  • 2
    If you wait for the program to finish before reading the pipe, you can get deadlock. It's better to read the output as it becomes available. In that respect your code from Delphi Dabbler is better. Investigate more into why the program appears to hang. Use a debugger. – Rob Kennedy Sep 26 '13 at 10:12
  • 1
    Come on, this smelly piece of code originates from the about.com and has been crititized and reviewed many times. – Free Consulting Sep 26 '13 at 12:25
  • @Free, this code appears to come from Delphi Dabbler; see the linked answer by Uwe to compare the two. Could you give some links to critiques of *this* code? – Rob Kennedy Sep 26 '13 at 13:06
  • 2
    @RobKennedy, you are right, precisely this one appears to be one of numerous derivatives (not 100% the same as DelphiDabler's snippet too). Anyway, I'd suggest to stop flogging a dead horse and move onto JCL helper (or corresponding JVCL component). – Free Consulting Sep 26 '13 at 14:31
  • @RobKennedy Ok, thanks for the warning about potential deadlocks. – Tim Sep 27 '13 at 01:33
  • @FreeConsulting Thanks, I didn't know that you could do this with JCL. So I can just use TJclCommandLineTool, and that should work on all versions of Windows without the risk of deadlocks as mentioned in Rob Kennedy's post? – Tim Sep 27 '13 at 02:03
  • 1
    It is indeed much more reliable than code snippet from the internet. – Free Consulting Sep 27 '13 at 04:32
  • @FreeConsulting, that comment (slightly expanded) should really be an answer. – Johan Sep 27 '13 at 04:49
  • this solution freezes in case if the command being executed doesn't end. Better solution described here: https://stackoverflow.com/questions/25723807/execute-dos-program-and-get-output-dynamically – Alexeev Valeriy Mar 22 '21 at 19:38

1 Answers1

7

There is a really useful unit in freepascal called "process", which does just that, and, work has been done to port it to Delphi so you can capture the output of a command in Delphi using a simple one liner:

RunCommand()

Or you can capture the output of the command with more advanced features by creating a TProcess object yourself (which RunCommand just wraps).

The project is here:

How to capture the output of a command, i.e. "dir" (list directory contents, famous MS DOS command) into a string then add it to a memo:

uses 
  dprocess; 
// ...
var 
  output: ansistring;
begin
  RunCommand('cmd', ['/c', 'dir'], output, [poNoConsole]);
  memo1.Lines.Add(output);
end;
Another Prog
  • 841
  • 13
  • 19