【问题标题】:CreateProcess, PowerShell and WaitForSingleObjectCreateProcess、PowerShell 和 WaitForSingleObject
【发布时间】:2016-07-22 17:01:46
【问题描述】:

我正在使用管道在我的程序中获取 cmd.exe 输出。有时,我注意到如果 cmd.exe 要求用户输入(我创建了隐藏的 cmd 窗口),程序就会挂起,因为没有人会将输入放在窗口中,而 cmd 只会留下来。所以我实现了 WaitForSingleObject 以避免在 cmd 要求用户输入或只是出于其他原因而挂起的情况下挂起。当我尝试执行 powershell 命令时出现问题,因为它看起来对 WaitForSingleObject 没有响应,而且我总是达到超时。功能是:

function GetDosOutput(const Exe, Param: string): string;
const
  InheritHandleSecurityAttributes: TSecurityAttributes =
    (nLength: SizeOf(TSecurityAttributes); bInheritHandle: True);
var
  hReadStdout, hWriteStdout: THandle;
  si: TStartupInfo;
  pi: TProcessInformation;
  WaitTimeout, BytesRead: DWord;
  lReadFile: boolean;
  Buffer: array[0..255] of AnsiChar;
begin
  Result:= '';
  if CreatePipe(hReadStdout, hWriteStdout, @InheritHandleSecurityAttributes, 0) then
  begin
    try
      si:= Default(TStartupInfo);
      si.cb:= SizeOf(TStartupInfo);
      si.dwFlags:= STARTF_USESTDHANDLES;
      si.hStdOutput:= hWriteStdout;
      si.hStdError:= hWriteStdout;
      if CreateProcess(Nil, PChar(Exe + ' ' + Param), Nil, Nil, True, CREATE_NO_WINDOW,
                        Nil, PChar(ExtractFilePath(ParamStr(0))), si, pi) then
      begin
        CloseHandle(hWriteStdout);
        while True do
        begin
          try
            WaitTimeout:= WaitForSingleObject(pi.hProcess, 20000);
            if WaitTimeout = WAIT_TIMEOUT then
            begin
              Result:= 'No result available';
              break;
            end
            else
            begin
              repeat
                lReadFile:= ReadFile(hReadStdout, Buffer, SizeOf(Buffer) - 1, BytesRead, nil);
                if BytesRead > 0 then
                begin
                  Buffer[BytesRead]:= #0;
                  OemToAnsi(Buffer, Buffer);
                  Result:= Result + String(Buffer);
                end;
              until not (lReadFile) or (BytesRead = 0);
            end;
            if WaitTimeout = WAIT_OBJECT_0 then
              break;
          finally
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
          end;
        end;
      end;
    finally
      CloseHandle(hReadStdout);
    end;
  end;
end;

如果我调用这个函数传递:

cmd.exe /C 目录 c:\

一切顺利。但如果我打电话使用:

powershell dir c:\ cmd.exe /C powershell dir c:\

WaitForSingleObject 达到超时,没有任何反应。对这个有帮助吗?

【问题讨论】:

  • Rob 的回答应该是准确的阻止原因。不过,我想不出任何方法来等待管道。可能你需要改变你的设计。也许在一个线程中阅读。或者也许在一个线程中等待。或者制作一个等待用户输入挂起的 mcve,以便我们重现问题。

标签: delphi


【解决方案1】:

管道的缓冲区可能已满。子进程被阻塞,等待您的进程从管道中读取数据并为更多输出腾出空间。但是,您的程序也被阻塞,等待子进程完成。因此,死锁。

您需要继续从管道读取,但问题是如果您调用ReadFile 并且进程由于其他原因而不是完整的管道缓冲区而挂起,那么您的程序也会挂起。 ReadFile 不提供超时参数。

ReadFile 没有超时参数,因为异步读取是使用重叠 I/O 完成的。您将包含 Windows 事件句柄的 TOverlapped 记录传递给 ReadFileReadFile 将立即返回,并在读取完成时发出事件信号。使用WaitForMultipleObjects 不仅可以等待进程句柄,还可以等待这个新的事件句柄。

不过,有一个障碍。 CreatePipe 创建匿名 管道,匿名管道不支持重叠 I/O。因此,您必须改用CreateNamedPipe。在运行时为管道生成一个唯一的名称,这样它就不会干扰任何其他程序(包括您的程序的其他实例)。

下面是代码如何运行的草图:

var
  Overlap: TOverlapped;
  WaitHandles: array[0..1] of THandle;
begin
  hReadStdout := CreateNamedPipe('\\.\pipe\unique-pipe-name-here',
    Pipe_Access_Inbound, File_Flag_First_Pipe_Instance or File_Flag_Overlapped,
    Pipe_Type_Byte or Pipe_Readmode_Byte, 1, x, y, 0, nil);
  Win32Check(hReadStdout <> Invalid_Handle_Value);
  try
    hWriteStdout := CreateFile('\\.\pipe\unique-pipe-name-here', Generic_Write,
      @InheritHandleSecurityAttributes, ...);
    Win32Check(hWriteStdout <> Invalid_Handle_Value);
    try
      si.hStdOutput := hWriteStdout;
      si.hStdError := hWriteStdout;
      Win32Check(CreateProcess(...));
    finally
      CloseHandle(hWriteStdout);
    end;
    try
      Overlap := Default(TOverlapped);
      Overlap.hEvent := CreateEvent(nil, True, False, nil);
      Win32Check(Overlap.hEvent <> 0);
      try
        WaitHandles[0] := Overlap.hEvent;
        WaitHandles[1] := pi.hProcess;
        repeat
          ReadResult := ReadFile(hReadStdout, ..., @Overlap);
          if ReadResult then begin
            // We read some data without waiting. Process it and go around again.
            SetString(NewResult, Buffer, BytesRead div SizeOf(Char));
            Result := Result + NewResult;
            continue;
          end;
          Win32Check(GetLastError = Error_IO_Pending);
          // We're reading asynchronously.
          WaitResult := WaitForMultipleObjects(Length(WaitHandles),
            @WaitHandles[0], False, 20000);
          case WaitResult of
            Wait_Object_0: begin
              // Something happened with the pipe.
              ReadResult := GetOverlappedResult(hReadStdout, @Overlap, @BytesRead, True);
              // May need to check for EOF or broken pipe here.
              Win32Check(ReadResult);
              SetString(NewResult, Buffer, BytesRead div SizeOf(Char));
              Result := Result + NewBuffer;
              ResetEvent(Overlap.hEvent);
            end;
            Wait_Object_0 + 1: begin
              // The process terminated. Cancel the I/O request and move on,
              // returning any data already in Result. (There's no further data
              // in the pipe, because if there were, WaitForMultipleObjects would
              // have returned Wait_Object_0 instead. The first signaled handle
              // determines the return value.
              CancelIO(hReadStdout);
              break;
            end;
            Wait_Timeout: begin
              // Timeout elapsed without receiving any more data.
              Result := 'no result available';
              break;
            end;
            Wait_Failed: Win32Check(False);
            else Assert(False);
          end;
        until False;
      finally
        CloseHandle(Overlap.hEvent);
      end;
    finally
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
    end;
  finally
    CloseHandle(hReadStdout);
  end;
end;

请注意,在上面的代码中,程序的任何新输出都会重置您分配给进程完成的 20 秒超时。这可能是可接受的行为,但如果不是,那么您必须跟踪已经过去了多少时间并在调用 WaitForMultipleObjects 之前调整超时值(也可能在调用 ReadFile 之前,以防万一操作系统选择处理 ReadFile 非重叠,如果您调用它时已经有可用数据,它可能会这样做)。

【讨论】:

  • 我会努力实现你所说的。
  • 如何使用 WaitForMultipleObjects 等待管道?
  • 嗯,通过使用假设的未来版本的 Windows,当然可以使用WaitForMultipleObjects 等待管道句柄!我确信管道句柄可以像其他句柄一样发出信号,@Sertac。顺便说一句,我想不出替代方案。我得再考虑一下。感谢您指出。
  • 我想我想出了一个解决方案,@Sertac。
  • 这看起来很酷,谢谢。另外感谢“以防操作系统选择处理非重叠”。我必须检查一些代码...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-01
  • 1970-01-01
  • 2021-12-13
相关资源
最近更新 更多