我需要在我的程序中启动两个外部程序,并将第一个程序的STDOUT连接到第二个程序的STDIN。在Windows环境下,如何用Delphi实现呢?我使用的是RAD Studio 2009。作为命令行命令,我的情况如下:
dumpdata.exe | encrypt.exe "mydata.dat"
一个似乎有效的快速测试(受到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.
这是在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.
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.
CreateProcess()方法可以重定向启动的应用程序的stdin和stdout。您的应用程序可以从第一个应用程序的stdout读取并写入第二个应用程序的stdin。
这种方法应该可行。在担心从Delphi调用它之前,通过在命令提示符窗口(DOS窗口)中运行来解决命令行问题。
然后只需使用WinExec或ShellExecute从Delphi调用该命令。有一些选项可以调用和等待,或者只是“点火并忘记”。
ExecutePiped
的重要部分是什么?(即bInheritHandle字段以及在StartupInfo记录中分配管道句柄的方式。)其他句柄字段是否从GetStdHandle获取很重要?命令2在命令1之前执行很重要吗?命令1真的需要写^Z吗?在子进程仍在运行时,管道句柄需要在父进程中保持打开状态吗? - Rob Kennedy