Delphi Advent Calendar 2012 12/21 の記事です。
前回、コンソールアプリケーションで Read/Write について述べました。
今回は、Read/Write の標準入出力先を変更してみます。
Read/Write にはファイル変数を指定することで、ファイルに値を出力したり、値をファイルから読み出したりできます。
しかし、それはあくまで入出力先を変数として与えただけで、標準入出力先が変わった訳ではありません。
それでは、標準入出力先を変更するにはどうすれば良いのでしょうか?
StartUpInfo に、その鍵があります。
StartUpInfo とは、CreateProcess でプロセスを生成するときに渡すパラメータの1つです。
StartUpInfo は構造体ですが、ここに次の重要なパラメータがあります。
hStdInput | 標準入力のハンドル |
hStdOutput | 標準出力のハンドル |
hStdError | 標準エラー出力のハンドル |
このパラメータの説明にあるとおり、ここにハンドルを指定することで、標準入出力先を変更できるのです!
ちなみに、UNIX では、標準入力のハンドルは 0, 標準出力のハンドルは 1 と、決まった値になっています。
Windows の場合は、ハンドルは決まっていません。
その代わり GetStdHandle という API を使って標準入出力のハンドルを取得できます。
それはそうと、実際に標準入出力先を変更してみます。
ハンドルに指定できるのは CreateFile などで返されるハンドルです。
つまり、ファイルハンドルを指定すれば、ファイルに出力されます。
今回はパイプを使おうと思います。
パイプを作るには CreatePipe という関数を使います。
パイプは WriteHandle に対して書き込まれた値を ReadHandle で読み出すことができる通信路です。
とりあえず、今回のソースを全文記載します。
001 program Project1;002003 {$APPTYPE CONSOLE}004005 uses006 System.SysUtils, Winapi.Windows;007008 function Exec(const iCommand, iParam: String): String;009 var010 ReadHandle, WriteHandle: THandle;011 SA: TSecurityAttributes;012 SI: TStartUpInfo;013 PI: TProcessInformation;014 Buffer: RawByteString;015 Len: Cardinal;016017 // パイプから値を読み出す018 procedure ReadResult;019 var020 Count: DWORD;021 ReadableByte: DWORD;022 Data: RawByteString;023 begin024 // 読み出しバッファをクリア025 ZeroMemory(PRawByteString(Buffer), Len);026027 // パイプに読み出せるバイト数がいくつあるのか調べる028 PeekNamedPipe(ReadHandle, PRawByteString(Buffer), Len, nil, nil, nil);029 ReadableByte := Length(Trim(String(Buffer)));030031 // 読み込める文字列があるなら032 if (ReadableByte > 0) then begin033 while034 (ReadFile(ReadHandle, PRawByteString(Buffer)^, Len, Count, nil))035 do begin036 Data := Data + RawByteString(Copy(Buffer, 1, Count));037038 if (Count >= ReadableByte) then039 Break;040 end;041042 Result := Result + Data;043 end;044 end;045046 begin047 Result := '';048049 ZeroMemory(@SA, SizeOf(SA));050 SA.nLength := SizeOf(SA);051 SA.bInheritHandle := True;052053 // パイプを作る054 CreatePipe(ReadHandle, WriteHandle, @SA, 0);055 try056 // StartInfo を初期化057 ZeroMemory(@SI, SizeOf(SI));058 with SI do begin059 cb := SizeOf(SI);060 dwFlags := STARTF_USESTDHANDLES; // 標準入出力ハンドルを使います!宣言061 hStdOutput := WriteHandle; // 標準出力を出力パイプに変更062 hStdError := WriteHandle; // 標準エラー出力を出力パイプに変更063 end;064065 // プロセスを作成066 if (not CreateProcess(067 PChar(iCommand),068 PChar(iParam),069 nil,070 nil,071 True,072 0,073 nil,074 nil,075 SI,076 PI))077 then078 Exit;079080 // 読み出しバッファを 4096[byte] 確保081 SetLength(Buffer, 4096);082 Len := Length(Buffer);083084 with PI do begin085 // プロセスが終了するまで、パイプを読み出す086 while (WaitForSingleObject(hProcess, 100) = WAIT_TIMEOUT) do087 ReadResult;088089 ReadResult;090091 // プロセスを閉じる092 CloseHandle(hProcess);093 CloseHandle(hThread);094 end;095 finally096 // パイプを閉じる097 CloseHandle(WriteHandle);098 CloseHandle(ReadHandle);099 end;100 end;101102 begin103 // dir の結果を出力104 Writeln(Exec('C:\Windows\System32\CMD.exe', '/C dir'));105 Readln;106 end.
このソースコードでは、コマンドラインで dir を呼んだ結果を表示します。
結果は、こんな風になります。

重要なのは 60~62 行の StartUpInfo の初期化部です。
前に記載したとおり hStdOutput, hStdError にパイプの書き込みハンドルを入れています。
この hStdOutput と hStdError を有効にするためにフラグに STARTF_USESTDHANDLES を代入しています。
この値を設定しないと標準入出力ハンドルは使用されません。
そして、起動されたコンソールアプリケーションは、設定された書き込み用パイプハンドル(WriteHandle)に値を書き込みます。
値は読み込み用パイプハンドル(ReadHandle)からから読み出すことができます(34行目)。
このようにちょっと手間ですが、標準入出力の値を変更することができました。
次回は、これらの API を使ってコンソールをデバッグ用出力として使う方法を紹介したいと思います。
0 件のコメント:
コメントを投稿