Executing External Programs/ja

From Free Pascal wiki
Revision as of 02:33, 22 March 2014 by TheCreativeCAT (talk | contribs) (→‎SysUtils.ExecuteProcess: Partial Translation added)
Jump to navigationJump to search

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)

日本語版メニュー
メインページ - Lazarus Documentation日本語版 - 翻訳ノート - 日本語障害情報


概要

外部のプログラムを動かすにはいくつかの方法がありますが、このチュートリアルでは 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を使用することです。ただし、モーダル(訳注: 外部プログラムに制御が移ること。下記)ですし、パイプもいかなるコントロールも使えません:

SysUtils.ExecuteProcess(UTF8ToSys('実行ファイルへのフルパス'), '', [])

呼出しは同期的です: 呼び出した側は、外部プログラムの動作が終了するまで「ハング」します - しかしアプリケーションの実行を進める前に、ユーザになにかをさせるには便利かもしれません。次の節の TProcess はもっと広い用途に用いることができ、おすすめです。Windows だけでいいなら、ShellExecute も使えます。

MS Windows : CreateProcess, ShellExecute と WinExec

Light bulb  Note: FPC/Lazarus は CreateProcessShellExecuteWinExec をサポートしてはいますが、プラットフォームは Win32/64 に限定されます。クロスプラットフォームなプログラムには TProcess をお使いください。
Light bulb  Note: WinExec は 16 bit 呼出しで、何年も前から Windows API の中で旧式化しています。最近の FPC は警告を発します。

ShellExecute は MS Windows で標準的な関数です (ShellApi.h)。マイクロソフトによる解説は documentation on MSDN をどうぞ。この関数が不安定だと思う方は、この解説の中の COM の初期化についての注釈をお読み下さい。

uses ..., ShellApi;

// 簡単な一行野郎(エラーを無視) :
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;

// バッチファイルを実行 :
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;

// 任意のフォルダでコマンドプロンプトを開く :
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;

// (隠れたコマンドウィンドウを通して)start コマンドを使ってデフォルトブラウザでウェブページの URL を開く:
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;

// あるいは便利な手続:
procedure RunShellExecute(const prog,params:string);
begin
  //  ( ハンドル, nil/'open'/'edit'/'find'/'explore'/'print',   // 'open' は必ずしも必要ではない 
  //      パス付きプログラム名, 引数, 作業用フォルダ,
  //        0=隠し / 1=SW_SHOWNORMAL / 3=最大化 / 7=最小化)   //定数 SW_ は Windows ユニットにある
  if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //成功
  // 0..32 はエラー
end;

WideChar 版の ShellExecuteExW や AnsiChar 版の ShellExecuteExA もあります。

The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.

If in Delphi you used ShellExecute for documents like Word documents or URLs, have a look at the open* (openurl etc) functions in lclintf (see the Alternatives section lower down this page).

Using ShellExecuteEx for elevation/administrator permissions

If you need to execute external program with administrator/elevated privileges, you can use the runas method with the alternative ShellExecuteEx function:

uses ShellApi, ...;

function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;
var
  sei: TShellExecuteInfoA;
begin
  FillChar(sei, SizeOf(sei), 0);
  sei.cbSize := SizeOf(sei);
  sei.Wnd := Handle;
  sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
  sei.lpVerb := 'runas';
  sei.lpFile := PAnsiChar(Path);
  sei.lpParameters := PAnsiChar(Params);
  sei.nShow := SW_SHOWNORMAL;
  Result := ShellExecuteExA(@sei);
end;

procedure TFormMain.RunAddOrRemoveApplication;
begin
  // Example that uses elevated rundll to open the Control Panel to Programs and features
  RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
end;

Unix fpsystem, fpexecve と shell

これらの関数はプラットフォーム依存です。

1.0.x では Unix.Shell は非推奨になりっています。fpsystem をお使いください。

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 を用いる実例として役に立つでしょう。