Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2011年05月08日] [更新 2018年11月06日] Delphi 一般・その他
例外等のエラーとエラーメッセージ表示
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
[整理番号 - 0910]







try - except - end 構文による例外処理と例外メッセージ関係


主に try - except - end による例外処理と例外メッセージに関数る記事です.
try - except - end の構文と try - finally - end の構文を組み合わせて使用すると,より柔軟性のある例外処理が可能です.






01_[ デバッガ例外通知 ] ダイアログを非表示にする


Delphi の IDE を,デフォルトの設定で使用している場合,例外が発生すると,下図のような [デバッガ例外通知] のダイアログが現れることがあります.
このダイアログが現れると,プログラムは停止してしまいます.[継続] を選択すれば実行を継続できますが,プログラムはエラーのダイアログを表示して停止します.

この [デバッガ例外通知] ダイアログは,ビルド構成が Release の時は現れません.また,EXE を直接実行した場合も現れません.



図1
デバッガ例外通知

リスト1
例外でプログラムが停止する例
procedure TForm1.Button1Click(Sender: TObject);
var
  A : Integer;
  B : Integer;
begin
  A := 0;
  B := 123 div A;

  //ここは実行されない
  MessageBox(Handle, '終了', '結果', MB_OK);
end;


この [デバッガ例外通知] ダイアログが現れないようにするには,ダイアログの [この例外の種類を無視] をチェックしてダイアログを閉じます.あるいは,[ツール] [オプション] のダイアログを表示します.そして,[オプション] ダイアログの [言語固有の例外] の [言語の例外で通知] のチェックを外します.


図2

[言語の例外で通知] のチェックを外して実行すると,下図のダイアログが現れます.これは,[デバッガ例外通知] のダイアログで [継続] を選択した時と同じ動作です.EXE を直接実行した場合もこのダイアログが現れます.
このダイアログが現れて,プログラムは停止します.上のコードの MessageBox は実行されません.


図3
[言語の例外で通知] のチェックを外してもエラーでプログラムは停止する

[備考]
[デバッガ例外通知] のダイアログを [ブレーク] ボタンクリックで閉じた後,[Ctrl] + [F2] のキー押下でプログラムの実行終了となります.これは,IDE のメニューで [実行] [プログラムの終了] の操作と同じです.




02_例外でプログラムが停止しないようにする


アプリケーションを使用してるユーザにとっては,例外が発生して,プログラムが停止しては困る場合があります.
例外が発生してもでプログラムが停止しないようにするには,前項の説明のように,[デバッガ例外通知] のダイアログを非表示にします.そして,次のコードのように,try... except... end; というブロックを使用します.
try と except の間に書かれたコードで例外が発生すると,except の次の行が実行されます.この時プログラムの実行は停止しません.ただし,システム関係のエラーは try... except でも捕捉できない場合があります.



[備考]
本ページの例外処理のサンプルは Delphi XE (UP1) Pro で行っています.Delphi XE5 ではデバッガ例外通知のダイアログが表示されました.Delphi XE5 でも,EXE を直接実行した時は,デバッガ例外通知のダイアログは表示されません.
いずれにしても,Delphi XE と同じように except 部のコードは実行されます.プロジェクトの再構築 (ビルド) を何回か実行しているうちに,Delphi XE5 でも,デバッガ例外通知のダイアログが現れなくなりました.


図4
例外が発生してもプログラムは停止しない
  • 例外発生時に表示されていた [ゼロによる除算] のダイアログは表示されない

リスト2
例外でプログラムが停止しないコード
procedure TForm1.Button1Click(Sender: TObject);
var
  LValueA : Integer;
  LValueB : Integer;
begin
  LValueA := 0;
  try
    LValueB := 123 div LValueA;
  except
  end;

  MessageBox(Handle, '終了', '結果', MB_OK);
end;




03_例外が発生したら自前のダイアログを表示する


上のコードは [デバッガ例外通知] のダイアログが現れません.エラーのダイアログも現れないでプログラムの実行が継続されます.
このままプログラムが継続されると,元々エラーがあるまま実行されるため.別のエラーが発生してしまう可能性があります.
そこで,次のコードでは,例外が発生したらダイアログを表示します.ダイアログを閉じると次のコードが実行されます.実際には,エラーに対する何らかの対応処理が必要です.


図5
例外処理内で独自のダイアログを表示

図6
上の図のダイアログを閉じた場合

リスト3
例外が発生したら自前のダイアログを表示する
実際には,エラーに対する何らかの対応処理が必要
procedure TForm1.Button1Click(Sender: TObject);
var
  LValueA : Integer;
  LValueB : Integer;
begin
  LValueA := 0;
  try
    LValueB := 123 div LValueA;
  except
    MessageBox(Handle, 'エラーが発生しました', '情報', MB_ICONERROR or MB_OK);
  end;

  MessageBox(Handle, '終了', '結果', MB_OK);
end;




04_例外の内容によって処理を分岐


例外に内容によって処理を分岐にするには,例えば次のように,例外のクラス名で処理を分けます.
例外のクラスの種類については,SysUtils ユニットに一覧があります.


図7
文字列を数値に変換できなかった時の自前のダイアログ

図8
0 で除算した時の自前のダイアログ

リスト4
例外の内容で処理を分岐する方法
procedure TForm1.Button1Click(Sender: TObject);
var
  LValueA : Integer;
  LValueB : Integer;
  LValueC : Integer;
begin
  LValueA := 100;
  try
    LValueB := StrToInt(Trim(Edit1.Text));
    LValueC := LValueA div LValueB;
  except
    on EDivByZero do begin
      Application.MessageBox('0 では割れません', '情報', MB_ICONERROR);
    end;
    on EConvertError do begin
      Application.MessageBox('整数に変換できません', '情報', MB_ICONERROR);
    end;
    else begin
      Application.MessageBox('その他のエラー', '情報', MB_ICONERROR);
    end;
  end;
end;




05_例外クラス名とエラーメッセージ文字列とエラーコード取得


通常のアプリケーションでは,特定の機能を関数やメソッドとして実装します.関数の実行結果の戻値にエラー情報を出力するには,エラーの内容によって,関数の戻値を設定することになります.
そのためには,どのようなエラー,例外が発生するかを知る必要があります.下のコードは,例外が発生した際,例外のクラスと例外のエラーメッセージ文字列を表示する例です.



図9
例外クラス名とエラーメッセージの取得

リスト5
エラーの例外クラス名とエラーメッセージを取得する例
procedure TForm1.Button1Click(Sender: TObject);
var
  LValueA : Integer;
  LValueB : Integer;
  StrText : string;
begin
  LValueA := 0;
  try
    LValueB := 123 div LValueA;
  except
    on E: Exception do begin
      StrText := E.ClassName + sLineBreak + E.Message;
      Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
    end;
  end;
end;


例外が発生した時のエラーコードを取得するサンプルです.
例外が発生すると TExceptionRecord レコード型のメンバに発生したエラーに関する情報が格納されます.そのレコード型のメンバから取得できます.



図10
エラーコードの取得例

リスト6
TExceptionRecord レコード型のメンバからエラーコードを取得
procedure TForm1.Button1Click(Sender: TObject);
var
  LValueA    : Integer;
  LValueB    : Integer;
  LExceptRec : PExceptionRecord;
  LErrCode   : Integer;
begin
  Memo1.Lines.Clear;

  LValueA := 0;
  try
    LValueB := 123 div LValueA;
  except
    on E: Exception do begin
      //PExceptionRecordを取得してエラー番号を取得
      LExceptRec := EExternal(E).ExceptionRecord;
      LErrCode   := LExceptRec.ExceptionCode;

      //エラーメッセージとエラー番号を表示
      Memo1.Lines.Add(E.Message);
      Memo1.Lines.Add(E.ClassName);
      Memo1.Lines.Add('$' + IntToHex(LErrCode, 8));
    end;
  end;
end;




06_エラーコードとエラーメッセージの取得 - SysErrorMessage


次のコードでは,try ... except... end; を使用していません.Windows API の関数の多くは内部で例外処理を実行しています.発生したエラーの種類は関数の戻り値で判定できます.
実行結果を含めたエラーコードの値は,GetLastError 関数でも取得できます.また,エラーの内容を示す文字列は,SysErrorMessage 関数で取得できます.
このような関数だけを使用する場合,try ... except ... end; の構文は必要ありません.

サンプルのコードは,[468_各種システム設定ダイアログの表示] の canonical name を使用してダイアログを表示する例となっています.


[備考]
GetLastError 関数は,エラーが発生しなくても,関数内での処理結果の値を返すことがあります.
GetLastError 関数の値は,Ansi 版の関数と Unicode 版の関数とで異なることがあります.


図11
設計時画面
図12
Windows Vista と 7 での実行結果
図13
Windows XP での実行結果
  • Windows XP では,以下が存在しないためにこのエラーとなる
    '/name Microsoft.DeviceManager'

リスト7
Windows API 実行時のエラーコードとエラーメッセージ文字列の取得
実際には ShellExecute 関数は,その戻り値で結果を判定できる
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 
  ShellAPI;

{$R *.dfm}

//=============================================================================
//  [468_各種システム設定ダイアログの表示]の[03_canonical nameを使用した方法]
//  のダイアログをShellExecute関数で実行する例
//
//  Windows XPではダイアログを表示できない
//  Windows Vista, 7ではダイアログを表示する
//
//  ShellExecuteの利用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrExePath : String;
  LStrPara    : String;
  LIntErrCode : Integer;
  LStrErrMsg  : String;
begin
  LStrExePath := 'C:\Windows\system32\control.exe';
  LStrPara    := '/name Microsoft.DeviceManager';

  ShellExecute(Handle,
               'open',
               PChar(LStrExePath),
               PChar(LStrPara),
               nil,
               SW_SHOW);

  //最後に発生したエラーのコードを取得
  LIntErrCode := GetLastError;
  //エラーコードに該当するメッセージ文字列を取得
  LStrErrMsg  := SysErrorMessage(LIntErrCode);
  MessageBox(Handle, PChar(IntToStr(LIntErrCode) + '  ' + LStrErrMsg), '結果', MB_OK);
end;

end.


ShellExecute 関数は,その戻り値で関数が正常に実行されたかを判定できます.
次のコードは,意図的に存在しないファイル名を指定して実行した結果です.この場合はエラーとして検出できますが,パラメータの間違いなどでダイアログが表示されなくてもエラーとはなりません.


図14
実行結果

リスト8
ShellExecute 関数は戻り値で正常に動作したか判定できる
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrExePath : String;
  LStrPara    : String;
  LStrErrMsg  : String;
  LRetCode    : Integer;
begin
  LStrExePath := 'C:\aWindows\system32\control.exe';
  LStrPara    := '/name Microsoft.DeviceManager';

  LRetCode := ShellExecute(Handle,
                           'open',
                           PChar(LStrExePath),
                           PChar(LStrPara),
                           nil,
                           SW_SHOW);

  //ShellExecute関数の戻り値が32以下の時はエラー
  if LRetCode <=32 then begin
    //エラーコードに該当するメッセージ文字列を取得
    LStrErrMsg  := SysErrorMessage(LRetCode);
    MessageBox(Handle, PChar(LStrErrMsg), '結果', MB_OK);
  end;
end;


デバイス関係のエラーでは NTSTATUS エラーコードを返すことがあります.
この NTSTATUS エラーコードは RtlNtStatusToDosError 関数を使用すれば Windows の System Error コードの値に変換できます.System Error コードの値に変換できれば,SysErrorMessage 関数でエラーメッセージの文字列を取得することができます.
ただし,全ての NTSTATUS エラーコードが変換できるわけではありません.

以下は STATUS_NO_SUCH_FILE というエラーコードを変換する例です.
STATUS_NO_SUCH_FILE の System Error の値は ERROR_FILE_NOT_FOUND です.STATUS_NO_SUCH_FILE という定数名は Delphi XE には実装されていませんが,定数名 ERROR_FILE_NOT_FOUND は Windows.pas で定義されています.



図15
NTSTATUS エラーコードの値を Windows の System Error コードの値に変換

リスト9
NTSTATUS エラーコードの値を Windows の System Error コードに変換
RtlNtStatusToDosError 関数を使用
implementation

{$R *.dfm}

function RtlNtStatusToDosError(Status: LongWord): Integer;
  stdcall; external 'ntdll.dll';

//=============================================================================
//  NTSTATUSエラーコードをシステムエラーコードに変換
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  STATUS_NO_SUCH_FILE = $C000000F;
var
  LErrCode    : DWord;
  LSysErrCode : Integer;
  LErrText    : string;
begin
  Memo1.Lines.Clear;

  //テスト用NTSTATUSエラーコード
  LErrCode := STATUS_NO_SUCH_FILE;

  //NTSTATUSエラーコードをシステムエラーコードに変換
  LSysErrCode := RtlNtStatusToDosError(LErrCode);

  //エラーメッセージを取得
  LErrText := SysErrorMessage(LSysErrCode);

  //元のエラーコードと変換結果を表示
  Memo1.Lines.Add('$' + IntToHex(LErrCode, 8));
  Memo1.Lines.Add('$' + IntToHex(LSysErrCode, 8));
  Memo1.Lines.Add(LErrText);
end;




07_エラーコードとエラーメッセージの取得 - FormatMessage


FormatMessage 関数でもエラーコードからメッセージ文字列を取得できます.SysErrorMessage 関数は内部で FormatMessage 関数を使用して,エラーコードからメッセージ文字列を取得しています.
次のコードは,ERROR_NOT_SAME_DEVICE (17 $00000011) の値のエラーメッセージ文字列を取得して表示します.どちらのボタンクリックコードも同じメッセージ文字列となります.


図16
実行結果
  • ERROR_NOT_SAME_DEVICE のメッセージ文字列を表示

リスト10
FormatMessage 関数でエラーコードのメッセージ文字列を取得
SysErrorMessage 関数は内部で FormatMessage 関数を実行している
procedure TForm1.Button1Click(Sender: TObject);
var
  LRetCode : Integer;
  LRetBuff : array [0..MAX_PATH - 1] of Char;
begin
  LRetCode := ERROR_NOT_SAME_DEVICE;
  FillChar(LRetBuff, SizeOf(LRetBuff), #0);
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ARGUMENT_ARRAY,
                nil,
                LRetCode,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                @LRetBuff,
                SizeOf(LRetBuff),
                nil);
  MessageBox(Handle, LRetBuff, '結果', MB_OK);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LRetCode : Integer;
  LRetText : string;
begin
  LRetCode := ERROR_NOT_SAME_DEVICE;
  //SysErrorMessageは内部でFormatMessageを使用している
  LRetText := SysErrorMessage(LRetCode);
  MessageBox(Handle, PChar(LRetText), '結果', MB_OK);
end;




08_GetLastError 関数はエラー番号をリセットする


GetLastError 関数を実行すると,エラー番号が 0 にリセットされます.したがって,エラー番号を取得しようとして,再度 GetLastError 関数を実行するとエラーなしとなってしまいます.
そこで,上のコードでは GetLastError 関数で取得した値を別の変数に代入しています.そして,その変数の値を利用して処理しています.




09_アプリケーションレベルでのエラー検出


実際の使い道があるかは分かりませんが,アプリケーションレベル,全体でエラーを検出する方法もあります.
これは,TApplicationEvents コンポーネントの OnException イベントでエラーを検出します.


図17
設計時画面
図18
エラー表示

リスト11
TApplicationEvents の OnException イベントによるエラー検出
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
// アプリケーションレベルでのエラー発生例
// いい例を思いつかなかったのて...
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  StrToInt('');
end;

//=============================================================================
// エラーが発生した場合、このイベントハンドラ内のコードが実行される
//=============================================================================
procedure TForm1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
begin
  MessageBox(Handle, PChar(E.Message), 'エラー', MB_OK);
end;

end.




10_「未定義」や「ファイルが見つかりません」のエラー


ここまでは,実行時のエラーに対する対処方法でした.
「未定義のエラー」や「ファイル ... が見つかりません」のエラーは,コンパイル時に発生するエラーです.コミュニティの場や筆者への問い合わせでよく見かける質問の 1 つです.多くの場合,
  • 変数や関数を定義しないで使用している
  • 存在しない関数やプロパティ,あるいはメソッド名,イベント名を使用している
  • 必要なファイル名 (ユニット名) が uses 部にない
  • uses 部にファイル名 (ユニット名) はあるが,それが存在しないかパスが通っていない

等が原因で発生します.未定義のエラーが発生した場合,考えてみれば当たり前ですが,問題の解決のためには,どの部分で発生して,何が「未定義」なのか,という情報が必要です.
このようなエラーが発生する原因は,環境によって異なる場合があります.開発環境や実行環境の情報も必要です.

プロジェクトの中には,ユニットをプロジェクトに追加しているものがあります.当サイトで提供しているプロジェクトにもあります.そのユニットがプロジェクトのソースコードに書かれている場所にないと,「ファイル...が見つかりません」のエラーが発生します.
その場合は,そのユニットをパスの通った場所に置いて,プロジェクトから削除します.あるいは,そのユニットを一度プロジェクトから削除して,再度プロジェクトに追加します.
プロジェクトへの追加や削除は,メニューの [プロジェクト] から操作できます.



[備考]
「ファイル ... が見つかりません」のエラーは,実行時にも発生する場合があります.それは実際にそのファイル名のファイルが存在しない場合です.




11_コンパイルのエラーメッセージ等のコピー ( エラー報告用 )


開発時にエラーが発生して,自身で解決できない場合,コミュニケーションの場で訊きたいことがあります.その際,単に「うまくいきません」とか「エラーが発生します」だけでなく,具体的な内容を書き込めば,より詳しく状況を他の方に伝えることができます.
それには,発生したエラーメッセージの文字列をコピーしたものを書くことです.他の方に教えてもらうのですから,このぐらいの手間は惜しむべきではないでしょう.

エラーというのは,最初に発生したエラーが原因で次々とエラーが発生することもあります.最初のエラーが重要なカギとなることが多いようです.



図19
Delphi 7 以前
  • メニューでは直接コピーはできない
  • 一度テキストファイルに保存
  • これは全てのメッセージの保存となる
  • [Ctrl] + [C] であれば選択中のメッセージだけのコピーが可能
図20
Delphi 2005 以降
  • メニューでも選択したメッセージのみのコピーが可能
  • [Ctrl] + [C] でも同じ
  • [Ctrl] + [A] で選択して [Ctrl] + [C] だと全てのメッセージのコピー
図21
エディタへの貼付例
  • 最初の「未定義の識別子」が原因で次のエラーが発生している例

Delphi では,表示ダイアログのテキストも [Ctrl] + [C] でコピーすることができます.


図22
ダイアログの例

上のダイアログを [Ctrl] + [C] のキー操作でコピーして,テキストエディタに貼り付けると以下のようになります.報告する時は,当然ですが,コピーしたものを丸ごと書くのではなく,不必要と思われる部分は削除すべきです.
掲示板等で質問をする場合は,テキストエディタで下書きするといいかも知れません.


[Window Title]
Project1

[Content]
$00000002
指定されたファイルが見つかりません。

[OK]


[デバッガ例外通知] ダイアログの場合は,次のようなテキストになります.


---------------------------
デバッガ例外通知
---------------------------
プロジェクト Project2.exe は例外クラス $C0000094 (メッセージ 'integer divide by zero at 0x004ae4c7')を送出しました。
---------------------------
ブレーク(B) 継続(C) ヘルプ
---------------------------




12_Runtime error 216


このエラーは無効なメモリ領域にアクセスした場合に発生します.Runtime,つまり実行時エラーなのですが,厄介なのは,このエラーのダイアログがプログラムの実行中ではなく,EXE の実行終了後に現れるということです.

もし,コードの変更を一切していないのにこのエラーが発生した場合は,該当プログラム以外が原因の可能性があるようです.自アプリ以外のアプリケーションやプログラムでも発生するのか調べてみる必要があるかも知れません.



図23
Runtime error 216 の例
  • 実行時に生成したフォームの解放処理のミスで発生




13_try - except で捕捉できない例外 - ネイティブ OS 例外


全ての例外が try - except で捕捉できるわけではありません.ネイティブ OS 例外と呼ばれる例外は try ~ except では捕捉できません.
下に示すコードは配列の範囲外の要素に値を代入する例です.実行すると,下図のようなメモリアクセス違反の例外は発生しますが,except 部のコードは実行されません. このデバッガ例外通知の例外ダイアログは,OS 例外のダイアログです.



図24

上のダイアログで [継続] ボタンをクリックすると,今度は下図のダイアログが現れます.
このダイアログは VCL 例外のダイアログです.



図25
VCL 例外ダイアログ

リスト12
配列の範囲外の要素に値を代入する例.例外 ($C0000005) が発生する
procedure TForm1.Button1Click(Sender: TObject);
var
  LArrInt : array [0..1] of Integer;
  LIndex  : Integer;
begin
  LIndex := -6;
  try
    LArrInt[LIndex] := 50;

    Abort;
  except
    // ここは実行されない
    MessageBox(Handle, '例外発生', '情報', MB_ICONWARNING);
  end;
end;


次のサンプルはスタックオーバーフローを発生するコードの例です.これも実行すると OS 例外が発生して下図のデバッガ例外通知のダイアログが現れます.このダイアログを [継続] で閉じると,上のテストコードと同じように VCL 例外のダイアログが現れます.そのダイアログを閉じると CPU ウィンドウが表示されます.
このサンプルでも except 部のコードは実行されません.



図26

リスト13
スタックオーバーフロー例外 ($C00000FD) が発生する例
procedure HogeHoge;
begin
  HogeHoge;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    HogeHoge;
  except
    // ここは実行されない
    MessageBox(Handle, '例外発生', '情報', MB_ICONWARNING);
  end;
end;


ネイティブ OS 例外の種類は IDE から確認できます.メニューの [ツール] [オプション] のダイアログで [ネイティブ OS 例外] を選択すると表示されます.
配列の範囲外の要素への代入の例外は $C0000005 ですからアクセス違反,つまりメモリアクセスの違反であることが確認できます.スタックオーバーフローの場合は $C00000FD となっています.

これらは,例外の項目を選択して設定をダブルクリックするか,下部のラジオボタンで設定を変更できます.ただし,Delphi 全体の設定となります.プロジェクト単位ではありません.これらの例外を無視してプログラムを実行するようにすると,予期しない実行結果となる可能性があることも考えておく必要があります.



図27
OS 例外




14_Win32Check 関数や OleCheck 関数による例外の捕捉


以上のようなネイティブ OS 例外は try ~ except で捕捉できませんが,Win32Check 関数を使用すると try ~ except で捕捉可能な VCL 例外を発生させることができことがあります.
また,OleCheck 関数を使用すると,COM 関係の操作が失敗した時に例外を捕捉することができます.
Win32Check 関数の引数は論理値です.Windows API の関数を実行する際,論理値として実行結果が取得できれば例外を発生させることが可能です.

次のコードは ShellExecute 関数の実行が失敗した時に VCL 例外を発生させる例です.ShellExecute 関数はその戻り値で実行結果を取得できますから,実際にはこのようなことは必要ありません.



図28
try ~ except で例外を捕捉した例

リスト14
ShellExecute 関数の実行で VCL 例外を発生させる例
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath : string;
begin
  LFilePath := 'HogeHoge.exe';
  try
    Win32Check(ShellExecute(Handle, '', PChar(LFilePath), nil, nil, SW_SHOW) > 32);
  except
    ShowMessage(SysErrorMessage(GetLastError));
  end;
end;




15_Windows Error Report ( WER ) による強制終了を回避


OS 例外が発生するサンプルコードを,非デバックの状態,つまり,作成した EXE を直接実行すると,下図のような Windows Error Report ( WER ) のダイアログが現れることがあります.このダイアログで [キャンセル] をクリックするとプログラムが強制終了します.終了しないでハングアップすることもあります.
当然ですが,ここでマイクロソフトに [情報を送信] しても全く意味がありません.今回のエラーの原因はプログラマ自身のコーディングのミスなのですから.

SetUnhandledExceptionFilter 関数を使用すると,この WER のダイアログを表示しないようにすることができます.ただし条件があります.OS 例外で使用したサンプルではできません.



図29
EXE を直接実行した場合
  • [キャンセル] をクリックするとプログラムが終了する
図30
上のダイアログで [キャンセル] しなかった場合
  • WER (Windows Error Report) のダイアログが現れる
  • プログラムは強制終了となる

SetUnhandledExceptionFilter 関数を使用して WER のダイアログを表示しないようにするためには,例外が発生しても except 部のコードが実行されることが必要条件です.この場所は,WER のダイアログが表示されたとした場合に,ダイアログを閉じた時の行先です.

以下のコードはメモリアクセス違反の例外 ($C0000005) が発生して,デバッガ例外通知のダイアログが現れますが,[継続] をクリックすると except 部のコードが実行されます.しかし,デバッグ時にデバッガ例外通知のダイアログが表示される場合,EXE を直接実行すると上の図のような WER のダイアログが現れます.
このようなプログラムであれば WER のダイアログを表示しないようにすることができます.



リスト15
メモリアクセス違反の例外が発生するが,except 部が実行される例
procedure TForm1.Button1Click(Sender: TObject);
var
  LpInt : PInteger;
begin
  try
    LpInt := AllocMem(256);
    try
      LpInt := nil;
      LpInt^ := 0;
    except
      MessageBox(Handle, '問題発生 !!', '情報', MB_ICONWARNING);
    end;
  finally
    FreeMem(LpInt);
  end;
end;


まず JITEnable (ジャストインタイムデバッガの設定) を 1 または 2 にします.SetErrorMode 関数でエラー処理モードの設定をします.そして,SetUnhandledExceptionFilter 関数のコールバック関数内に処理を書きます.
以下が具体的なコードです.
コンパイルして作成した EXE を直接実行して [Button1]] をクリックするとexcept 部が実行されます.WER のダイアログは表示されません.プログラムも終了しません.



リスト16
WER のダイアログを表示しないで,プログラムも終了させない例
//-----------------------------------------------------------------------------
//  SetUnhandledExceptionFilter関数のコールバック関数
//-----------------------------------------------------------------------------
function WERDigThrough(const ExpInfo: TExceptionPointers): Integer; stdcall;
const
  EXCEPTION_EXECUTE_HANDLER = 1;
  EXCEPTION_CONTINUE_SEARCH = 0;
begin
  Result := EXCEPTION_EXECUTE_HANDLER;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  JITEnable := 1;
  SetErrorMode(SEM_FAILCRITICALERRORS);
  SetUnhandledExceptionFilter(@WERDigThrough);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LpInt : PInteger;
begin
  try
    LpInt := AllocMem(256);
    try
      LpInt := nil;
      LpInt^ := 0;
    except
      MessageBox(Handle, '問題発生 !!', '情報', MB_ICONWARNING);
    end;
  finally
    FreeMem(LpInt);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  LInfoText : string;
begin
  LInfoText := 'こちらの処理は正常に動作します';
  MessageBox(Handle, PChar(LInfoText), '情報', MB_ICONINFORMATION);
end;


例外が発生した場合にプログラムを終了したいのであれば,例えば SetUnhandledExceptionFilter 関数のコールバック関数の処理を以下のようにしても対応できます.



図31
WER のではなく,独自のダイアログを表示

リスト17
WER のダイアログを表示しないで,独自ダイアログを表示してプログラムを終了する例
function WERDigThrough(const ExpInfo: TExceptionPointers): Integer; stdcall;
const
  EXCEPTION_EXECUTE_HANDLER = 1;
  EXCEPTION_CONTINUE_SEARCH = 0;
var
  LInfoText : string;
begin
  LInfoText := '問題が発生しました' + sLineBreak + 'プログラムを終了します';
  MessageBox(0, PChar(LInfoText), 'お詫び', MB_ICONWARNING);
  TerminateProcess(GetCurrentProcess, 0);

  Result := EXCEPTION_EXECUTE_HANDLER;
end;


この「この問題に関する詳細情報を送信しますか ?」の WER ダイアログは Windows の [問題レポートの設定] で非表示にすることができます.
[ファイル名を指定して実行] で以下のコマンドを実行します.表示されるダイアログで [すべてのユーザのレポート設定を変更] をクリックします.そのダイアログで [解決策を確認しない (推奨しません) ] を選択します.


%SystemRoot%\System32\control.exe /name Microsoft.ActionCenter /page pageSettings

ただし,上の図のダイアログを表示しない設定にすると,今度は下図のような「動作を停止しました」と言うダイアログが現れるようになることがあります.
もちろん「プログラムを終了します」をクリックします.



図32
動作停止のダイアログ