【问题标题】:Start two processes and connect them with a pipe in Delphi在 Delphi 中启动两个进程并用管道连接它们
【发布时间】:2010-06-02 12:09:12
【问题描述】:

我需要在我的程序中启动两个外部程序,并将第一个程序的 STDOUT 连接到第二个程序的 STDIN。如何在 Delphi 中实现这一点(RAD Studio 2009,如果重要的话)?我在 Windows 环境中操作。

作为命令行命令,我的情况如下所示:

dumpdata.exe | encrypt.exe "mydata.dat"

【问题讨论】:

    标签: delphi pipe


    【解决方案1】:

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

    child1:说“你好,世界!” 3x 到标准输出

    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.
    

    parent:启动 child1 重定向到 child2

    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 中的访问冲突”。我已经构建了所有程序。也许我错过了什么......
    • 我在代码中看不到任何可能导致 A/V 的内容。我使用的是 D2007,但它应该也可以在 D2009 中使用。
    • CreateProcess 的宽字符版本(Delphi 2009 会调用它)可以修改命令行字符串,因此您不能将字符串文字传递给它。将其存储在字符串变量中并在将其类型转换为 PChar 之前调用 UniqueString。
    • 我认为这个答案将受益于对正在发生的事情的一些讨论。例如,ExecutePiped 的重要部分是什么? (即 bInheritHandle 字段,以及在 StartupInfo 记录中分配管道句柄的方式。)从 GetStdHandle 获取其他句柄字段是否重要?在命令 1 之前执行命令 2 是否重要?命令 1 真的需要写 ^Z 吗?当子进程仍在运行时,管道句柄是否需要在父进程中保持打开状态?
    【解决方案2】:

    CreateProcess() 允许您重定向启动的应用程序的标准输入和标准输出。您的应用程序可以从第一个应用程序标准输出读取并写入第二个应用程序标准输入。

    【讨论】:

    • 有没有什么办法可以省去中间人,让两个进程直接通信?
    【解决方案3】:

    这是在 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.
    

    【讨论】:

      【解决方案4】:

      这种方法应该有效。在担心从 Delphi 调用它之前,先通过在命令提示符窗口(DOS 窗口)中运行来确定命令行。
      然后只需使用 WinExec 或 ShellExecute 从 Delphi 调用该命令。有呼叫和等待的选项,或者只是“触发并忘记”。

      【讨论】:

      • 该命令在命令行上运行良好。如果我想等待进程结束,我怎么能做到这一点?我担心的是管道命令应该如何包含在 ShellExecute(Ex) 的参数中?它应该完全在 lpFile 参数中还是部分在 lpParameters 中?
      • AFAIK 是处理重定向操作符的命令外壳(它不是“DOS”窗口,它是非 gui win32/64 子系统),而不是 Windows API 本身。如果是这样,该命令只能用于调用 cmd.exe 并将该命令行传递给它。
      猜你喜欢
      • 2023-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-19
      相关资源
      最近更新 更多