在Delphi中启动两个进程并用管道连接它们

6
我需要在我的程序中启动两个外部程序,并将第一个程序的STDOUT连接到第二个程序的STDIN。在Windows环境下,如何用Delphi实现呢?我使用的是RAD Studio 2009。作为命令行命令,我的情况如下:
dumpdata.exe | encrypt.exe "mydata.dat"
4个回答

9

一个似乎有效的快速测试(受到JCL的启发):

child1:向标准输出打印3次“Hello, world!”

program child1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

procedure Main;
var
  I: Integer;
begin
  for I := 0 to 2 do
    Writeln('Hello, world!');
  Write(^Z);
end;

begin
  try
    Main;
  except
    on E: Exception do
    begin
      ExitCode := 1;
      Writeln(ErrOutput, Format('[%s] %s', [E.ClassName, E.Message]));
    end;
  end;
end.

child2: 将标准输入的任何内容回显到OutputDebugString(可以通过DebugView查看)

program child2;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils, Classes;

procedure Main;
var
  S: string;
begin
  while not Eof(Input) do
  begin
    Readln(S);
    if S <> '' then
      OutputDebugString(PChar(S));
  end;
end;

begin
  try
    Main;
  except
    on E: Exception do
    begin
      ExitCode := 1;
      Writeln(ErrOutput, Format('[%s] %s', [E.ClassName, E.Message]));
    end;
  end;
end.

父级:启动子级1重定向到子级2

program parent;

{$APPTYPE CONSOLE}

uses
  Windows, Classes, SysUtils;

procedure ExecutePiped(const CommandLine1, CommandLine2: string);
var
  StartupInfo1, StartupInfo2: TStartupInfo;
  ProcessInfo1, ProcessInfo2: TProcessInformation;
  SecurityAttr: TSecurityAttributes;
  PipeRead, PipeWrite: THandle;
begin
  PipeWrite := 0;
  PipeRead := 0;
  try
    SecurityAttr.nLength := SizeOf(SecurityAttr);
    SecurityAttr.lpSecurityDescriptor := nil;
    SecurityAttr.bInheritHandle := True;
    Win32Check(CreatePipe(PipeRead, PipeWrite, @SecurityAttr, 0));

    FillChar(StartupInfo1, SizeOf(TStartupInfo), 0);
    StartupInfo1.cb := SizeOf(TStartupInfo);
    StartupInfo1.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    StartupInfo1.wShowWindow := SW_HIDE;
    StartupInfo1.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
    StartupInfo1.hStdOutput := PipeWrite;
    StartupInfo1.hStdError := GetStdHandle(STD_ERROR_HANDLE);

    FillChar(StartupInfo2, SizeOf(TStartupInfo), 0);
    StartupInfo2.cb := SizeOf(TStartupInfo);
    StartupInfo2.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    StartupInfo2.wShowWindow := SW_HIDE;
    StartupInfo2.hStdInput := PipeRead;
    StartupInfo2.hStdOutput := GetStdHandle(STD_OUTPUT_HANDLE);
    StartupInfo2.hStdError := GetStdHandle(STD_ERROR_HANDLE);

    FillChar(ProcessInfo1, SizeOf(TProcessInformation), 0);
    FillChar(ProcessInfo2, SizeOf(TProcessInformation), 0);

    Win32Check(CreateProcess(nil, PChar(CommandLine2), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo2,
      ProcessInfo2));

    Win32Check(CreateProcess(nil, PChar(CommandLine1), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo1,
      ProcessInfo1));

    WaitForSingleObject(ProcessInfo2.hProcess, INFINITE);
  finally
    if PipeRead <> 0 then
      CloseHandle(PipeRead);
    if PipeWrite <> 0 then
      CloseHandle(PipeWrite);
    if ProcessInfo2.hThread <> 0 then
      CloseHandle(ProcessInfo2.hThread);
    if ProcessInfo2.hProcess <> 0 then
      CloseHandle(ProcessInfo2.hProcess);
    if ProcessInfo1.hThread <> 0 then
      CloseHandle(ProcessInfo1.hThread);
    if ProcessInfo1.hProcess <> 0 then
      CloseHandle(ProcessInfo1.hProcess);
  end;
end;

procedure Main;
begin
  ExecutePiped('child1.exe', 'child2.exe');
end;

begin
  try
    Main;
  except
    on E: Exception do
    begin
      ExitCode := 1;
      Writeln(Error, Format('[%s] %s', [E.ClassName, E.Message]));
    end;
  end;
end.

这正是我一直在寻找的。谢谢你。但出现了一个问题,运行父程序时,在第一行CreateProcess处出现“模块kernel32.dll中的访问冲突”。我已经构建了所有的程序,也许我漏掉了什么... - Steve
我在代码中找不到任何可能导致A/V的原因。我使用的是D2007,但它也应该在D2009中工作。 - Ondrej Kelle
3
CreateProcess的宽字符版本(Delphi 2009称之为)可以修改命令行字符串,因此不能将字符串字面值传递给该函数。请将其存储在字符串变量中,并在类型转换为PChar之前调用UniqueString。 - Rob Kennedy
我认为这个答案会受益于一些讨论,例如,ExecutePiped的重要部分是什么?(即bInheritHandle字段以及在StartupInfo记录中分配管道句柄的方式。)其他句柄字段是否从GetStdHandle获取很重要?命令2在命令1之前执行很重要吗?命令1真的需要写^Z吗?在子进程仍在运行时,管道句柄需要在父进程中保持打开状态吗? - Rob Kennedy

2

这是在Delphi XE中工作的修正代码。 命令行字符串必须是变量,并且在ExecutePiped函数之上定义。

    program Parent;

    {$APPTYPE CONSOLE}

    uses
      Windows, SysUtils, Classes;

    var cmd1, cmd2 :string;

    function ExecutePiped(CommandLine1: string; CommandLine2: string):string;
    var
      StartupInfo1, StartupInfo2 : TStartupInfo;
      ProcessInfo1, ProcessInfo2 : TProcessInformation;
      SecurityAttr               : TSecurityAttributes;
      PipeRead, PipeWrite        : THandle;
      Handle                     : Boolean;
      WorkDir                    : String;
    begin
      PipeWrite := 0;
      PipeRead  := 0;
      try
        SecurityAttr.nLength              := SizeOf(SecurityAttr);
        SecurityAttr.bInheritHandle       := True;
        SecurityAttr.lpSecurityDescriptor := nil;

        CreatePipe(PipeRead, PipeWrite, @SecurityAttr, 0);

        FillChar(StartupInfo1, SizeOf(TStartupInfo), 0);
        StartupInfo1.cb          := SizeOf(TStartupInfo);
        StartupInfo1.dwFlags     := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
        StartupInfo1.wShowWindow := SW_HIDE;
        StartupInfo1.hStdInput   := GetStdHandle(STD_INPUT_HANDLE);
        StartupInfo1.hStdOutput  := PipeWrite;
        StartupInfo1.hStdError   := GetStdHandle(STD_ERROR_HANDLE);

        FillChar(StartupInfo2, SizeOf(TStartupInfo), 0);
        StartupInfo2.cb          := SizeOf(TStartupInfo);
        StartupInfo2.dwFlags     := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
        StartupInfo2.wShowWindow := SW_HIDE;
        StartupInfo2.hStdInput   := PipeRead;
        StartupInfo2.hStdOutput  := GetStdHandle(STD_OUTPUT_HANDLE);
        StartupInfo2.hStdError   := GetStdHandle(STD_ERROR_HANDLE);

        FillChar(ProcessInfo1, SizeOf(TProcessInformation), 0);
        FillChar(ProcessInfo2, SizeOf(TProcessInformation), 0);

        WorkDir := '';

        Handle := CreateProcess(nil, PChar(CommandLine2), nil, nil, True, 0, nil, PChar(WorkDir), StartupInfo2, ProcessInfo2);
        Handle := CreateProcess(nil, PChar(CommandLine1), nil, nil, True, 0, nil, PChar(WorkDir), StartupInfo1, ProcessInfo1);

        WaitForSingleObject(ProcessInfo2.hProcess, INFINITE);

      finally

        if PipeRead              <> 0 then CloseHandle(PipeRead);
        if PipeWrite             <> 0 then CloseHandle(PipeWrite);

        if ProcessInfo2.hThread  <> 0 then CloseHandle(ProcessInfo2.hThread);
        if ProcessInfo2.hProcess <> 0 then CloseHandle(ProcessInfo2.hProcess);

        if ProcessInfo1.hThread  <> 0 then CloseHandle(ProcessInfo1.hThread);
        if ProcessInfo1.hProcess <> 0 then CloseHandle(ProcessInfo1.hProcess);

      end;

    end;

    procedure Main;
    begin
      cmd1 := '"child1.exe"';
      cmd2 := '"child2.exe"';
      ExecutePiped(cmd1, cmd2);
    end;

    begin
      try
        Main;
      except
        on E: Exception do
        begin
          ExitCode := 1;
          Writeln(Error, Format('[%s] %s', [E.ClassName, E.Message]));
        end;
      end;
    end.

为了测试,我已经修改了Child2.pas文件,将接收到的文本写入文件中。
    program Child2;

    {$APPTYPE CONSOLE}

    uses
    Windows, SysUtils, Classes;

    procedure Main;
    var S: string;
        OutFile : TextFile;
    begin
      AssignFile(OutFile, 'test.txt');
      Rewrite(OutFile);
      while not Eof(Input) do
      begin
        Readln(S);
        Writeln(OutFile,S);
        //if S <> '' then OutputDebugString(PChar(S));
      end;
      CloseFile(OutFile);
    end;

    begin
      try
        Main;
      except
        on E: Exception do
        begin
          ExitCode := 1;
          Writeln(ErrOutput, Format('[%s] %s', [E.ClassName, E.Message]));
        end;
      end;
    end.

2

CreateProcess()方法可以重定向启动的应用程序的stdin和stdout。您的应用程序可以从第一个应用程序的stdout读取并写入第二个应用程序的stdin。


有没有办法绕过中间人,让这两个进程直接相互通信? - Steve

0

这种方法应该可行。在担心从Delphi调用它之前,通过在命令提示符窗口(DOS窗口)中运行来解决命令行问题。
然后只需使用WinExec或ShellExecute从Delphi调用该命令。有一些选项可以调用和等待,或者只是“点火并忘记”。


该命令在命令行上运行良好。如果我想等待进程结束,我该如何实现?我的担心是,管道命令应该如何包含在ShellExecute(Ex)的参数中?它应该完全包含在lpFile参数中还是部分包含在lpParameters参数中? - Steve
1
据我所知,AFAIK是命令行壳(它不是“DOS”窗口,而是一个非GUI的win32/64子系统),用于处理重定向运算符,而不是Windows API本身。如果是这样,那么该命令只能通过调用cmd.exe并将该命令行传递给它来使用。 - user160694

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接