我需要在我的程序中啓動兩個外部程序,並將第一個STDOUT連接到第二個程序的STDIN。如何在Delphi中實現這一點(RAD Studio 2009,如果它很重要)?我在Windows環境中運行。啓動兩個進程,並用Delphi中的管道連接它們
作爲一個命令行命令我的情況會是這個樣子:
dumpdata.exe | encrypt.exe "mydata.dat"
我需要在我的程序中啓動兩個外部程序,並將第一個STDOUT連接到第二個程序的STDIN。如何在Delphi中實現這一點(RAD Studio 2009,如果它很重要)?我在Windows環境中運行。啓動兩個進程,並用Delphi中的管道連接它們
作爲一個命令行命令我的情況會是這個樣子:
dumpdata.exe | encrypt.exe "mydata.dat"
快速測試,似乎工作(由JCL嚴重的啓發):
child1:說 '你好,世界!' 3×標準輸出
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.
父:發射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.
這種做法應該工作。在擔心從Delphi調用它之前,通過在命令提示符窗口(DOS窗口)中運行得到命令行。
然後,只需使用WinExec或ShellExecute從Delphi中調用該命令即可。有呼叫和等待的選項,或者只是「消防和忘記」。
該命令在命令行上正常工作。如果我想等待流程結束,我怎麼能做到這一點?我擔心的是,如何將管道命令包含在ShellExecute(Ex)的參數中?它應該完全在lpFile參數中還是部分在lpParameters中? – Steve 2010-06-02 12:42:40
AFAIK是處理重定向操作符的命令shell(它不是「DOS」窗口,它是非gui的win32/64子系統),而不是Windows API本身。如果是這樣的命令可以只用於調用cmd.exe並將該命令行傳遞給它。 – 2010-06-02 12:48:48
CreateProcess()允許您重定向啓動的應用程序的標準輸入和標準輸出。您的應用程序可以從第一個應用程序標準輸出中讀取並寫入第二個應用程序標準輸入。
有沒有什麼辦法可以讓中間人離開,讓兩個進程直接相互溝通? – Steve 2010-06-02 13:19:04
這裏是在Delphi XE中正確工作的代碼。 CommandLine字符串必須是變量,並且也在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.
這看起來正是我在找的東西。謝謝。由於某些原因,雖然運行父程序會在第一個CreateProcess行中給我「模塊kernel32.dll中的訪問衝突」。我已經建立了所有的程序。也許我失去了一些東西...... – Steve 2010-06-02 13:43:08
我看不出任何可能導致A/V的代碼。我使用D2007,但它也應該在D2009中工作。 – 2010-06-02 13:59:00
CreateProcess的廣角版本(這是Delphi 2009會調用的版本)可以修改命令行字符串,因此您不得將字符串文字傳遞給它。將其存儲在一個字符串變量中,並在將其輸出到PChar之前調用UniqueString。 – 2010-06-02 14:52:50