2016-07-22 2 views
1

내 프로그램 내에서 cmd.exe 출력을 가져 오기 위해 파이프로 작업하고 있습니다. 때로는 cmd.exe가 사용자 입력을 요청하면 (내가 숨겨진 cmd 창을 만들 때) 아무도 창이 입력을 저장하지 않고 cmd가 그대로 남아 있기 때문에 프로그램이 중단됩니다. 그래서 WaitForSingleObject를 구현하여 cmd가 사용자 입력을 요청하거나 다른 이유로 중지하는 경우를 방지합니다. WaitForSingleObject에 대해 반응이 없기 때문에 powershell 명령을 실행하려고하면 문제가 발생하며 항상 시간 초과에 도달합니다. 이 기능은 다음과 같습니다CreateProcess, 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 DIR C : \

그것은 확실히 이동합니다.

파워 쉘의 디렉토리에 c : 내가 사용하는 전화하지만 또는 cmd.exe를 \/C PowerShell을 해줄 c :를

\ WaitForSingleObject이는 시간 제한에 도달하고, 아무 일도 발생하지 않습니다. 이거 도와 줘?

+0

롭의 대답은 블록 이유에 대해 정확해야합니다. 나는 파이프를 기다릴 어떤 수단도 생각할 수 없다.아마도 디자인을 변경해야 할 것입니다. 스레드에서 아마도 읽습니다. 또는 스레드에서 기다릴 수도 있습니다. 또는 사용자 입력을 기다리는 mcve를 만들면 문제를 재현 할 수 있습니다. –

답변

2

파이프의 버퍼가 가득 찼을 수 있습니다. 하위 프로세스가 차단되어 프로세스가 파이프에서 읽기를 기다리고 더 많은 출력을위한 공간을 확보합니다. 그러나 프로그램이 차단되어 하위 프로세스가 완료되기를 기다립니다. 따라서 교착 상태.

파이프에서 계속 읽어야하지만 문제는 ReadFile을 호출하고 프로세스가 전체 파이프 버퍼가 아닌 다른 이유로 중지되면 프로그램도 중지된다는 것입니다. ReadFile은 시간 초과 매개 변수를 제공하지 않습니다.

ReadFile 비동기 읽기가 중복 I/O을 대신 사용하기 때문에 시간 초과 매개 변수가 없습니다. ReadFile에 Windows 이벤트 핸들이 포함 된 TOverlapped 레코드를 전달합니다. ReadFile은 즉시 반환되며 읽기가 완료되면 이벤트를 알립니다. WaitForMultipleObjects을 사용하여 프로세스 핸들뿐만 아니라이 새 이벤트 핸들도 기다립니다.

하지만 걸림돌이 있습니다. CreatePipe익명의 파이프를 만들고 익명 파이프는 겹친 I/O를 지원하지 않습니다. 따라서 대신 CreateNamedPipe을 사용해야합니다. 실행시 파이프의 고유 한 이름을 생성하여 다른 프로그램 (예 : 프로그램의 추가 인스턴스 포함)을 방해하지 않도록하십시오. 위의 코드에서, 프로그램에서 새로운 출력이 기본적으로 프로세스가 완료 될 때까지 당신이 할당 된 20 초 제한 시간을 다시 것

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; 

참고 :

다음은 코드가 갈 수있는 방법의 스케치입니다. 허용되는 동작 일 수도 있지만 그렇지 않다면 WaitForMultipleObjects을 호출하기 전에 이미 경과 한 시간을 추적하고 시간 제한 값을 조정해야합니다 (그리고 아마도 ReadFile을 호출하기 전에 OS가 선택하는 경우를 대비하여). ReadFile을 겹치지 않게 처리하십시오. 호출 할 때 이미 데이터가있는 경우 처리 할 수 ​​있습니다.

+0

나는 당신이 말한 것을 구현하려고 할 것입니다. – user2864778

+0

WaitForMultipleObjects를 사용하여 파이프를 대기하는 방법은 무엇입니까? –

+0

음, 파이프 핸들이 WaitForMultipleObjects로 대기 할 수있는 Windows의 가상 버전을 사용하면 물론! 나는 파이프 핸들이 다른 핸들처럼 신호 될 수 있다고 생각했다. @Sertac. 손을 떼면 나는 대안을 생각할 수 없다. 나는 이것에 대해 좀 더 생각해야 할 것이다. 그것을 지적 주셔서 감사합니다. –

관련 문제