Getting output from a command-line program in a De

2020-07-28 00:24发布

问题:

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?

回答1:

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:

  • https://github.com/z505/TProcess-Delphi
  • A simple demo: https://github.com/z505/TProcess-Delphi/tree/master/demo-simple

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;