Executing External Programs/ja
│
Deutsch (de) │
English (en) │
español (es) │
français (fr) │
italiano (it) │
日本語 (ja) │
Nederlands (nl) │
polski (pl) │
português (pt) │
русский (ru) │
slovenčina (sk) │
中文(中国大陆) (zh_CN) │
概要
外部のプログラムを動かすにはいくつかの方法がありますが、このチュートリアルでは TProcessを中心に紹介しています。
Delphiで ShellExecute や WinExec を使っていたなら、 FPC/Lazarusにおける代替としてTProcessを使用するようにしましょう。(TProcess は cross-platformであり、Linuxでも有効です。)
Note: FPC/Lazarus は ShellExecute 及び WinExec をサポートしていますが、win32のみで有効です。 あなたのプログラムが cross-platformを目標にしているなら、TProcessの使用が最善です! 次に、さまざまな手法の比較を表にします
方法 | プラットフォーム | 一行で書けるか | 機能の程度 |
SysUtils.ExecuteProcess | クロスプラットフォーム | Yes | 極めて限定的、同期的 |
(ShellApi) ShellExecute | MS Windows のみ | Yes | 多数。より高度な/管理者権限で実行可能 |
Unix fpsystem, fpexecve | Unix のみ | ||
TProcess | クロスプラットフォーム | No | 全機能が可能 |
RunCommand(InDir) | クロスプラットフォーム FPC 2.6.2以降が必要 | Yes | よくあるTProcess利用法をカバー |
(LCLIntf) OpenDocument | クロスプラットフォーム | Yes | 文書を開くだけ |
SysUtils.ExecuteProcess
パイプや他のコントロールから成るフォームを使用しない場合、最も簡単な方法は、 SysUtils.ExecuteProcess('実行ファイルへのフルパス',['引数1','引数2'])を使用することです。
TProcess
TProcessを使用して外部プログラムを実行することができます。TProcessを使うメリットは次の通りです。
- プラットホームに依存しない
- stdinからの読み込み、stdoutへの書き込みが可能
Note: TProcess は terminal/shell ではないので、直接スクリプトを実行することや、出力結果をリダイレクトすることは出来ません。 しかし、 TProcess を用いて同様な結果を得ることができます。いくつかのサンプルを以下に示します。
Important: 実行ファイルへのフルパスを明示する必要があります。(例えば、'cp' ではなく '/bin/cp' のように。) もしもプログラムにパスが通っていたなら、LCLのFileUtil ユニットから、FindDefaultExecutablePath 関数を使用できます。
単純な見本
// これはどのようにして外部プログラムを実行するかを示すデモプログラムです
program launchprogram;
// 関数や手続きを使用するためにファイルをインクルードします
uses
Classes, SysUtils, Process;
// "TProcess"型の変数"AProcess"を定義します
var
AProcess: TProcess;
// ここからプログラムが開始します
begin
// TProcessオブジェクトを生成し、変数AProcessにアサインします
AProcess := TProcess.Create(nil);
// AProcessに実行するコマンドを伝えます
// FreePascalコンパイラを実行してみましょう
AProcess.CommandLine := 'ppc386 -h';
// プログラムを走らせるときの、オプションを定義しましょう
// このオプションは、実行した外部プログラムが停止するまで、
// このプログラムが動かないようにします vvvvvvvvvvvvvv
AProcess.Options := AProcess.Options + [poWaitOnExit];
// AProcessは実行するコマンドラインを知っています
AProcess.Execute;
// ppc386が停止するまで、これは実行されません。
AProcess.Free;
end.
おめでとう!自分のプログラムから外部プログラムを実行する方法を学べました。
より向上した見本
それでは、どのようにして実行した外部プログラムの出力を読めばよいでしょう? 先ほどの例を拡張してみましょう。
// これはどのようにして外部プログラムを実行させ、その出力を読むかを
// 示すデモプログラムです。
program launchprogram;
// 関数や手続きを使用するためにファイルをインクルードします
uses
Classes, SysUtils, Process;
// "TProcess"型の変数"AProcess"を定義します
// また、プログラムの出力からデータを読み取り、
// 保存する TStringList を追加します。
var
AProcess: TProcess;
AStringList: TStringList;
// ここからプログラムが開始します
begin
// TProcessオブジェクトを生成し、変数AProcessにアサインします
AProcess := TProcess.Create(nil);
// TStringListオブジェクトを生成します
AStringList := TStringList.Create;
// AProcessに実行するコマンドを伝えます
// FreePascalコンパイラを実行してみましょう
AProcess.CommandLine := 'ppc386 -h';
// プログラムを走らせるときの、オプションを定義しましょう
// [poWaitOnExit]は、実行した外部プログラムが停止するまで、
// このプログラムが動かないようにします
// また、[poUsePipes]でファイルの出力を読みたいことを伝えます
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
// AProcessは実行するコマンドラインを知っています
AProcess.Execute;
// ppc386が停止するまで、次にいきません
// プログラムの出力を、TStringListに読み込みます。
AStringList.LoadFromStream(AProcess.Output);
// ファイルに保存します
AStringList.SaveToFile('output.txt');
// ファイルが保存されたので、
// TStringListとTProcessを開放します
AStringList.Free;
AProcess.Free;
end.
巨大な出力を読み取る
前述したサンプルでは、外部プログラムの終了まで待ち、その後に外部プログラムの出力内容を読み取りました。 ここで、外部プログラムが大量のデータを出力すると想定した場合、パイプが満杯になるとパイプの内容が読み出されるまで外部プログラムはストップします。 しかし、外部プログラムが終わるまでは、読み出し手のプログラムはパイプの内容を読み出せないため、袋小路に陥ります。
後述するサンプルでは poWaitOnExitを使わず、プログラムの実行中に出力から読むようにしました。出力はメモリストリームに保存され、追って TStringList に読み出されます。
program procoutlarge;
{
Copyright (c) 2004 by Marc Weustink
This example is creeated in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
}
uses
Classes, Process, SysUtils;
const
READ_BYTES = 2048;
var
S: TStringList;
M: TMemoryStream;
P: TProcess;
n: LongInt;
BytesRead: LongInt;
begin
// 出力サイズが不明であり、poWaintOnExit は使用できません。
// Linux では出力パイプの大きさは2KBであり、
// データの大きさがこれ以上である場合、行き詰まります。
//
// 一時的にMemorystreamが出力バッファとして使われます。
M := TMemoryStream.Create;
BytesRead := 0;
P := TProcess.Create(nil);
P.CommandLine := 'ppc386 -va bogus.pp';
P.Options := [poUsePipes];
WriteLn('-- executing --');
P.Execute;
while P.Running do
begin
// メモリ空間を確保します
M.SetSize(BytesRead + READ_BYTES);
// 読んでいきます
n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
if n > 0
then begin
Inc(BytesRead, n);
Write('.')
end
else begin
// データがないときは100 ms待ちます
Sleep(100);
end;
end;
// 読み込みの最終段階です
repeat
// メモリ空間を確保します
M.SetSize(BytesRead + READ_BYTES);
// 読んでいきます
n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
if n > 0
then begin
Inc(BytesRead, n);
Write('.');
end;
until n <= 0;
if BytesRead > 0 then WriteLn;
M.SetSize(BytesRead);
WriteLn('-- executed --');
S := TStringList.Create;
S.LoadFromStream(M);
WriteLn('-- linecount = ', S.Count, ' --');
for n := 0 to S.Count - 1 do
begin
WriteLn('| ', S[n]);
end;
WriteLn('-- end --');
S.Free;
P.Free;
M.Free;
end.
TProcessの出力と入力を使用する
Lazarus-CCR SVNのprocessdemoを参照してください。
TProcess利用のヒント
クロスプラットフォームのアプリケーションを作成する場合、"{$IFDEF}s" と "{$ENDIF}s" 命令を利用することで、OSに応じてコマンドラインを変更できます。
Example:
{...}
AProcess:TProcess.Create(nil)
{$IFDEF WIN32}
AProcess.CommandLine := 'calc.exe'; //Windows Calc
{$ENDIF}
{$IFDEF LINUX}
AProcess.CommandLine := 'kcalc'; //KDE Calc
{$ENDIF}
AProcess.Execute; //右の書き方もできます。 AProcess.Active:=True
{...}
aspell processによるプロセス間通信(talking)のサンプル
pasdocのソースコードを見ると、パイプを介して実行中のaspellプロセスと通信することでスペルチェックを行う二つのユニットがあります:
- PasDoc_ProcessLineTalk.pas unit は、TProcessを継承するTProcessLineTalkクラスを実装しています。 このクラスによって、どんなプロセスとの間でも行単位の通信が容易にできます。
- PasDoc_Aspell.pas units は、TAspellProcessクラスを実装しています。 このクラスは、基礎となるTProcessLineTalkのインスタンスをaspellプロセスの実行と実行中のaspellプロセスとの通信に用いながら、スペルチェックを行います。
- 両ユニットは pasdoc の残りのソースから比較的独立しています。 パイプを介して他のプログラムを実行し、プロセスと通信するためにTPorcess を用いる実例として役に立つでしょう。