Delphi XE8:运行外部控制台应用程序、等待其结果和捕获其结果时出现问题

Delphi XE8:运行外部控制台应用程序、等待其结果和捕获其结果时出现问题,delphi,pipe,console-application,createprocess,waitforsingleobject,Delphi,Pipe,Console Application,Createprocess,Waitforsingleobject,在Windows下的DelphiXe8中,我试图调用外部控制台应用程序并捕获其输出。我使用以下代码,如和中所述: procedure TForm1.按钮1点击(发送方:TObject); 过程RunDosInMemo(DosApp:String;AMemo:TMemo); 常数 ReadBuffer=2400; 变量 安全性:t安全属性; ReadPipe,WritePipe:THandle; 开始:TStartUpInfo; ProcessInfo:TProcessInformation; 缓

在Windows下的DelphiXe8中,我试图调用外部控制台应用程序并捕获其输出。我使用以下代码,如和中所述:

procedure TForm1.按钮1点击(发送方:TObject);
过程RunDosInMemo(DosApp:String;AMemo:TMemo);
常数
ReadBuffer=2400;
变量
安全性:t安全属性;
ReadPipe,WritePipe:THandle;
开始:TStartUpInfo;
ProcessInfo:TProcessInformation;
缓冲区:Pchar;
拜特斯拉德:德沃德;
通知:德沃德;
S:字符串;
开始
从安全开始
nlength:=SizeOf(TSecurityAttributes);
binherithandle:=真;
lpsecuritydescriptor:=零;
结束;
如果Createpipe(ReadPipe、WritePipe、,
@安全,0)那么
开始
缓冲区:=AllocMem(ReadBuffer+1);
FillChar(开始,Sizeof(开始),#0);
start.cb:=SizeOf(start);
start.hStdOutput:=writepe;
start.hStdInput:=读取管道;
start.dwFlags:=STARTF_USESTDHANDLES+STARTF_USESHOWWINDOW;
start.wShowWindow:=SW_HIDE;
S:=唯一字符串(DosApp);
如果CreateProcess(无,
PChar(S),
@保安,,
@保安,,
是的,
正常优先级等级,
无
无
开始
ProcessInfo)然后
开始
重复
Apprunning:=WaitForSingleObject
(ProcessInfo.hProcess,100);
Application.ProcessMessages;
直到(通知等待超时);
重复
字节读取:=0;
ReadFile(ReadPipe,Buffer[0],ReadBuffer,BytesRead,nil);
缓冲区[字节读取]:=#0;
OemToAnsi(缓冲区,缓冲区);
AMemo.Text:=AMemo.Text+字符串(缓冲区);
直到(字节读取<读取缓冲区);
结束;
FreeMem(缓冲区);
CloseHandle(ProcessInfo.hproces);
CloseHandle(ProcessInfo.hThread);
关闭手柄(读取管);
关闭手柄(WritePie);
结束;
结束;
开始{按钮1代码}

rundosinemo('cmd.exe/c dir',备忘录1)// 您正在运行的程序要么期望输入(如来自键盘),要么产生超出管道缓冲区的输出。在任何一种情况下,该程序都会挂起等待进一步的I/O操作,但您的父程序会在处理任何输出之前等待该子程序终止,并且从不提供任何输入

您需要在程序仍在运行时处理输出管道。否则,缓冲区可能会被填满,并且子级将阻塞,直到有更多的空间可用。同样,如果您不打算向另一个进程提供任何输入,则可能不应该为它提供有效的输入句柄。这样,如果它试图读取输入,它将失败,而不是阻塞

此外,您给该程序的输入句柄被附加到输出句柄。如果程序尝试读取,它将读取自己的输出,就像I/O ouroboros一样。要处理输入和输出,需要两个管道


(请注意,此死锁正是问题所在。同一答案中的第二个代码块解决了该问题以及ouroboros问题。)

您正在运行的程序期望输入(如来自键盘)或产生超出管道缓冲区的输出。在任何一种情况下,该程序都会挂起等待进一步的I/O操作,但您的父程序会在处理任何输出之前等待该子程序终止,并且从不提供任何输入

您需要在程序仍在运行时处理输出管道。否则,缓冲区可能会被填满,并且子级将阻塞,直到有更多的空间可用。同样,如果您不打算向另一个进程提供任何输入,则可能不应该为它提供有效的输入句柄。这样,如果它试图读取输入,它将失败,而不是阻塞

此外,您给该程序的输入句柄被附加到输出句柄。如果程序尝试读取,它将读取自己的输出,就像I/O ouroboros一样。要处理输入和输出,需要两个管道


(请注意,这个死锁正是问题所在。同一答案中的第二个代码块解决了这个问题,以及ouroboros问题。)

总结一下:@David Heffernan的代码正在运行。问题是控制台应用程序发出UTF-16

总结一下:@David Heffernan的代码在工作中。问题是控制台应用程序发出UTF-16

坦率地说,令人惊讶的是,这个程序能找到多少不同的变体。我想我的代码是有效的:大卫,谢谢你的帮助。我试过你的代码,它适用于几个控制台应用程序,除了我需要使用的那个。控制台应用程序已执行,文件大小似乎正确,但AnsiBuffer只包含无意义的信息。不幸的是,由于许可和保密原因,我无法命名或上传控制台应用程序。控制台应用程序在Wondows Commad提示符下工作正常。我可以尝试做些什么来进一步分析或解决问题?谢谢,不知道。如果你不能告诉我们关于这个应用程序的情况,这会有点棘手。大卫,我用以下命令尝试了你的代码:='cmd.exe'和参数:='/c d:\ConsoleApp.exe/parameter>c:\test.txt'。我使用重定向符号“>”将输出写入文件。这很有效。(命令处理器cmd.exe似乎是必要的,以便“>”工作。)管道中的重定向一定有问题。如果你有一个私人电子邮件地址,我可以通过电子邮件将控制台应用程序发送给你。也许你可以看看?你真是太好了。如果你没有时间,当然没问题。非常感谢你!不,我不能那样做。很抱歉如果你需要帮助,在这里问一个问题。坦率地说,令人惊讶的是
procedure TForm1.Button1Click(Sender: TObject) ;

  procedure RunDosInMemo(DosApp:String;AMemo:TMemo) ;
  const
    ReadBuffer = 2400;
  var
    Security : TSecurityAttributes;
    ReadPipe,WritePipe : THandle;
    start : TStartUpInfo;
    ProcessInfo : TProcessInformation;
    Buffer : Pchar;
    BytesRead : DWord;
    Apprunning : DWord;
    S: String;
  begin
    With Security do begin
      nlength := SizeOf(TSecurityAttributes) ;
      binherithandle := true;
      lpsecuritydescriptor := nil;
    end;
    if Createpipe (ReadPipe, WritePipe,
                   @Security, 0) then 
    begin
      Buffer := AllocMem(ReadBuffer + 1) ;
      FillChar(Start,Sizeof(Start),#0) ;
      start.cb := SizeOf(start) ;
      start.hStdOutput := WritePipe;
      start.hStdInput := ReadPipe;
      start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
      start.wShowWindow := SW_HIDE;

      S:=UniqueString(DosApp);
      if CreateProcess(nil,
              PChar(S),
              @Security,
              @Security,
              true,
              NORMAL_PRIORITY_CLASS,
              nil,
              nil,
              start,
              ProcessInfo) then
      begin
        repeat
          Apprunning := WaitForSingleObject
                        (ProcessInfo.hProcess,100) ;
          Application.ProcessMessages;
        until (Apprunning <> WAIT_TIMEOUT) ;
        Repeat
          BytesRead := 0;
          ReadFile(ReadPipe,Buffer[0], ReadBuffer,BytesRead,nil) ;
          Buffer[BytesRead]:= #0;
          OemToAnsi(Buffer,Buffer) ;
          AMemo.Text := AMemo.text + String(Buffer) ;
        until (BytesRead < ReadBuffer) ;
      end;
      FreeMem(Buffer) ;
      CloseHandle(ProcessInfo.hProcess) ;
      CloseHandle(ProcessInfo.hThread) ;
      CloseHandle(ReadPipe) ;
      CloseHandle(WritePipe) ;
    end;
  end;

begin {button 1 code}
  RunDosInMemo('cmd.exe /c dir',Memo1) ; //<-- this works
  RunDosInMemo('"c:\consoleapp.exe" "/parameter"',Memo1) //<-- this hangs in the repeat until (Apprunning <> WAIT_TIMEOUT) ; 
end;