【问题标题】:Execute DOS program and get output dynamically执行DOS程序并动态获取输出
【发布时间】:2014-11-01 15:30:24
【问题描述】:

我需要执行一个 'DOS' 程序(控制台应用程序)并动态检索其输出(能够随时结束 DOS 程序也很好,因为 DOS 程序可能会运行数小时)。

我有这个功能,但有时(很少)会死机。 我需要一个新功能或修复以下功能。

procedure ExecuteAndGetOutDyn(CONST ACommand, AParameters: String; AMemo: TMemo);
CONST
  CReadBuffer = 128*KB;  //original was 2400bytes
VAR
  SecurityAttrib: TSecurityAttributes;
  hRead: THandle;
  hWrite: THandle;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  pBuffer: array[0..CReadBuffer] of AnsiChar;
  dRead: DWord;
  dRunning: DWord;
  WasOK: Boolean;
begin
  SecurityAttrib.nLength := SizeOf(TSecurityAttributes);
  SecurityAttrib.bInheritHandle := True;
  SecurityAttrib.lpSecurityDescriptor := nil;

  if CreatePipe(hRead, hWrite, @SecurityAttrib, 0) then
   begin
    FillChar(StartupInfo, SizeOf(TStartupInfo), #0);
    StartupInfo.cb         := SizeOf(TStartupInfo);
    StartupInfo.hStdInput  := hRead;
    StartupInfo.hStdOutput := hWrite;
    StartupInfo.hStdError  := hWrite;
    StartupInfo.dwFlags    := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
    StartupInfo.wShowWindow:= SW_HIDE;

    if CreateProcess(NIL, PChar(ACommand + ' ' + AParameters), @SecurityAttrib, @SecurityAttrib, True, NORMAL_PRIORITY_CLASS, NIL, NIL, StartupInfo, ProcessInfo) then
     begin
      REPEAT
        dRunning:= WaitForSingleObject(ProcessInfo.hProcess, 100);
        Application.ProcessMessages;
        REPEAT
          dRead := 0;
          WasOK := Windows.ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, NIL);
          if NOT WasOK then mesajerror('Cannot read console output.');
          pBuffer[dRead] := #0;

          OemToAnsi(pBuffer, (pBuffer));
          AMemo.Lines.Add(String(pBuffer));
        UNTIL (dRead < CReadBuffer) OR NOT WasOK;
      UNTIL (dRunning <> WAIT_TIMEOUT) { OR Abort};
      CloseHandle(ProcessInfo.hProcess);
      CloseHandle(ProcessInfo.hThread);
     end;

    CloseHandle(hRead);
    CloseHandle(hWrite);
   end;
end;

最大的问题是程序冻结没有特定的条件。我只是调用 ExecuteAndGetOutDyn,有时它会在“DOS”程序完成后冻结。我一发现就会发布出现冻结的条件。

【问题讨论】:

  • 您的代码看起来基本正确。我就是这样做的。您的错误检查有点不稳定。 WaitForSingleObject 可能会失败,在这种情况下,您可能不应该再阅读了。您不检查 ReadFile 的返回值,这似乎很不谨慎。请注意,管道全错了。程序的标准输出通过管道传送到它的输入中。你永远不想要那个。通常你会有两个管道。管道 1 是从您的流程到孩子输入的通道。管道 2 是从子输出返回到您的进程的通道。
  • 我不愿意在没有 MCVE 的情况下尝试回答
  • 还有最后一条评论。您没有执行 DOS 程序。这些是针对控制台子系统(也称为控制台应用程序)的 Windows 可执行文件。
  • 你的调试告诉你什么?代码在哪里冻结?
  • 添加一些诊断。输出调试字符串等

标签: delphi


【解决方案1】:

一个明显的问题是你的管道。您有一个管道,您安排子进程 stdout 写入一端,子进程 stdin 从另一端读取。那不好。为什么你希望进程从它自己的输出中读取它的输入?同时父进程从管道中读取。你有两个进程试图读取这个管道。我无法想象结局会很好。

你需要两个管道。一个用于孩子的标准输入。父母写入它,孩子从中读取。另一个管道用于孩子的标准输出。孩子写,父母读。

或者如果您不希望子进程有任何标准输入,则创建一个管道,将写入端连接到子进程标准输出并让父进程从读取端读取。

另一个问题是,如果进程已经终止,并且您已经阅读了它的所有内容,那么对ReadFile 的调用将无限期地阻塞。在尝试从中读取之前,您需要确保管道包含某些内容。我会为此使用GetFileSizeEx

我个人倾向于在一个线程内完成所有这些操作,以避免调用ProcessMessages

您还应该始终检查 API 返回值是否有错误。这不适用于对WaitForSingleObjectReadFile 的调用。

我提出以下几点建议:

program DynamicStdOutCapture;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Math,
  Winapi.Windows;

function GetFileSizeEx(hFile: THandle; var FileSize: Int64): BOOL; stdcall;
  external kernel32;

procedure Execute(const Command: string; const Parameters: string;
  const Timeout: DWORD; const Output: TProc<string>);

const
  InheritHandleSecurityAttributes: TSecurityAttributes =
    (nLength: SizeOf(TSecurityAttributes); bInheritHandle: True);

var
  hReadStdout, hWriteStdout: THandle;
  si: TStartupInfo;
  pi: TProcessInformation;
  WaitRes, BytesRead: DWORD;
  FileSize: Int64;
  AnsiBuffer: array [0 .. 1024 - 1] of AnsiChar;

begin
  Win32Check(CreatePipe(hReadStdout, hWriteStdout,
    @InheritHandleSecurityAttributes, 0));
  try
    si := Default (TStartupInfo);
    si.cb := SizeOf(TStartupInfo);
    si.dwFlags := STARTF_USESTDHANDLES;
    si.hStdOutput := hWriteStdout;
    si.hStdError := hWriteStdout;
    Win32Check(CreateProcess(nil, PChar(Command + ' ' + Parameters), nil, nil,
      True, CREATE_NO_WINDOW, nil, nil, si, pi));
    try
      while True do
      begin
        WaitRes := WaitForSingleObject(pi.hProcess, Timeout);
        Win32Check(WaitRes <> WAIT_FAILED);
        while True do
        begin
          Win32Check(GetFileSizeEx(hReadStdout, FileSize));
          if FileSize = 0 then
          begin
            break;
          end;
          Win32Check(ReadFile(hReadStdout, AnsiBuffer, SizeOf(AnsiBuffer) - 1,
            BytesRead, nil));
          if BytesRead = 0 then
          begin
            break;
          end;
          AnsiBuffer[BytesRead] := #0;
          OemToAnsi(AnsiBuffer, AnsiBuffer);
          if Assigned(Output) then
          begin
            Output(string(AnsiBuffer));
          end;
        end;
        if WaitRes = WAIT_OBJECT_0 then
        begin
          break;
        end;
      end;
    finally
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
    end;
  finally
    CloseHandle(hReadStdout);
    CloseHandle(hWriteStdout);
  end;
end;

procedure DoOutput(Text: string);
begin
  Write(Text);
end;

procedure Main;
begin
  Execute('ping', 'stackoverflow.com -t', 100, DoOutput);
end;

begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

【讨论】:

  • 对于那些对显示/隐藏 DOS 窗口感兴趣的人的一个小改动:如果 Hide 然后 ProcCreationFlags:= CREATE_NO_WINDOW+NORMAL_PRIORITY_CLASS else ProcCreationFlags:= CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS;
  • 这是唯一对我有用的解决方案,可以防止应用程序在执行的命令未结束时冻结(例如 bat 文件中的“暂停”命令)。
猜你喜欢
  • 1970-01-01
  • 2012-02-25
  • 2019-11-09
  • 2017-08-15
  • 1970-01-01
  • 1970-01-01
  • 2012-08-30
  • 1970-01-01
  • 2023-03-21
相关资源
最近更新 更多