【问题标题】:Getting output from a shell/dos app into a Delphi app从 shell/dos 应用程序获取输出到 Delphi 应用程序
【发布时间】:2012-02-25 13:26:43
【问题描述】:

我有一个用 delphi 编码的命令行应用程序,我需要从普通桌面应用程序(也用 delphi 编码)调用它。简而言之,我想调用命令行应用程序并在列表框中显示它“实时”输出的文本。

自从我玩过 shell 已经很久了,但我清楚地记得,为了从命令行应用程序中获取文本 - 我必须使用管道符号“>”。像这样:

C:/mycmdapp.exe >c:/result.txt

这会将打印到 shell 的任何文本(使用 writeLn)转储到一个名为“result.txt”的文本文件中。

但是..(泡菜来了),我想要一个实时结果而不是积压文件。一个典型的例子是 Delphi 编译器本身 - 它设法向 IDE 报告正在发生的事情。如果我没记错的话,我似乎记得我必须创建一个“管道”通道(?),然后将管道名称分配给 shell 调用。

我试图用谷歌搜索这个,但老实说我不确定如何制定它。希望社区中的某个人可以为我指明正确的方向。

更新:这个问题可能与How do I run a command-line program in Delphi? 相同。尽管标题和问题本身并不相同,但其中一些答案符合我的要求。

【问题讨论】:

  • Console Application Runner Classes - 看起来很有希望
  • 我正在使用 Delphi XE2(尽管我拥有 Delphi 4 及更高版本的大多数 Delphi 版本)。 Delphi 2010+ 与 XE 和 XE2 二进制兼容,这难道不是之前的大新闻之一吗?在这种情况下,这些类应该可以工作。非常感谢您的反馈!
  • @Jon:不,这是不正确的。 Delphi 编译器版本之间没有二进制兼容性(唯一的例外是在 D6 和 D7 之间)。如果不重新编译源代码,则不能将 D2010 编译的 .dcu 文件与任何其他版本的 Delphi 一起使用。
  • 感谢您的清理!如果我们不问,就不要学习。我本可以发誓我在某个地方读到过——但它可能是错的。感谢您指出:)
  • @KenWhite,不是 D2006 到 D2007 吗?

标签: delphi pipe windows-shell shellexecute


【解决方案1】:

Zarco Gajic 经常有一个解决方案:Capture the output from a DOS (command/console) Window。这是他文章的副本,以供将来参考:

该示例运行“chkdsk.exe c:\”并将输出显示到 Memo1。 在表单上放置一个 TMemo (Memo1) 和一个 TButton (Button1)。将此代码放在Button1OnCLick 事件过程中:

procedure RunDosInMemo(DosApp: string; AMemo:TMemo);
const
    READ_BUFFER_SIZE = 2400;
var
    Security: TSecurityAttributes;
    readableEndOfPipe, writeableEndOfPipe: THandle;
    start: TStartUpInfo;
    ProcessInfo: TProcessInformation;
    Buffer: PAnsiChar;
    BytesRead: DWORD;
    AppRunning: DWORD;
begin
    Security.nLength := SizeOf(TSecurityAttributes);
    Security.bInheritHandle := True;
    Security.lpSecurityDescriptor := nil;

    if CreatePipe({var}readableEndOfPipe, {var}writeableEndOfPipe, @Security, 0) then
    begin
        Buffer := AllocMem(READ_BUFFER_SIZE+1);
        FillChar(Start, Sizeof(Start), #0);
        start.cb := SizeOf(start);

        // Set up members of the STARTUPINFO structure.
        // This structure specifies the STDIN and STDOUT handles for redirection.
        // - Redirect the output and error to the writeable end of our pipe.
        // - We must still supply a valid StdInput handle (because we used STARTF_USESTDHANDLES to swear that all three handles will be valid)
        start.dwFlags := start.dwFlags or STARTF_USESTDHANDLES;
        start.hStdInput := GetStdHandle(STD_INPUT_HANDLE); //we're not redirecting stdInput; but we still have to give it a valid handle
        start.hStdOutput := writeableEndOfPipe; //we give the writeable end of the pipe to the child process; we read from the readable end
        start.hStdError := writeableEndOfPipe;

        //We can also choose to say that the wShowWindow member contains a value.
        //In our case we want to force the console window to be hidden.
        start.dwFlags := start.dwFlags + STARTF_USESHOWWINDOW;
        start.wShowWindow := SW_HIDE;

        // Don't forget to set up members of the PROCESS_INFORMATION structure.
        ProcessInfo := Default(TProcessInformation);

        //WARNING: The unicode version of CreateProcess (CreateProcessW) can modify the command-line "DosApp" string. 
        //Therefore "DosApp" cannot be a pointer to read-only memory, or an ACCESS_VIOLATION will occur.
        //We can ensure it's not read-only with the RTL function: UniqueString
        UniqueString({var}DosApp);

        if CreateProcess(nil, PChar(DosApp), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, start, {var}ProcessInfo) then
        begin
            //Wait for the application to terminate, as it writes it's output to the pipe.
            //WARNING: If the console app outputs more than 2400 bytes (ReadBuffer),
            //it will block on writing to the pipe and *never* close.
            repeat
                Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
                Application.ProcessMessages;
            until (Apprunning <> WAIT_TIMEOUT);

            //Read the contents of the pipe out of the readable end
            //WARNING: if the console app never writes anything to the StdOutput, then ReadFile will block and never return
            repeat
                BytesRead := 0;
                ReadFile(readableEndOfPipe, Buffer[0], READ_BUFFER_SIZE, {var}BytesRead, nil);
                Buffer[BytesRead]:= #0;
                OemToAnsi(Buffer,Buffer);
                AMemo.Text := AMemo.text + String(Buffer);
            until (BytesRead < READ_BUFFER_SIZE);
        end;
        FreeMem(Buffer);
        CloseHandle(ProcessInfo.hProcess);
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(readableEndOfPipe);
        CloseHandle(writeableEndOfPipe);
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin {button 1 code}
   RunDosInMemo('chkdsk.exe c:\',Memo1);
end;

更新: 上面的示例一步读取输出。这是来自DelphiDabbler 的另一个示例,展示了如何在进程仍在运行时读取输出:

function GetDosOutput(CommandLine: string; Work: string = 'C:\'): string;
var
  SA: TSecurityAttributes;
  SI: TStartupInfo;
  PI: TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite: THandle;
  WasOK: Boolean;
  Buffer: array[0..255] of AnsiChar;
  BytesRead: Cardinal;
  WorkDir: string;
  Handle: Boolean;
begin
  Result := '';
  with SA do begin
    nLength := SizeOf(SA);
    bInheritHandle := True;
    lpSecurityDescriptor := nil;
  end;
  CreatePipe(StdOutPipeRead, StdOutPipeWrite, @SA, 0);
  try
    with SI do
    begin
      FillChar(SI, SizeOf(SI), 0);
      cb := SizeOf(SI);
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      wShowWindow := SW_HIDE;
      hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
      hStdOutput := StdOutPipeWrite;
      hStdError := StdOutPipeWrite;
    end;
    WorkDir := Work;
    Handle := CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine),
                            nil, nil, True, 0, nil,
                            PChar(WorkDir), SI, PI);
    CloseHandle(StdOutPipeWrite);
    if Handle then
      try
        repeat
          WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
          if BytesRead > 0 then
          begin
            Buffer[BytesRead] := #0;
            Result := Result + Buffer;
          end;
        until not WasOK or (BytesRead = 0);
        WaitForSingleObject(PI.hProcess, INFINITE);
      finally
        CloseHandle(PI.hThread);
        CloseHandle(PI.hProcess);
      end;
  finally
    CloseHandle(StdOutPipeRead);
  end;
end;

【讨论】:

  • 太完美了!太感谢了!我没想到去看看 delphi.about.com,太好了!
  • @JonLennartAasenden,我添加了第二个示例,展示了更好的方法。
  • 上面的代码在使用Unicode版本的CreateProcess时会出现问题:这个函数的Unicode版本CreateProcessW可以修改这个字符串的内容。因此,此参数不能是指向只读内存的指针(例如 const 变量或文字字符串)。如果这个参数是一个常量字符串,该函数可能会导致访问冲突(msdn.microsoft.com/en-us/library/windows/desktop/…
  • 当程序产生的输出超出管道缓冲区的容量时,Zarco 的代码不会导致死锁吗?
  • Holy Intersimone,对于一个应该是单功能的操作来说,这是很多复杂的代码。 10 个变量、填充缓冲区、管道、句柄、标志…… Delphi 在这方面确实需要赶上 Free Pascal。在最新版本中将命令的输出放在字符串“s”中只是“RunCommand('/bin/bash',['-c','echo $PATH'],s)”,它是跨平台的也是。
【解决方案2】:

您的硬盘上可能已经有代码:JCL(JEDI 代码库)的 JclSysUtils 单元中的 Execute 函数可以满足您的需要:

function Execute(const CommandLine: string; OutputLineCallback: TTextHandler; 
  RawOutput: Boolean = False; AbortPtr: PBoolean = nil): Cardinal;

您可以为其提供回调过程:
TTextHandler = procedure(const Text: string) of object;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 2013-10-01
    • 2017-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多