tags:

views:

351

answers:

3

I need to launch two external programs in my program and connect the STDOUT of the first one to the STDIN of the second program. How can you achieve this in Delphi (RAD Studio 2009, if it matters)? I'm operating in Windows environment.

As a commandline command my situation would look something like this:

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

That approach should work. Before worrying about calling it from Delphi, get the command line worked out by running in a command prompt window (DOS window).
Then just call that command from Delphi with WinExec or ShellExecute. There are options for calling and waiting, or just "fire and forget".

Chris Thornton
The command works well on the command line. If I want to wait for the processes to end, how could I achieve this? My worry is that how should the piped command be included in the parameters of ShellExecute(Ex)? Should it be completely in the lpFile parameter or partially in the lpParameters?
Steve
AFAIK is the command shell (which is not a "DOS" windows, it's a non-gui win32/64 subsytem) to handle redirect operators, not the Windows API themselves. If so that command could be only used invoking cmd.exe and passing that command line to it.
ldsandon
+2  A: 

CreateProcess() allows you to redirect both stdin and stdout of application launched. Your application can read from the first app stdout and write to the second app stdin.

ldsandon
Is there any way to leave out the middle man and just let the two processes communicate directly with each other?
Steve
+4  A: 

A quick test which seems to work (inspired heavily by JCL):

child1: say 'Hello, world!' 3x to standard output

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: echo whatever comes on standard input to OutputDebugString (can be viewed by 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: launch child1 redirected to 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.
TOndrej
This looks exactly what I was looking for. Thank you. For some reason though running the parent program gives me "an access violation in module kernel32.dll" on the first CreateProcess line. I have built all the programs. Maybe I'm missing something...
Steve
I can't see anything in the code which could cause an A/V. I used D2007 but it should also work in D2009.
TOndrej
The wide-character version of CreateProcess (which is what Delphi 2009 would call) can modify the command-line string, so you mustn't pass a string literal to it. Store it in a string variable and call UniqueString before type-casting it to PChar.
Rob Kennedy
That solved it. Thank you!
Steve
I think this answer would benefit from some discussion of what's going on. For example, what's the important part of `ExecutePiped`? (I.e., the bInheritHandle field, and the way the pipe handles are assigned in the StartupInfo records.) Is it important that the other handle fields are gotten from GetStdHandle? Is it important that command 2 be executed before command 1? Does command 1 really need to write ^Z? Do the pipe handles need to remain open in the parent while the children are still running?
Rob Kennedy