我正在使用管道将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 dir c:\
一切顺利。但如果我打电话说:
powershell dir c:\ 或 cmd.exe /C powershell dir c:\
WaitForSingleObject达到超时,什么都不会发生。在这个问题上有帮助吗?
发布于 2016-07-23 01:38:11
管道的缓冲器可能已经满了。子进程被阻塞,等待从管道读取进程并为更多输出腾出空间。但是,您的程序也被阻塞,等待子进程完成。因此,陷入僵局。
您需要继续读取管道,但问题是,如果调用ReadFile而进程挂起的原因不是完全管道缓冲区,那么您的程序也会挂起。ReadFile不提供超时参数。
ReadFile没有超时参数,因为异步读取是使用重叠I/O完成的。您可以将包含一个ReadFile事件句柄的TOverlapped记录传递给ReadFile。ReadFile将立即返回,并在读取完成后向事件发出信号。使用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,如果调用时已有可用的数据,则可能会这样做)。
https://stackoverflow.com/questions/38531839
复制相似问题