Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2013年06月10日] [更新 2019年07月23日] Delphi サンプルプログラム集
485_アプリの起動とファイルを開く関数
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
485_OpenAppFile.zip [9,528 KB] 2019年07月23日版 (EXE 同梱)



  • 2014年01月18日
  • デフォルトのブラウザとメーラーの実行パス名を取得するサンプルを追加
  • 2016年08月25日
  • [ファイル名を指定して実行] のサンプルを追加




アプリの起動とファイルを開く関数


メモ帳や他のプログラム、あるいはファイル等を、Delphi のプログラムから起動したり、表示する際の参考コード類です。以下の関数類を使用したサンプルとなっています。
  • ShellExecute 関数
  • ShellExecuteEx 関数
  • CreateProcess 関数
  • アクションクラスの TFileRun

実際問題として、実行ファイル、あるいは表示する文書ファイル等の指定と、コマンドライン (オプション) の指定の問題です。それだと、数行で説明は終わりです。それでは寂しいので、具体的なケースについていろいろテストしています。
対象のアプリにどんな起動オプションがあるかは、各々のアプリのヘルプ等で確認してください。






01_パス名やパラメータの空白文字と & ( アンパサンド ) 文字に注意


アプリケーションを起動したり、ファイルを開く時、そのアプリケーションなり、ファイル特有のパラメータを指定する場合があります。このパラメータを、コマンドラインオプション、あるいは、起動パラメータ、コマンドラインスイッチ等と言っています。

複数のオプションがある場合、各々のオプションの区切りとして、半角の空白や & (アンパサンド) が使用されています。半角の空白や & 文字を一連の文字列として認識させるには、二重引用符 (ダブルクォーテーション " ) で囲みます。そうしないと、空白あるいは & までを 1 つのパラメータとして認識してしまいます。ダブルクォーテーションを付加するには、SysUtils ユニットの AnsiQuotedStr 関数が使用できます (ダブルクォーテーションの削除は AnsiDequotedStr 関数)。
AnsiStrings ユニットにも同じ名前の関数がありますが、Delphi 2007 以前用です。

起動オプションが必要ない場合でも、起動するアプリやファイルのパス名に空白文字が含まれている場合、ダブルクォーテーションで囲まないと正常に認識しないこともあります。それは、起動に使用する関数と、引数の扱い方によります。
このことは、ファイルやディレクトリ名 (フォルダ名) 等を扱う場合と、コマンドラインのパラメータを扱う際の、一般的な注意事項です。



  //ダブルクォーテーションで括らないパス名
  //C:\Program を 1 つの文字列として解釈される場合がある
  C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE

  //ダブルクォーテーションで括ったパス名
  //ダブルクォーテーションの中を 1 つの文字列として解釈してくれる
  "C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE"






02_拡張子の関連付けでファイルを開く  -  ShellExecute 関数


ShellExecute 関数で、指定したファイルを表示するサンプルです。
ShellExecute 関数あるいは ShellExecuteEx 関数は、起動するプログラムを指定しなくても、ファイルを表示することができます。実際には、そのファイルの拡張子に関連付けされているプログラムが起動して、そのプログラムがファイルを表示します。したがって、エクセルのブックの場合、エクセルが起動して、そのエクセルがエクセルのブックを表示します。

つまり、起動するプログラムは、そのファイルの拡張子によって決まります。
このサンプルのファイルの拡張子は .xls です。筆者の環境では、Excel 2007 が起動して、エクセルのブックを表示します。



図1
ボタンをクリック
  • エクセルが起動してブックが表示される

リスト1
拡張子の関連付けアプリでファイルを開く
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  ShellExecute関数でエクセルのブックを開く
//  第3引数に開くファイルを指定する場合
//  第3引数は1つの文字列と認識するので、二重引用符(ダブルクォーテーション " )
//  で囲まなくてもよい
//
//  ShellExecute関数は、拡張子に関連付けされたアプリを起動できる
//  ShellExecuteの使用には、usesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath  : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動ファイルのパス
  LFilePath := '..\App Files\テスト.xls';
  LParams := '';

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LFilePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.


ShellExecute 関数あるいは ShellExecuteEx 関数でファイルを指定した時、そのファイルを開くアプリケーションは、下図のダイアログ、[コントロールパネル] [既定のプログラム] [ファイルの種類またはプロトコルのプログラムへの関連付け] で設定されているプログラムです。

下図のダイアログは、Windows Vista 以降では、[ファイル名を指定して実行] ダイアログ、次のコマンドをコマンドプロンプトで実行すると表示できます。


 control.exe /name Microsoft.DefaultPrograms /page pageFileAssoc


図2
拡張子との関連付けアプリ

下図のように、エクスプローラのメニューで [開く] を選択すると、そのファイルを表示、またはプログラムが起動します。
ファイルの場合、上のサンプルと同じく、そのファイルに関連付けされたアプリケーションが起動して、そのファイルを表示します。上のサンプルと動作は同じですが、上のサンプルは、エクスプローラの [開く] とは違います。
エクスプローラと同じ [開く] 動作をするコードは、下に示すコードとなります。このコードは起動するプログラムをエクスプローラにしています。そして、パラメータとして、表示するファイルを指定しています。

このコードは、プロジェクトのフォルダ内の Acro_Text.pdf というファイルを、拡張子 .pdf に関連付けされたプログラムで表示します。



図3
エクスプローラの [開く]

procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath   : String;
  LFilePath  : String;
  LhInstance : Cardinal;
begin
  //エクスプローラの実行名(実際には起動しない)
  LExePath  := 'explorer.exe';
  //表示するプログラムのEXE名またはファイル名
  LFilePath := 'Acro_Test.pdf';

  LhInstance := ShellExecute(Handle,
                             nil,
                             PChar(LExePath),
                             PChar(LFilePath),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;




03_指定アプリケーションでファイルを開く 1   -  ShellExecute 関数


ファイルは、プログラム、アプリケーションを指定して開くこともできます。ここでは、エクセルのブックを既定のプログラムと同じプログラムで開きます。ShellExecute 関数の
  • 第 2 引数には空文字または open という文字列
  • 第 3 引数には実行形式ファイル名
  • 第 4 引数には開くファイル名等、起動時のオプション、パラメータを指定
    (指定したアプリケーションを起動するだけの場合は、この値を nil にする)

ということになります。起動するアプリケーション側に、パラメータを処理する機能が実装されていない場合は、パラメータは無視されます。
システムが認識可能な実行ファイル名であれば、ディレクトリ名は省略可能可能です。

マイクロソフトが提供しているツール類の中には、EXE 等の実行ファイルを直接指定するのではなく、DLL 等に格納されているものもあります。また、同じ EXE 内に複数のツールが収められていることもあります。



図4
実行画面
  • 筆者の環境では、カレントバージョンが Excel 2010 なので、それが起動する

リスト2
指定アプリケーションでファイルを開く。カレントバージョンのアプリが起動する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses 
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  指定したアプリケーションでファイルを開く
//
//  開くファイルのパスに空白文字が含まれているので、SysUtilsのAnsiQuotedStr関
//  数を使用して、ファイル名の文字列を二重引用符(ダブルクォーテーション)で囲む
//  ただし、アプリケーションによっては不要の場合もある
//  それは、アプリケーションが、引数をどのように解釈するかによる
//
//  ShellExecuteの使用には、usesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動プログラムのパス
  //システム環境でパスが通っているのでフルパスでなくても起動する
  LExePath := 'EXCEL.EXE';
  //起動アプリの起動時パラメータ。ここではエクセルのブックのファイル名
  //エクセルを起動するだけの場合はLParamsの値を空文字とする
  LParams := '..\App Files\テスト.xls';
  LParams := SysUtils.AnsiQuotedStr(LParams, '"');

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LExePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.


次のコードのように、起動するアプリの実行ファイル名をフルパスで指定すれば、そのアプリでファイルを表示することができます。
このコードでは、Excel 2007 の EXE ファイルを指定しています。


図5
Excel 2007 を起動

リスト3
指定アプリケーションでファイルを開く。起動アプリをフルパスで指定する例
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  環境変数文字列から実際のフルパスを取得する関数
  //---------------------------------------------------------------------------
  function ExpandEnvironmentString(S: String): String;
  var
    LDestChar : array [0..MAX_PATH-1] of Char;
  begin
    ExpandEnvironmentStrings(PChar(S), LDestChar, MAX_PATH);
    Result := LDestChar;
  end;
  //---------------------------------------------------------------------------

var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動プログラムのパス
  LExePath := '%ProgramFiles%\Microsoft Office\Office12\EXCEL.EXE';
  LExePath := ExpandEnvironmentString(LExePath);
  //起動アプリの起動時パラメータ。ここではエクセルのブックのファイル名
  //エクセルを起動するだけの場合はLParamsの値を空文字とする
  LParams := '..\App Files\テスト.xls';
  LParams := SysUtils.AnsiQuotedStr(LParams, '"');

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LExePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  //起動失敗
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;




04_指定アプリケーションでファイルを開く 2  -    ShellExecute 関数


ファイルを表示するアプリは、エクセルやワード、メモ帳だけとは限りせん。
このサンプルでは、自作の画像ビューワが画像ファイル名をパラメータとして受け取って表示します。画像ビューワと言っても、TImage を配置しただけのものです。
この画像ビューワは、添付の [App Files] フォルダにあります。

前のサンプルも、このサンプルも、アプリケーションを起動オプション付きで起動する、という動作です。このサンプルでは、起動オプションは画像ファイル名ですが、何かを表示するのではなく、起動オプションは、処理の内容を指定する場合もあります。
例えば、Delphi のコマンドラインコンパイラでは、コンパイルのオプションの指定もあります。アプリケーションの起動は、必ずしも画像を表示したり、文書を表示するだけとは限りません。



図6
実行結果
  • 自作の画像ビューワが起動して、引数で指定した画像を表示する
  • 起動するプログラムは管理者権限で実行するようになっているので、管理者への昇格ダイアログが現れる

リスト4
指定アプリケーションでファイルを開く
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  任意のアプリケーションをShellExecute関数で起動する例
//  ここでは、MyImgViewer.exeというプログラムを起動して画像を表示
//
//  ShellExecuteの使用には、usesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動プログラムのパス
  //相対パスで指定
  LExePath := '..\App Files\MyImgViewer.exe';
  //起動時のオプションパラメータ
  //相対パスで指定
  //パス名に空白文字があるので、ダブルクォーテーションで囲む
  LParams := '..\App Files\Parrots.gif';
  LParams := SysUtils.AnsiQuotedStr(LParams, '"');

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LExePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.




05_ブラウザを起動してウェブページを表示  -  ShellExecute 関数


インターネットブラウザを起動してウェブページを表示する例です。
ShellExecute 関数は、拡張子の関連付けでアプリを起動できます。したがって、.htm, html ファイルを指定すれば、多くの場合、インターネットブラウザが起動して、指定したページを表示します。
上のサンプルのように、起動するアプリケーションを指定すれば、そのブラウザで .htm, html ファイルを表示することできます。
筆者の環境では、既定のブラウザが Firefox となっています。


アクションクラスの TBrowseURL を使用しても、デフォルトのブラウザで指定したウェブページを表示することができます。このクラスは TActionList に指定可能なアクションクラスの 1 つです。
TBrowseURL は実行時にインスタンスを生成して使用します。


図7
Button1 をクリック
  • 既定のブラウザである Firefox が起動してウェブページを表示する
図8
Button2 をクリック
  • IE (インターネットエクスプローラ) が起動してウェブページを表示する

リスト5
ブラウザでウェブページを表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  デフォルトのブラウザを起動して、指定のウェブページを表示
//  筆者の環境ではFireFox
//
//  ShellExecuteの使用には、usesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath  : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //表示するウェブページのアドレス
  LFilePath := 'http://mrxray.on.coocan.jp/index.htm';
  LParams   := '';

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LFilePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

//=============================================================================
//  IE(InternetExplorer)を起動して、指定のページを表示
//
//  ShellExecuteの使用には、usesにShellAPIが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  環境変数文字列から実際のフルパスを取得する関数
  //---------------------------------------------------------------------------
  function ExpandEnvironmentString(S: String): String;
  var
    LDestChar : array [0..MAX_PATH-1] of Char;
  begin
    ExpandEnvironmentStrings(PChar(S), LDestChar, MAX_PATH);
    Result := LDestChar;
  end;
  //---------------------------------------------------------------------------

var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動アプリのパス
  LExePath := '%ProgramFiles%\Internet Explorer\iexplore.exe';
  LExePath := ExpandEnvironmentString(LExePath);

  //パラメータの設定
  //ここでは表示するウェブページ
  LParams := 'http://mrxray.on.coocan.jp/index.htm';

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LExePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.




06_メールの新規作成  -  ShellExecute 関数


メールの新規作成画面を表示する例です。
次のコードのように、第 3 引数にメールソフト (メーラー) の実行ファイル名を指定し、第 4 引数のパラメータを mailto: とすると、メールの新規作成画面を表示できます。



リスト6
メールソフトを指定してメールの新規作成画面を表示
procedure TForm1.Button2Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  環境変数文字列から実際のフルパスを取得する関数
  //---------------------------------------------------------------------------
  function ExpandEnvironmentString(S: String): String;
  var
    LDestChar : array [0..MAX_PATH-1] of Char;
  begin
    ExpandEnvironmentStrings(PChar(S), LDestChar, MAX_PATH);
    Result := LDestChar;
  end;
  //---------------------------------------------------------------------------

var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動アプリのパス
  LExePath := '%ProgramFiles%\HidemaruMail\TuruKame.exe';
  LExePath := ExpandEnvironmentString(LExePath);
  //起動パラメータ(メールの新規作成)
  LParams := 'mailto:';

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LExePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;


既定のメールソフト (メーラー) を使用する場合は、最初のサンプル同様、起動アプリの指定を省略して、ファイル名を mailto: とします。


リスト7
デフォルトのメールソフトのメールの新規作成画面を表示
procedure TForm1.Button1Click(Sender: TObject);
var
  LCmdText   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  LCmdText := 'mailto:';
  LParams  := '';

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LCmdText),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;


次のコードは、上の 2 つのコードを実際にテストするためのサンプルですが、宛先、件名等もセットするようにしています。
これまでのサンプルでは、引数のパラメータの区切り文字は半角の空白でした。mailto: では、
  • mailto: に続く最初の項目名の前にクエスチョンマーク (?) を付ける
  • その他の項目の区切り文字はアンパサンド (ampersand, &)

とします。そのため、使用する文字に注意が必要です。件名や本文中に & 文字があると、そこが各パラメータ (項目) の区切りと解釈されてしまいます。この文字を使用する時は、この文字の 16進表記の先頭に % を付けた文字列とします。
また、Windows Live メールでは、改行コードはそのままでは認識しません。これも 16進表記の先頭に % を付けた文字列とします。コードの値ではなく、文字列とします。

秀丸メールは、Windows Live メールの書式の他、各項目の区切り文字として、 ? が使用できます。改行コードに sLineBreak も使用できます。? マークを区切り文字として使用した場合、? は 16 進表記の先頭に % を付けた文字列とします。このサンプルではそうしています。

ファイルの自動添付は、セキュリティ上の問題からできないようです。



[備考]
以下のリンクのサンプルは、インターネットブラウザの入力フォームからメールを送信する例ですが、この場合も、添付ファイルを自動的に設定することはできません。

Indy コンポーネントや参考リンクの TSendMail クラスを使用すると、添付ファイル付きでメールの送信ができます。
Indy コンポーネントを使用する方法は「delphi Indy メール送信 添付ファイル」等のキーワードでネット検索してみてください。
TSendMail では、TSendMail.Attachments プロパティで添付ファイルの設定ができます。TSendMail は、後述の TFileRun と同じように、実行時に生成して使用できます。
Indy を使用するメールの送信も、TSendMail を使用するメールの送信も、メーラーを起動しないメールの送信となります。



図9
Button1 をクリック
  • Windows Live メールの新規メール作成画面となる
図10
Button2 をクリック
  • 秀丸メールの新規メール作成画面となる

リスト8
宛先や件名を指定してメールを新規作成する
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  システム既定のメールソフトでメールを新規作成
//  筆者の環境では、Windows Live メール Version 2009
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LCmdText   : String;
  LParams    : String;
  LhInstance : Cardinal;
  LBodyText  : String;
begin
  LBodyText := 'メール新規作成 ? と %26 と改行文字には注意' + '%0D%0A'
             + 'サンプルプログラムです';

  //開くファイルとしてのメールの各項目値
  //区切り文字は ? と &
  LCmdText := 'mailto:'   + '宛先メールアドレス'
            + '?subject=' + 'メールテスト'
            + '&bcc='     + 'BCC メールアドレス'
            + '&bcc='     + 'BCC メールアドレス'
            + '&body='    + LBodyText;
  LParams := '';

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LCmdText),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

//=============================================================================
//  筆者の使用しているメールソフトでメールの新規作成
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  環境変数文字列から実際のフルパスを取得する関数
  //---------------------------------------------------------------------------
  function ExpandEnvironmentString(S: String): String;
  var
    LDestChar : array [0..MAX_PATH-1] of Char;
  begin
    ExpandEnvironmentStrings(PChar(S), LDestChar, MAX_PATH);
    Result := LDestChar;
  end;
  //---------------------------------------------------------------------------

var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
  LBodyText  : String;
begin
  //起動アプリのパス
  LExePath := '%ProgramFiles%\HidemaruMail\TuruKame.exe';
  LExePath := ExpandEnvironmentString(LExePath);

  LBodyText := 'メール新規作成 ' + '%3F と %26 には注意' + sLineBreak
             + 'サンプルプログラムです';

  //パラメータの設定(メールの各項目値)
  //項目の区切り文字は ?
  LParams := 'mailto:'   + '宛先メールアドレス'
           + '?subject=' + 'メールテスト'
           + '?bcc='     + 'BCC メールアドレス'
           + '?bcc='     + 'BCC メールアドレス'
           + '?body='    + LBodyText;

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LExePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.




07_ファイルのダウンロード  -  ShellExecute 関数


ファイル名にネット上の圧縮ファイルを指定すると。そのファイルのダウンロードなります。その動作確認用のサンプルコードです。


図11
Button1 をクリック
  • ダウンロードのダイアログは、既定のブラウザによって異なる

リスト9
ファイルのダウンロード
//=============================================================================
//  ShellExecute関数でファイルをダウンロード
//  ファイルの有無はチェックしていない
//
//  ShellExecuteの使用には、usesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath  : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //ダウンロードファイルの指定
  LFilePath := 'http://mrxray.on.coocan.jp/Delphi/zip/004_TimerTest.zip';
  LParams   := '';

  LhInstance := ShellExecute(Handle,
                             'open',
                             PChar(LFilePath),
                             PChar(LParams),
                             nil,
                             SW_SHOW);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;




08_指定アプリケーションでファイルを開く  -  ShellExecuteEx 関数


ShellExecuteEx 関数も ShellExecute 関数と同じように使用できます。ShellExecuteEx 関数では、ファイル名を項目識別子で指定できます。

引数は TShellExecuteInfo レコード型です。このレコード型の lpFile の値が ShellExecute 関数の第 3 引数に、lpParameters が第 4 引数に相当します。パラメータの区切り文字に注意するなど、パラメータ扱い方は同じです。
ShellExecuteEx 関数は機能が拡張されていて、起動したプロセスのハンドルが取得でき、起動完了まで待ったり、起動したアプリケーションが終了まで待つといった処理も可能です。

このサンプルでは、独立した変数で値を設定できるように、関数にして使用するようにしています。サンプルプログラム集にも同名の関数がありますが、サンプルごとに動作仕様を変更している場合があります。注意してください。

このサンプルは、テキストエディタまたはメモ帳でテキストファイルを表示します。



図12
Button1 をクリック
  • 既定のプログラムが起動して、でテキストファイルを表示
  • 既定のプログラム (テキストファイルを表示するアプリ) がメモ帳である場合も多いかも知れない
図13
Button2 をクリック
  • メモ帳が起動して、テキストファイルを表示

リスト10
ShellExecuteEx 関数を使用してファイルを開く
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    CheckBox1: TCheckBox;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  ShellExecuteExで指定ファイルを実行
//  ShellExcuteExの使用にはusesにShellAPIが必要
//-----------------------------------------------------------------------------
function RunExeFile(hWnd: HWND; AFile: String; AParameters: String;
   Verb: String): Cardinal;
var
  LInfo : TShellExecuteInfo;
  LRet  : Cardinal;
begin
  FillChar(LInfo, SizeOf(LInfo), 0);
  LInfo.cbSize := SizeOf(LInfo);
  LInfo.Wnd    := hWnd;

  LInfo.fMask  := SEE_MASK_FLAG_DDEWAIT
               or SEE_MASK_FLAG_NO_UI
               or SEE_MASK_NOCLOSEPROCESS;
  LInfo.lpVerb := PChar(Verb);

  LInfo.lpFile       := PChar(AFile);
  LInfo.lpParameters := PChar(AParameters);
  LInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@LInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if LInfo.hInstApp >= 33 then begin
    //プロセスが生成されるまで待つ
    repeat
      LRet := WaitForInputIdle(LInfo.hProcess, 50);
      Application.ProcessMessages;
    until LRet <> WAIT_TIMEOUT;

    CloseHandle(LInfo.hProcess);
  end;

  Result := LInfo.hInstApp;
end;

//=============================================================================
//  ShellExecuteEx関数でテストファイルを開く
//  TShellExecuteInfo.lpFileに開くファイルを指定する場合
//  二重引用符(ダブルクォーテーション)で囲まなくてもよい
//
//  ShellExecuteEx関数は、拡張子に関連付けされたアプリを起動できる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath  : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動ファイルの相対パス
  LFilePath := '..\App Files\テスト.txt';
  //起動アプリの起動オプション
  LParams := '';

  LhInstance := RunExeFile(Handle, LFilePath, LParams, '');

  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

//=============================================================================
//  ShellExecuteEx関数でテストファイルを開く
//  TShellExecuteInfo.lpParametersは空白区切りの引数
//
//  空白文字を含むパラメータは二重引用符(ダブルクォーテーション)で囲む
//  ただし、アプリケーションによっては不要の場合もある
//  それは、アプリケーションが、引数をどのように解釈するかによる
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動プログラムのパス
  LExePath := 'notepad.exe';
  //起動アプリの起動オプション
  LParams := '..\App Files\テスト.txt';
  if CheckBox1.Checked then begin
    LParams := SysUtils.AnsiQuotedStr(LParams, '"');
  end;

  LhInstance := RunExeFile(Handle, LExePath, LParams, '');
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.


上のコードの repeat 文の部分は、次のように while 文にしても動作は同じです。


  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if LInfo.hInstApp >= 33 then begin
    //プロセスが生成されるまで待つ
    while WaitForInputIdle(LInfo.hProcess, 50) = WAIT_TIMEOUT do begin
      Application.ProcessMessages;
    end;

    CloseHandle(LInfo.hProcess);
  end;




09_ShellExecuteEx 関数で起動したアプリの終了待ちと最前面表示


上のサンプルは、対象のアプリが起動するとただちに自アプリに制御が戻ってきます。
対象のアプリを起動し、それが終了するまで待つには、プロセスが生成されるまで (アイドル状態になるまで) 待つ部分を次のように変更します。

ただし、このコードに変更しても、起動元のフォームに対するキーやマウスに対する操作は、メッセージキューに記録されていて、起動したアプリが終了すると、メッセージキューに記録された操作が実行されてしまいます。例えばシステムメニューの閉じるボタン ([X]) の操作が実行されてしまいます。




    //プロセスがアイドル状態になるまで待つ
    repeat
      LRet := WaitForInputIdle(LInfo.hProcess, 50);
      Application.ProcessMessages;
    until LRet <> WAIT_TIMEOUT;

       ↓ 以下に変更

    //起動したプロセスが終了するまで待つ
    while WaitForSingleObject(LInfo.hProcess, 100) = WAIT_TIMEOUT do
      Application.ProcessMessages;


そこで、このサンプルでは、起動元のフォームの Enabled プロパティの値を False にして、操作できないようにしてからアプリを起動し、終了したら True にするようにしています。

起動するアプリは全く別のプロセスとして実行されます。したがって、起動したアプリの終了を検出しても、必ずしも起動元の自アプリがアクティブなウィンドウとなる保障はありません。起動したアプリのが終了を検出したら、起動元のフォームがアクティブになるようにするにはスレッドアタッチという手法を使用します。 このサンプルでは指定フォームを最前面にする処理を関数にしています。



図14
Button1 をクリック
  • メモ帳が起動
  • メモ帳を終了しないと、Form1 には戻れない

リスト11
ShellExecuteEx 関数で起動したアプリの終了を待つ
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure BringToFrontForm(AHandle: HWND);
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  ShellExecuteExで指定ファイルを実行
//  ShellExcuteExの使用にはusesにShellAPIが必要
//-----------------------------------------------------------------------------
function RunExeFile(hWnd: HWND; AFile: String; AParameters: String;
   Verb: String): Cardinal;
var
  LInfo : TShellExecuteInfo;
begin
  FillChar(LInfo, SizeOf(LInfo), 0);
  LInfo.cbSize := SizeOf(LInfo);
  LInfo.Wnd    := hWnd;

  LInfo.fMask  := SEE_MASK_FLAG_DDEWAIT or
                  SEE_MASK_FLAG_NO_UI   or
                  SEE_MASK_NOCLOSEPROCESS;
  LInfo.lpVerb := PChar(Verb);

  LInfo.lpFile       := PChar(AFile);
  LInfo.lpParameters := PChar(AParameters);
  LInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@LInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  //起動したプロセス(アプリ)の終了を待つ処理
  if LInfo.hInstApp >= 33 then begin
    while WaitForSingleObject(LInfo.hProcess, 100) = WAIT_TIMEOUT do begin
      Application.ProcessMessages;
    end;

    CloseHandle(LInfo.hProcess);
  end;

  Result := LInfo.hInstApp;
end;

//=============================================================================
//  ShellExecuteEx関数でテストファイルを開く
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath   : String;
  LParams    : String;
  LhInstance : Cardinal;
begin
  //起動プログラムのパス
  LExePath := 'notepad.exe';
  //起動アプリの起動オプションのパス名を絶対パスに変換
  LParams := ExpandFileName('..\App Files\テスト.txt');
  LParams := SysUtils.AnsiQuotedStr(LParams, '"');

  //起動したアプリが終了するまで、このフォームは操作不可にする
  Self.Enabled := False;

  //アプリ起動
  LhInstance := RunExeFile(Handle, LExePath, LParams, '');
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end else begin
    BringToFrontForm(Handle);
    Windows.MessageBox(Handle, 'メモ帳が終了しました', '終了', MB_ICONINFORMATION);
  end;
  Self.Enabled := True;
end;

//-----------------------------------------------------------------------------
//  指定フォームを最前面に表示する関数
//
//  起動したプロセスが終了するまで待つ場合
//  起動したアプリが終了しても、必しも本アプリが最前面に表示されるとか限らない
//  そこで強制的に最前面にする
//
//  AHandle : 前面に表示するフォームのハンドル (メインフォーム以外も可)
//  SPI_GETFOREGROUNDLOCKTIMEOUT, SPI_SETFOREGROUNDLOCKTIMEOUT はデバック時無効
//-----------------------------------------------------------------------------
procedure TForm1.BringToFrontForm(AHandle: HWND);
var
  LhForeWnd     : HWND;
  LTimeOut      : DWORD;
  LThreadForeID : Cardinal;
  LThreadSelfID : Cardinal;
begin
  // 現在の最前面のウィンドウハンドルを取得
  // 少しタイムラグがないとウィンドウハンドルが取得できないことがある
  LhForeWnd := 0;
  while True do begin
    LhForeWnd := GetForegroundWindow;
    if LhForeWnd <> 0 then Break;
    Application.ProcessMessages;
    Sleep(10);
  end;
  // 現在の最前面ウィンドウのプロセスIDの値を取得
  LThreadForeID := GetWindowThreadProcessId(LhForeWnd);
  // 前面表示するウィンドウのプロセスのスレッドIDの値を取得
  // LThreadSelfID := GetWindowThreadProcessId(AHandle); でもOK (値は違う)
  LThreadSelfID := GetCurrentThreadId;
  try
    // スレッドのアタッチ
    AttachThreadInput(LThreadSelfID, LThreadForeID, True);
    // タスクバーの点滅時間の設定値を取得 (フォアグランドにするまでの時間)
    // デフォルト値は非常に大きな値.通常は前面に表示しないで点滅するだけ
    SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @LTimeOut, 0);

    // タスクバーの点滅時間の設定
    // 点滅時間を 0 にして直ちに最前面に表示するようにする
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, 0);

    // 最小化されているときは元に戻す
    if IsIconic(AHandle) then begin
      SendMessage(AHandle, WM_SYSCOMMAND, SC_RESTORE, 0);
    end;
    // 最前面に表示する
    SetForegroundWindow(AHandle);
  finally
    // タスクバーの点滅時間の設定を元に戻す
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Pointer(LTimeOut), 0);
    // 終了したらアタッチを切り離す
    AttachThreadInput(LThreadSelfID, LThreadForeID, False);
  end;
end;

end.




10_管理者に昇格してアプリを起動  -  ShellExecuteEx 関数


ShellExecuteEx 関数では TShellExecuteInfo.lpVerb に runas を指定すると、指定したアプリケーションを起動する時に、管理者への昇格ダイアログを表示することができます。

このサンプルは、IDE 上から実行しても意味がありません。Delphi の IDE は管理者権限で実行しているので、管理者への昇格ダイアログは表示されません。このサンプルのボタンは、ElevationRequired プロパティを True にして、ボタンに「縦マーク」を表示しています。

後述の CreateProcess 関数で別の EXE なりアプリを起動する際、そのアプリを管理者権限で実行するには、CreateProcess 関数を実行するアプリ自身を管理者権限で実行します。



図15
Button1 をクリック
  • 管理者への昇格ダイアログ表示
  • [はい] ボタンをクリック
図16
コマンドプロンプト起動
  • タイトルに [管理者] の文字がある
  • コマンドプロンプトが、管理者権限で実行されていることになる

Button2 をクリックすると、添付の画像ビューワ MyImgViewer.exe を起動します。
もちろん、本サンプルプログラムを実行する時に「管理者として実行」していると、各々のアプリケーション、プログラムを起動する時にこれらのダイアログは現れません。


図17
Button2 をクリック
  • 管理者への昇格ダイアログを表示
  • [はい] ボタンをクリック
図18
自作の画像ビューワが起動する

リスト12
管理者に昇格してアプリを起動
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  ShellExecuteExで指定ファイルを実行
//  ShellExcuteExの使用にはusesにShellAPIが必要
//-----------------------------------------------------------------------------
function RunExeFile(hWnd: HWND; AFile: String; AParameters: String;
   Verb: String): Cardinal;
var
  LInfo : TShellExecuteInfo;
  LRet  : Cardinal;
begin
  FillChar(LInfo, SizeOf(LInfo), 0);
  LInfo.cbSize := SizeOf(LInfo);
  LInfo.Wnd    := hWnd;

  LInfo.fMask  := SEE_MASK_FLAG_DDEWAIT or
                  SEE_MASK_FLAG_NO_UI   or
                  SEE_MASK_NOCLOSEPROCESS;
  LInfo.lpVerb := PChar(Verb);

  LInfo.lpFile       := PChar(AFile);
  LInfo.lpParameters := PChar(AParameters);
  LInfo.nShow        := SW_SHOW;

  ShellExecuteEx(@LInfo);

  //ShellExecuteExは成功するとhInstAppに33以上の値を返す
  if LInfo.hInstApp >= 33 then begin
    //プロセスがアイドル状態になるまで待つ
    repeat
      LRet := WaitForInputIdle(LInfo.hProcess, 50);
      Application.ProcessMessages;
    until LRet <> WAIT_TIMEOUT;

    CloseHandle(LInfo.hProcess);
  end;

 Result := LInfo.hInstApp;
end;

//=============================================================================
//  ShellExecuteEx関数でコマンドプロンプトを管理者権限で起動
//  TShellExecuteInfo.lpParametersは空白区切りの引数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath   : String;
  LParams    : String;
  LVerbStr   : String;
  LhInstance : Cardinal;
begin
  //起動プログラムのパスと起動オプションの指定
  LExePath := 'Cmd.exe';
  LParams  := '/k cd /d c:\';
  //管理者への昇格ダイアログを表示
  LVerbStr := 'runas';

  LhInstance := RunExeFile(Handle, LExePath, LParams, LVerbStr);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

//=============================================================================
//  ShellExecuteEx関数で指定アプリを管理者権限で起動
//  TShellExecuteInfo.lpParametersは空白区切りの引数
//  空白文字を含むパラメータは二重引用符(ダブルクォーテーション)で囲む
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LExePath   : String;
  LParams    : String;
  LVerbStr   : String;
  LhInstance : Cardinal;
begin
  LExePath := '..\App Files\MyImgViewer.exe';
  LParams  := '..\App Files\Parrots.gif';
  LParams  := SysUtils.AnsiQuotedStr(LParams, '"');
  //管理者への昇格ダイアログを表示
  LVerbStr := 'runas';

  LhInstance := RunExeFile(Handle, LExePath, LParams, LVerbStr);
  if LhInstance <= 32 then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.




11_CreateProcess 関数でアプリケーションを起動


CreateProcess 関数は、ShellExecute 関数, ShellExecuteEx 関数でアプリケーションやファイルを開く場合と以下のような違いがあります。
  • 拡張子の関連付けでアプリケーションを起動するものではない
  • 第 2 引数に実行ファイル名とオプションのパラメータの文字列の両方を与える
  • 起動するアプリケーション管理者権限で実行するには、自分自身も管理者権限で実行する

このサンプルは、CreateProcess 関数を使用する専用の関数を作成して利用するようにしています。サンプルプログラム集には、同名の関数を作成して使用しているサンプルがありますが、サンプルによって内容が異なる場合があります。注意してください。

ShellExecuteEx の場合もそうですが、アプリケーションの起動を待つと言うのは、実際にはプロセスの起動待ちのことです。アプリケーションの操作可能なウィンドウが表示されるまで待つという意味ではありません。



[備考]
基本的に CreateProcess 関数では起動するアプリ、プログラムの EXE 名をフルパスで指定しますが、環境変数の %PATH% に含まれているディレクトリ内にあるアプリ、プログラムは、ディレクトリ名を省略しても起動できます。



図19
実行結果
  • 起動するプログラムは管理者権限で起動するようになっているので、添付の EXE を管理者権限で起動
  • [Button1] をクリック
  • 添付の画像ビューワが起動

リスト13
CreateProcess 関数でアプリケーションを起動
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して、指定したファイルを実行する
//  アプリの起動完了まで待つ
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
  LRet         : DWORD;
begin
  Result := 0;

  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := WAIT_FAILED;
  end else begin
    //プロセスが生成されるまで待つ
    repeat
      LRet := WaitForInputIdle(LProcessInfo.hProcess, 50);
      Application.ProcessMessages;
    until LRet <> WAIT_TIMEOUT;

    GetExitCodeProcess(LProcessInfo.hProcess, Result);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  CreateProcess関数で指定アプリを引数付きで起動
//  CreateProcess関数の第2引数は空白区切りの引数
//  したがって、ファイル名、引数のパラメータに空白が含まれている場合は、ダブル
//  クォーテーションで囲んでおく必要がある
//
//  CreateProcess関数は、拡張子の関連付けでの起動はできない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath : String;
  LParams  : String;
  LRet     : DWORD;
begin
  //起動アプリとその引数の設定
  LExePath := '..\App Files\MyImgViewer.exe';
  LExePath := SysUtils.AnsiQuotedStr(LExePath, '"');
  LParams  := '..\App Files\Parrots.gif';
  LParams  := SysUtils.AnsiQuotedStr(LParams, '"');

  //アプリを起動
  LRet := WinExecAndWait32V2(LExePath + ' ' + LParams, SW_SHOWNORMAL);
  if LRet = WAIT_FAILED then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;

end.


本サンプルのように、CreateProcess 関数を使用すると、拡張子に関連付けされたアプリケーション以外のアプリケーションでファイルを表示あるいは起動できます。
例えば、上のコードの、Button1 クリックのコードを以下のようにすると、.pdf 文書を、ワードパッドで表示できます。



procedure TForm1.Button1Click(Sender: TObject);
var
  LExeName : String;
  LParams  : String;
  LRet     : DWORD;
begin
  //起動アプリとその引数の設定
  LExeName := 'C:\Program Files\Windows NT\Accessories\wordpad.exe';
  LExeName := SysUtils.AnsiQuotedStr(LExeName, '"');
  LParams  := 'Acro_Test.pdf';
  LParams  := SysUtils.AnsiQuotedStr(LParams, '"');

  //アプリを起動
  LRet := WinExecAndWait32V2(LExeName + ' ' + LParams, SW_SHOWNORMAL);
  if LRet = WAIT_FAILED then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end;
end;




12_CreateProcess 関数で起動したアプリの終了待ちと最前面表示


上のサンプルは、対象のアプリが起動するとただちに自アプリに制御が戻ってきます。
対象のアプリを起動し、それが終了するまで待つには、プロセスが生成されるまで (アイドル状態になるまで) 待つ部分を次のように変更します。

ただし、このコードに変更しても、起動元のフォームに対するキーやマウスに対する操作は、メッセージキューに記録されていて、起動したアプリが終了すると、メッセージキューに記録された操作が実行されてしまいます。例えばシステムメニューの閉じるボタン ([X]) の操作が実行されてしまいます。



    //プロセスが生成されるまで待つ
    repeat
      LRet := WaitForInputIdle(LProcessInfo.hProcess, 50);
      Application.ProcessMessages;
    until ret <> WAIT_TIMEOUT;

       ↓ 以下に変更

    //起動したプロセスが終了するまで待つ
    while WaitForSingleObject(LProcessInfo.hProcess, 100) = WAIT_TIMEOUT do
      Application.ProcessMessages;


そこで、このサンプルでは、起動元のフォームの Enabled プロパティの値を False にして、操作できないようにしてからアプリを起動し、終了したら True にするようにしています。

起動するアプリは全く別のプロセスとして実行されます。したがって、起動したアプリの終了を検出しても、必ずしも起動元の自アプリがアクティブなウィンドウとなる保障はありません。起動したアプリのが終了を検出したら、起動元のフォームがアクティブになるようにするにはスレッドアタッチという手法を使用します。 このサンプルでは指定フォームを最前面にする処理を関数にしています。



図20
Button1 をクリック
  • メモ帳が起動
  • メモ帳を終了しないと、Form1 には戻れない

リスト14
CreateProcess 関数で起動したアプリの終了を待つ
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    procedure BringToFrontForm(AHandle: HWND);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して、指定したファイルを実行する
//  起動したアプリが終了するまでこの関数が終了しないコードになっている
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
begin
  Result := 0;

  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := WAIT_FAILED;
  end else begin
    //起動したプロセスが終了するまで待つ
    while WaitForSingleObject(LProcessInfo.hProcess, 100) = WAIT_TIMEOUT do
      Application.ProcessMessages;

    GetExitCodeProcess(LProcessInfo.hProcess, Result);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  CreateProcess関数で指定アプリを引数付きで起動
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LExePath : string;
  LParams  : string;
  LRet     : DWORD;
begin
  //起動アプリとその引数の設定
  LExePath := 'notepad.exe';
  LExePath := SysUtils.AnsiQuotedStr(LExePath, '"');
  LParams  := ExpandFileName('..\App Files\テスト.txt');
  LParams  := SysUtils.AnsiQuotedStr(LParams, '"');

  //起動したアプリが終了するまで、このフォームは操作不可にする
  Self.Enabled := False;

  //アプリ起動
  LRet := WinExecAndWait32V2(LExePath + ' ' + LParams, SW_SHOWNORMAL);
  if LRet = WAIT_FAILED then begin
    Windows.MessageBox(Handle, '起動に失敗しました', 'エラー', MB_ICONSTOP);
  end else begin
    BringToFrontForm(Handle);
    Windows.MessageBox(Handle, 'メモ帳が終了しました', '終了', MB_ICONINFORMATION);
    Self.Enabled := True;
  end;
end;

//-----------------------------------------------------------------------------
//  指定フォームを最前面に表示する関数
//
//  起動したプロセスが終了するまで待つ場合
//  起動したアプリが終了しても、必しも本アプリが最前面に表示されるとか限らない
//  そこで強制的に最前面にする
//
//  AHandle : 前面に表示するフォームのハンドル (メインフォーム以外も可)
//  SPI_GETFOREGROUNDLOCKTIMEOUT, SPI_SETFOREGROUNDLOCKTIMEOUT はデバック時無効
//-----------------------------------------------------------------------------
procedure TForm1.BringToFrontForm(AHandle: HWND);
var
  LhForeWnd     : HWND;
  LTimeOut      : DWORD;
  LThreadForeID : Cardinal;
  LThreadSelfID : Cardinal;
begin
  // 現在の最前面のウィンドウハンドルを取得
  // 少しタイムラグがないとウィンドウハンドルが取得できないことがある
  LhForeWnd := 0;
  while True do begin
    LhForeWnd := GetForegroundWindow;
    if LhForeWnd <> 0 then Break;
    Application.ProcessMessages;
    Sleep(10);
  end;
  // 現在の最前面ウィンドウのプロセスIDの値を取得
  LThreadForeID := GetWindowThreadProcessId(LhForeWnd);
  // 前面表示するウィンドウのプロセスのスレッドIDの値を取得
  // LThreadSelfID := GetWindowThreadProcessId(AHandle); でもOK (値は違う)
  LThreadSelfID := GetCurrentThreadId;
  try
    // スレッドのアタッチ
    AttachThreadInput(LThreadSelfID, LThreadForeID, True);
    // タスクバーの点滅時間の設定値を取得 (フォアグランドにするまでの時間)
    // デフォルト値は非常に大きな値.通常は前面に表示しないで点滅するだけ
    SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @LTimeOut, 0);

    // タスクバーの点滅時間の設定
    // 点滅時間を 0 にして直ちに最前面に表示するようにする
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, 0);

    // 最小化されているときは元に戻す
    if IsIconic(AHandle) then begin
      SendMessage(AHandle, WM_SYSCOMMAND, SC_RESTORE, 0);
    end;
    // 最前面に表示する
    SetForegroundWindow(AHandle);
  finally
    // タスクバーの点滅時間の設定を元に戻す
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Pointer(LTimeOut), 0);
    // 終了したらアタッチを切り離す
    AttachThreadInput(LThreadSelfID, LThreadForeID, False);
  end;
end;

end.




13_作業ディレクトリの場所の指定


ShellExecute 関数、ShellExecuteEx 関数あるいは CreateProcess 関数でアプリケーションを起動した場合、作業フォルダは、通常は起動したアプリケーションのフォルダ (ディレクトリ) となります。
アプリケーションによっては、作業ディレクトリを明示的に指定する必要がある場合があります。
作業ディレクトリがどのように使用されるのかは、そのアプリケーションの仕様によります。

ShellExecuteEx 関数では、 TShellExecuteInfo の lpDirectory メンバーの値で、CreateProcess 関数では、8 番目 (最後から 3 番目) の引数で指定できます。

アプリによっては、起動オプションで作業ディレクトリを設定できることがあります。



[備考]
cmd.exe (コマンドプロセッサ) を起動する際、作業ディレクトリを指定すると、その作業ディレクトリがカレントディレクトリになります。




14_相対パスと絶対パスの変換


上の方の ShellExecute 関数のサンプルで、相対パスを使用しているコードがあります。アプリケーションで使用するフォルダ (ディレクトリ) を相対パスで設定すると便利です。そうすればアプリケーションを配置する場所が変わっても、必要なフォルダを参照できます。
しかし、Windows API の中には、相対パスでは動作しない場合もあります。実行時には、相対パスを絶対パスに変換しておくと確実です。絶対パスへの変換には、ExpandFileName 関数が使用できます。

逆に、絶対パスを相対パスに変換するには、ExtractRelativePath 関数を使用します。



[備考]
相対パスと絶対パスの変換は、現在のカレントディレクトリが基準パスとなります。


自分自身のアプリケーションの EXE ファイルの絶対パスは次のコードで取得できます。


procedure TForm1.Button1Click(Sender: TObject);
var
  LExeFullPath : String;
begin
  //自分自身(EXE)の絶対パスを取得
  //基本的にはどちらでも取得可能
  LExeFullPath := Application.ExeName;
  LExeFullPath := ParamStr(0);
end;


また、複数のパスを連結したり、起動オプションの文字列の設定に、次のような SysUtils.pas 内の関数が使用できます。PathSep は環境文字列のセパレータ等に使われている文字です。
この定義文は、Delphi 6, 7, 2007, 2009, 2010, XE, XE2, XE3, XE4, XE5 のものです。


const
  PathDelim  = {$IFDEF MSWINDOWS} '\'; {$ELSE} '/'; {$ENDIF}
  DriveDelim = {$IFDEF MSWINDOWS} ':'; {$ELSE} '';  {$ENDIF}
  PathSep    = {$IFDEF MSWINDOWS} ';'; {$ELSE} ':'; {$ENDIF}


参考までに、改行文字には sLineBreak 定数が使用てきます。sLineBreak の System.pas 内での定義は次のようになっています。


const
   sLineBreak = {$IFDEF LINUX} AnsiChar(#10) {$ENDIF}
      {$IFDEF MSWINDOWS} AnsiString(#13#10) {$ENDIF}
      {$IFDEF MACOS} AnsiChar(#10) {$ENDIF};


Delphi XE2, XE3, XE4, XE5 での改行文字の定義は次のようになっています。


const
   sLineBreak = {$IFDEF POSIX}} AnsiChar(#10) {$ENDIF}
      {$IFDEF MSWINDOWS} AnsiString(#13#10) {$ENDIF}




15_デフォルト ( 既定 ) のインターネットブラウザの実行パス名の取得


これまでのサンプルでは、ブラウザ (インターネットブラウザ) やメーラーは、拡張子に関連付けのあるアプリ、あるいは指定したアプリを起動しています。
システムの既定のブラウザやメーラーは拡張子に関連付けされているのが普通ですが、これを意識的にシステムがデフォルトとして設定しているアプリケーションで起動する場合の参考です。

次のコードは、デフォルトのブラウザの起動パスをレジストリから取得します。取得した文字列にはコマンドラインの引数部分も含まれている場合があります。そこで、

  • (1) レジストリからコマンドラインの文字列を取得
  • (2) PathRemoveArgs 関数を使用して、引数を除いた実行パス名だけを取得
  • (3) AnsiDequotedStr 関数でダブルクォーテーションを取り除く
  • (4) 念のため、GetLongPathName 関数で「長いパス名」に変換
という手順で取得しています。


図21
実行画面例
  • デフォルトのブラウザのフルパス

リスト15
デフォルトのインターネットブラウザの実行パス名を取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Registry,
  ShLwApi;

{$R *.dfm}

//=============================================================================
//  デフォルトのブラウザのフルパスをレジストリから取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  短いパス名を長いパス名に変換する関数
  //  必要がなければ実行しなくてもよい
  //---------------------------------------------------------------------------
  function ChangeLongPathName(ShortPath: String): String;
  var
    LSize : Integer;
  begin
    LSize := GetLongPathName(PChar(ShortPath), nil, 0);
    if LSize > 0 then begin
      SetLength(Result, LSize);
      LSize := GetLongPathName(PChar(ShortPath), PChar(Result), LSize);
      SetLength(Result, LSize);
    end else begin
      Result := '';
    end;
  end;
  //---------------------------------------------------------------------------

var
  LReg     : TRegistry;
  LAppKey  : String;
  LAppItem : String;
  LText    : String;
begin
  LReg         := TRegistry.Create;
  LReg.Access  := KEY_READ;
  LReg.RootKey := HKEY_CLASSES_ROOT;
  try
    //引数を''にして既定値を取得
    LAppKey  := 'http\shell\open\command';
    LAppItem := '';
    if LReg.OpenKey(LAppKey, False) then begin
      try
        LText := LReg.ReadString(LAppItem);

        //文字列から実行ファイル名だけを取得してダブルクォーテーションを取り除く
        ShLwApi.PathRemoveArgs(PChar(LText));
        LText := SysUtils.AnsiDequotedStr(PChar(LText), '"');
        LText := ChangeLongPathName(LText);
        MessageBox(Handle, PChar(LText), '実行パス', MB_ICONINFORMATION);
      finally
        LReg.CloseKey;
      end;
    end;
  finally
    FreeAndNil(LReg);
  end;
end;

end.


デフォルトのブラウザの実行パス名は、次のように、http, https 等のプロトコルによって異なる場合があります。上のサンプルでは、http プロトコルの場合の設定を取得しています。

HKEY_CLASS_ROOT\htmlfile\shell\open\command
HKEY_CLASS_ROOT\htmlfile\shell\opennew\command
HKEY_CLASS_ROOT\http\shell\open\command
HKEY_CLASS_ROOT\https\shell\open\command



図22




16_デフォルト( 既定 ) のメーラーの実行パス名の取得


今度は、既定のメーラーの起動パスをレジストリから取得します。取得した文字列にはコマンドラインの引数部分も含まれている場合があります。そこで、上のサンプルと同じように、

  • (1) レジストリからコマンドラインの文字列を取得
  • (2) PathRemoveArgs 関数を使用して、引数を除いた実行パス名だけを取得
  • (3) AnsiDequotedStr 関数でダブルクォーテーションを取り除く
  • (4) 念のため、GetLongPathName 関数で「長いパス名」に変換
という手順で取得しています。


図22
実行画面例
  • デフォルトのメーラーのフルパス

リスト16
デフォルトのメーラーの実行パス名を取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Registry,
  ShLwApi;

{$R *.dfm}

//=============================================================================
//  デフォルトのメーラーのフルパスをレジストリから取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  短いパス名を長いパス名に変換する関数
  //  必要がなければ実行しなくてもよい
  //---------------------------------------------------------------------------
  function ChangeLongPathName(ShortPath: String): String;
  var
    LSize : Integer;
  begin
    LSize := GetLongPathName(PChar(ShortPath), nil, 0);
    if LSize > 0 then begin
      SetLength(Result, LSize);
      LSize := GetLongPathName(PChar(ShortPath), PChar(Result), LSize);
      SetLength(Result, LSize);
    end else begin
      Result := '';
    end;
  end;
  //---------------------------------------------------------------------------

var
  LReg     : TRegistry;
  LAppKey  : String;
  LAppItem : String;
  LText    : String;
begin
  LReg         := TRegistry.Create;
  LReg.Access  := KEY_READ;
  LReg.RootKey := HKEY_CLASSES_ROOT;
  try
    //引数を''にして既定値を取得
    LAppKey  := 'mailto\shell\open\command';
    LAppItem := '';
    if LReg.OpenKey(LAppKey, False) then begin
      try
        LText := LReg.ReadString(LAppItem);

        //文字列から実行ファイル名だけを取得してダブルクォーテーションを取り除く
        ShLwApi.PathRemoveArgs(PChar(LText));
        LText := SysUtils.AnsiDequotedStr(PChar(LText), '"');
        LText := ChangeLongPathName(LText);
        MessageBox(Handle, PChar(LText), '実行パス', MB_ICONINFORMATION);
      finally
        LReg.CloseKey;
      end;
    end;
  finally
    FreeAndNil(LReg);
  end;
end;

end.




17_TFileRun を使用したアプリの起動


アプリの起動は、TFileRun を使用することもできます。
このクラスは TActionList に指定可能なアクションクラスの 1 つです。このクラスは、フォームに TActionList を配置しなくても、実行時に生成して使用することができます。


[備考]
アクションクラスを使用したサンプルが以下の記事にもあります。


図23
TFileRun を使用したアプリの起動

リスト17
TFileRun を使用したアプリの起動 - エクセルを起動してエクセルのブックを表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ExtActns;

{$R *.dfm}

//=============================================================================
//  TFileRunを使用したアプリの起動
//  TFileRunはTActionListで指定可能なアクションクラス
//  このコードは、指定のエクセルのブックをエクセルで表示する
//
//  TFileRunの使用にはusesにExtActnsが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LActClass : TFileRun;
  LFilePath : String;
begin
  //表示するエクセルのブックのフルパス名
  //途中に空白文字がある場合は、ダブルクォーテーションで括る
  LFilePath := ExpandFileName('../App Files/テスト.xls');
  LFilePath := AnsiQuotedStr(LFilePath, '"');

  LActClass := TFileRun.Create(Self);
  try
    LActClass.FileName   := 'EXCEL.EXE';
    LActClass.Parameters := LFilePath;
    LActClass.Execute;
  finally
    FreeAndNil(LActClass);
  end;
end;

end.




18_ShellExecute 等の関数と [ ファイル名を指定して実行 ] ダイアログ


本ページで使用した関数類は、コードでファイル名を指定して実行します。
Windows には [ファイル名を指定して実行] というツールがあります。Windows 9X の時代には [アプリの起動] という名前でしたが、Windows XP でダイアログの名前が変更されています。名前の通り、実行ファイル名やコマンドを入力して実行できます。

[ファイル名を指定して実行] のダイアログは、プログラムからも表示できます。下に示すコードはその例です。このコードは、Shell32.dll 内の序数 61 の非公開関数を使用しています。
64 ビット版の Windows で使用するには、64 ビットの EXE を作成します。あるいはリダイレクトの機能を無効にしして実行します。リダイレクトを無効にすると、32 ビットの EXE でも、64 ビット版の [ファイル名を指定して実行] のダイアログとして動作します。

下図は、以下のコマンドを実行した結果です。このコマンドは [既定のプログラム] の設定ダイアログを表示します。


control.exe /name Microsoft.DefaultPrograms


[備考]
[ファイル名を指定して実行] のダイアログは、TShell1.FileRun や シェルコマンドを使用しても表示することができますが、[管理者として実行] しても管理者権限での実行にならなかったり、ダイアログの説明文が表示されないことがあります。


図24
[ファイル名を指定して実行] ダイアログ
図25
コマンドの実行例

リスト18
[ファイル名を指定して実行] のダイアログを表示する
  //---------------------------------------------------------------------------
  //  [ファイル名を指定して実行] ダイアログを表示する関数の定義
  //  この関数は非公開API関数
  //
  //  Shell32.dllを使用するにはusesにShellAPIが必要
  //---------------------------------------------------------------------------
  procedure SH_RunFileDlg(
            Handle     : THandle;
            hIcon      : HICON;
            lpazPaht   : PChar;
            lpszTitle  : PChar;
            lpszPrompt : PChar;
            uFlages    : Integer); stdcall; external 'shell32.dll' index 61;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//  実際にはフォームは表示しない
//  プロジェクトのソースコードに以下の変更をしている
//    Application.ShowMainForm := False;   デフォルトはTrue
//    Application.Run;                     実行しない(削除している)
//  プロジェクトのアイコンも変更している
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LDlgTitle : PChar;
  LPrompt   : PChar;
begin
  LDlgTitle := 'ファイル名を指定して実行';
  LPrompt   := nil;

  SH_RunFileDlg(Handle, 0, nil, LDlgTitle, LPrompt, 0);
end;


本ページで使用した関数類も、コマンドの入力欄を配置したダイアログを作成して表示すれば、[ファイル名を指定して実行] のダイアログと同じように使用できます。
下の図はその例です。[ファイル名を指定して実行] のダイアログと ShellExecute 関数による実行では機能的に違いがあります。[ファイル名を指定して実行] のダイアログでは、コマンドは 1 行ですが、ShellExecute 関数では、実行ファイル名とパラメータを別にする必要があります。また、環境変数は実際のパス名に変換しておく必要があります。

この図のコマンドは、上と同じ [既定のプログラム] の設定ダイアログを表示します。



図26
[既定のプログラム] の設定ダイアログを表示する例

リスト19
コマンドの入力欄を配置して、ShellExecute 関数でコマンドを実行する
implementation

uses
  ShellAPI;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  環境変数を含む(%等の文字を含む)を実際のパス名に変換
//-----------------------------------------------------------------------------
function ExpandEnvironmentString(S: string): String;
var
  LDstChar : array [0..MAX_PATH - 1] of Char;
begin
  ExpandEnvironmentStrings(PChar(S), LDstChar, MAX_PATH);
  Result := LDstChar;
end;

//=============================================================================
//  フォーム生成時の処理
//
//  コマンドの入力欄を配置して、ShellExecute 関数でコマンドを実行する例
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Self.Caption := 'ファイル名を指定して実行';

  //起動直後にEdit1を編集状態に
  //[Enter]キーで実行
  //[Esc]キーでダイアログを閉じる
  ActiveControl   := Edit1;
  Button1.Default := True;
  Button2.Cancel  := True;

  Edit1.Text := 'control.exe';
  Edit2.Text := '/name Microsoft.DefaultPrograms';
end;

//=============================================================================
//  入力欄に入力したコマンドを実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath : string;
  LParams   : string;
  LResult   : Cardinal;
  LText     : string;
begin
  LFilePath := ExpandEnvironmentString(Trim(Edit1.Text));
  LParams   := ExpandEnvironmentString(Trim(Edit2.Text));

  LResult := ShellExecute(Handle, '', PChar(LFilePath), PChar(LParams), nil, SW_SHOW);

  if LResult <= 32 then begin
    LText := SysErrorMessage(LResult);
    Windows.MessageBox(Handle, PChar(LText), '情報', MB_ICONWARNING);
  end;

  Close;
end;

//=============================================================================
//  [キャンセル]
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Close;
end;




19_自前の [ ファイル名を指定して実行 ] ダイアログ ( 簡易版 )


以下のコードのように、コマンド文字列を分解すれば、シングルラインでのコマンド入力にすることもできます。自前の [ファイル名を指定して実行] ダイアログということになります。
実用的にするには、64 ビット Windows での使用も考慮して、リダイレクトの機能を無効にするなどの処理も必要となるでしょう。アプリケーション・マニフェストも必要です。



図27
コマンドの入力を 1 行にした例

リスト20
コマンドの入力欄を配置して、ShellExecute 関数でコマンドを実行する
コマンドをシングルラインで入力可能にする例
//=============================================================================
//  入力欄に入力したコマンドを実行
//  1行だけのコマンド文字列を実行ファイル名とパラメータに分解する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList  : TStringList;
  LFilePath : string;
  LParams   : string;
  LResult   : Cardinal;
  LText     : string;
  LPos      : Integer;
  i         : Integer;
begin
  if Trim(Edit1.Text) = '' then exit;

  LStrList := TStringList.Create;
  try
    //コマンド文字列を半角空白で区切ったリストを作成
    //TStringList.StrictDelimiterがなくて動作可能
    LStrList.Delimiter       := ' ';
    LStrList.StrictDelimiter := True;
    LStrList.DelimitedText   := Trim(Edit1.Text);
    LStrList.Text := ExpandEnvironmentString(LStrList.Text);

    LFilePath := '';
    LParams   := '';

    if LStrList.Count = 1 then begin
      LFilePath := LStrList[0];
    end else begin
      //実行形式ファイルの検索
      LPos := 0;
      for i := 0 to LStrList.Count - 1 do begin
        LFilePath := LFilePath + LStrList[i];
        if not FileExists(LFilePath) then begin
          if FileExists(LFilePath + '.EXE')
            or FileExists(LFilePath + '.CMD')
            or FileExists(LFilePath + '.BAT') then begin
            LPos := i + 1;
            break;
          end else begin
            LFilePath := LFilePath + ' ';
          end;  
        end else begin
          LPos := i + 1;
          break;
        end;
      end;
      //ディレクトリ名が省略されているパス名の場合
      //ファイル名に空白がある場合は検出不可(そのようなファイルはないと仮定)
      if LPos = 0 then begin
        LFilePath := LStrList[0];
        LPos      := 1;
      end;  

      //パラメータ部分の文字列を作成
      for i := LPos to LStrList.Count - 1 do begin
        LParams := LParams + LStrList[i] + ' ';
      end;
    end;  
  finally
    FreeAndNil(LStrList);
  end;

  LFilePath :=  Trim(LFilePath);
  if Pos(' ', LFilePath) > 0 then begin
    LFilePath := AnsiQuotedStr(LFilePath, '"');
  end;
  LParams := Trim(LParams);

  //コマンド実行
  LResult := ShellExecute(Handle, '', PChar(LFilePath), PChar(LParams), nil, SW_SHOW);

  if LResult <= 32 then begin
    LText := SysErrorMessage(LResult);
    Windows.MessageBox(Handle, PChar(LText), '情報', MB_ICONWARNING);
  end;

  Close;
end;