2016-07-22 13 views
1

Ich arbeite mit Rohrleitungen, um die cmd.exe Ausgabe in meinem Programm zu bekommen. Manchmal habe ich bemerkt, dass, wenn die cmd.exe nach Benutzereingaben fragt (ich erstelle ein verstecktes cmd-Fenster), das Programm hängt, weil niemand die Eingabe in das Fenster legt, und der cmd bleibt einfach. Also habe ich WaitForSingleObject implementiert, um zu verhindern, dass die Fälle, in denen cmd nach Benutzereingaben fragt oder sich einfach aus einem anderen Grund aufhängt, hängen bleiben. Das Problem tritt auf, wenn ich versuche, Powershell-Befehle auszuführen, weil es für WaitForSingleObject nicht mehr reagiert und ich immer das Timeout erreiche. Die Funktion ist:CreateProcess, PowerShell und 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; 

Wenn ich diese Funktion übergeben nennen:

cmd.exe/C dir c: \

Es geht in Ordnung. Aber wenn ich rufe mit:

Power dir c: \ oder cmd.exe/C Power dir c: \

Die WaitForSingleObject erreicht das Timeout, und nichts passiert. Irgendwelche Hilfe bei diesem?

+0

Robs Antwort sollte über den Blockgrund genau sein. Ich kann mir aber keine Möglichkeit vorstellen, auf eine Pfeife zu warten.Wahrscheinlich müssen Sie Ihr Design ändern. Vielleicht einen Thread einlesen. Oder warte vielleicht in einem Thread. Oder machen Sie ein mcve, bei dem das Warten auf Benutzereingaben aufhört, damit wir das Problem reproduzieren können. –

Antwort

2

Der Puffer der Pipe ist wahrscheinlich voll. Der untergeordnete Prozess ist blockiert und wartet darauf, dass Ihr Prozess aus der Pipe liest und Platz für weitere Ausgaben schafft. Ihr Programm wird jedoch auch blockiert und wartet darauf, dass der untergeordnete Prozess abgeschlossen wird. Also, Deadlock.

Sie müssen weiterhin aus der Pipe lesen, aber das Problem ist, dass, wenn Sie ReadFile aufrufen und der Prozess hängt aus einem anderen Grund als ein voller Pipepuffer, dann hängt Ihr Programm auch. ReadFile bietet keinen Timeout-Parameter.

ReadFile hat keinen Timeout-Parameter, da asynchrone Lesevorgänge stattdessen unter Verwendung von überlappten E/A ausgeführt werden. Sie übergeben an ReadFile einen TOverlapped-Datensatz, der ein Windows-Ereignishandle enthält. ReadFile wird sofort zurückgegeben, und es wird das Ereignis signalisieren, wenn der Lesevorgang abgeschlossen ist. Verwenden Sie WaitForMultipleObjects, um nicht nur auf den Prozesshandle, sondern auch auf diesen neuen Ereignishandle zu warten.

Es gibt einen Haken, obwohl. CreatePipe erstellt anonyme Pipes und anonyme Pipes unterstützen keine überlappenden E/A. Daher müssen Sie stattdessen CreateNamedPipe verwenden. Erstellen Sie einen eindeutigen Namen für die Pipe zur Laufzeit, damit sie keine anderen Programme beeinträchtigt (einschließlich zusätzlicher Instanzen von Ihr Programm).

Hier ist eine Skizze, wie der Code gehen könnte:

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; 

Beachten Sie, dass in dem obigen Code, jede neue Ausgabe aus dem Programm wird im Wesentlichen das Timeout von 20 Sekunden Reset Sie den Prozess zugeteilt zu beenden. Das kann ein akzeptables Verhalten sein, aber wenn nicht, dann müssen Sie nachverfolgen, wie viel Zeit bereits vergangen ist, und den Zeitüberschreitungswert vor dem Aufruf von WaitForMultipleObjects einstellen (und vielleicht auch vor dem Aufruf von ReadFile, falls das Betriebssystem sich dazu entschließt) Handle ReadFile nicht überlappt, was es tun könnte, wenn bereits Daten verfügbar sind, wenn Sie es aufrufen).

+0

Ich werde versuchen zu implementieren, was Sie gesagt haben. – user2864778

+0

Warten auf die Pipe mit WaitForMultipleObjects? –

+0

Um, mit der hypothetischen zukünftigen Version von Windows, wo Pipe-Handles natürlich mit 'WaitForMultipleObjects' gewartet werden können! Ich dachte sicher, dass Rohrgriffe wie andere Griffe signalisiert werden können, @Sertac. Auf der anderen Seite kann ich mir keine Alternative vorstellen. Ich muss darüber mehr nachdenken. Danke, dass du es aufgezeigt hast. –