|
メニュー
アンテナ
|
[Delphi] GUI アプリからコンソールアプリを実行するには概要GUI アプリからコンソールアプリを単に実行するだけならば, ShellExecute(Handle, nil, 'command.com /c dir c:\', nil, nil, 0); とでもすればいいが,これだと DOS 窓が出てきてしまい,こっそりバックグラウンドで
実行したいようなときには具合が悪い。最後の引数に
この方法でそれを実現するには,まず もうひとつの手は、指定されたプログラムを起動して、その標準入力としてファイルを与え、
標準出力をファイルへ吐き出すようなコンソールプログラム(DJGPP の コードGrabStdOut 関数(ユニットではない) type
// CreateProcess() が失敗したときに投げられる例外
// この関数を置くユニットの interface にでも書いて
ELaunchFailed = class(Exception)
end;
function GrabStdOut(sCommandLine: string; StdIn, StdOut, StdErr: TStream): DWORD;
const
BUFFER_SIZE = 8192;
WAIT_FOR_READY = 1000;
WAIT_FOR_RUN = 20;
var
hReadPipe, hWritePipe: THandle;
hStdInReadPipe, hStdInWritePipe, hStdInWritePipeDup: THandle;
hErrReadPipe, hErrWritePipe: THandle;
sa: TSecurityAttributes;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
dwStdOut, dwStdErr, dwRet: DWord;
StreamBufferSize, nWritten: DWord;
abyBuffer: array[0..BUFFER_SIZE] of Byte;
begin
ZeroMemory(@sa, sizeof(TSecurityAttributes));
with sa do
begin
nLength := sizeof(TSecurityAttributes);
lpSecurityDescriptor := nil;
bInheritHandle := true;
end;
Result := $ffffffff;
hReadPipe := 0; hWritePipe := 0;
hErrReadPipe := 0; hErrWritePipe := 0;
CreatePipe(hStdInReadPipe, hStdInWritePipe, @sa, BUFFER_SIZE);
DuplicateHandle(GetCurrentProcess(), hStdInWritePipe, GetCurrentProcess(),
@hStdInWritePipeDup, 0, false, DUPLICATE_SAME_ACCESS);
CloseHandle(hStdInWritePipe);
CreatePipe(hReadPipe, hWritePipe, @sa, BUFFER_SIZE);
try
CreatePipe(hErrReadPipe, hErrWritePipe, @sa, BUFFER_SIZE);
try
ZeroMemory(@StartupInfo, sizeof(TStartupInfo));
with StartupInfo do
begin
cb := sizeof(TStartupInfo);
dwFlags := STARTF_USESTDHANDLES;
// これがないと DOS 窓が表示されてしまう
wShowWindow := SW_HIDE;
// 標準 IO にパイプの端っこを指定してやる
hStdInput := hStdInReadPipe;
hStdOutput := hWritePipe;
hStdError := hErrWritePipe;
end;
// コンソールアプリ起動
if CreateProcess(nil, PChar(sCommandLine), @sa, nil, true, DETACHED_PROCESS,
nil, nil, StartupInfo, ProcessInfo) then
begin
// 入力待ちになるまで待ってから,
WaitForInputIdle(ProcessInfo.hProcess, WAIT_FOR_READY);
StreamBufferSize := BUFFER_SIZE;
while StreamBufferSize = BUFFER_SIZE do
begin
// 入力を与える
StreamBufferSize := StdIn.Read(abyBuffer, BUFFER_SIZE);
WriteFile(hStdInWritePipeDup, abyBuffer, StreamBufferSize, nWritten, nil);
end;
// 入力を与え終わった
CloseHandle(hStdInWritePipeDup);
try
repeat
// Fixed 2006.08.26: バッファの最後を取りこぼすのを修正
dwRet := WaitForSingleObject(ProcessInfo.hProcess, WAIT_FOR_RUN);
// 標準出力パイプの内容を調べる
PeekNamedPipe(hReadPipe, nil, 0, nil, @dwStdOut, nil);
if (dwStdOut > 0) then
begin
// 内容が存在すれば、読み取る
ReadFile(hReadPipe, abyBuffer, dwStdOut, dwStdOut, nil);
StdOut.WriteBuffer(abyBuffer, dwStdOut);
end;
// 同様にエラー出力の処理
PeekNamedPipe(hErrReadPipe, nil, 0, nil, @dwStdErr, nil);
if (dwStdErr > 0) then
begin
ReadFile(hErrReadPipe, abyBuffer, dwStdErr, dwStdErr, nil);
StdErr.WriteBuffer(abyBuffer, dwStdErr)
end;
until (dwRet = WAIT_OBJECT_0); // コンソールアプリのプロセスが存在している間
GetExitCodeProcess(ProcessInfo.hProcess, Result);
finally
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
CloseHandle(hStdInReadPipe);
end;
end
else
raise ELaunchFailed.Create('CreateProcess() failed!');
finally
CloseHandle(hErrReadPipe);
CloseHandle(hErrWritePipe);
end;
finally
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
end;
end;
解説引数 上記の動作を実現するために, grep foo | sort | uniq としたときには,grep の結果を並び替えて同一行を削除するが,これらのコマンドの間にあるのが パイプで,一方の出力をそのままもう一方の入力へ流し込むときによく用いる。 この関数では,GUI 側からパイプに書き込むと,それがそのままコンソールアプリの標準入力へ 流れるようになっているし,コンソールアプリの標準出力に関してはその逆となっている。 パイプと与えるデータが用意できたら,あとはコンソールアプリが終了するまで, 標準出力とエラー出力を取り出して,ストリームに収める。 使い方新規フォームを作成し、TButton を 1 個、TMemo を 3 個、TEdit を 1 個置いて、 TButton の onClick に次のようなコードを書く。 GrabStdOut() の使い方 procedure TForm1.Button1Click(Sender: TObject);
var
stdOut, stdIn, stdErr: TStream;
begin
stdOut := TMemoryStream.Create;
stdIn := TMemoryStream.Create;
stdErr := TMemoryStream.Create;
try
try
memo1.Lines.SaveToStream(stdIn);
stdIn.Position := 0; // これを忘れると標準入力を与えられない
GrabStdOut(Edit1.Text, stdin, stdout, stderr);
stdOut.Position := 0; stdErr.Position := 0; // これをしないと memo に入らない
memo2.Lines.LoadFromStream(stdOut);
memo3.Lines.LoadFromStream(stdErr);
except
on ELaunchFailed do
begin
Application.MessageBox('CreateProcess() failed!', 'ヽ(`Д´)ノ', 16);
end;
end;
finally
stdOut.Free;
stdIn.Free;
stdErr.Free;
end;
end;
TForm1 のユニットに GrabStdOut() を、名前をあわせつつコピペして実行する。 TEdit にコマンドラインを、Memo1 に標準入力の内容を入れて Button1 を押すと、 Memo2 に標準出力、Memo3 にエラー出力が入る。プロセスの起動に失敗すると ダイアログを出す。 応用ストリームへの書き込みの代わりにコールバックを呼ぶようにすれば、 コンソールアプリの終了を待たずに、吐き出したデータを利用できるようになる。 スレッド化するとさらに美味しくなると思う。 出典auROra の公式サイト告知取得機能のソース 改版履歴初版はいつ書いたんだっけ…….
|