こんにちは!現場でバリバリとWindows Formsアプリを開発していると、避けて通れないのが「予期せぬ例外(エラー)による突然の強制終了」ですよね。
ユーザーがせっかく入力したデータがパーになったり、現場の担当者から「なんか急に画面が消えたんだけど!」と冷や汗モノの連絡が来たり……エンジニアなら誰もが一度は頭を抱えた経験があるはずです。
今回は、Visual Basic (VB.NET) のWindows Formsアプリケーションにおいて、アプリの「お守り」となるグローバル例外ハンドリング(エラーフック機構)の極意を伝授します。
「ThreadException」と「UnhandledException」の決定的な違いをマスターし、現場で即戦力となる堅牢なエラー処理を一緒に作り上げていきましょう!ここをクリアすれば、あなたのアプリの信頼性は段違いに跳ね上がりますよ。
—
1. なぜ「グローバル例外ハンドリング」が必要なのか?
通常のプログラミングでは、エラーが起きそうな場所を `Try…Catch` ステートメントで囲みますよね。
.net
Try
‘ ファイル読み込みなどの処理
Dim data As String = File.ReadAllText(“C:\settings.txt”)
Catch ex As Exception
‘ エラー処理
MessageBox.Show(ex.Message)
End Try
しかし、開発規模が大きくなると、すべてのボタンクリック、すべてのバックグラウンド処理、すべてのイベントで `Try…Catch` を完璧に書き忘れない……というのは実質的に不可能です。
どこかでキャッチし忘れた例外が発生すると、アプリは容赦なく「フリーズ」または「強制終了」し、Windowsの冷たいエラーダイアログが表示されてしまいます。これを防ぐために、「アプリ全体のどこかでキャッチされなかったエラーを、最後にまとめて網にかける巨大なセーフティネット」がグローバル例外ハンドリングです。
—
2. 2つの網:ThreadException と UnhandledException の使い分け
VB.NETのWindows Formsには、エラーをキャッチするための主要なフックが2つ存在します。ここが今回の最重要ポイントです。
1. `Application.ThreadException` (UIスレッド例外)
- 対象: ボタンクリックや画面描画など、人間が操作するUIスレッド上で発生した未処理の例外。
- 特徴: イベントハンドラの中で発生したエラーをキャッチします。ここでアプリを終了させるか、そのまま継続させるかを選ぶことができます。
2. `AppDomain.CurrentDomain.UnhandledException` (非UIスレッド / 全ドメイン例外)
- 対象: バックグラウンドスレッド(`Task` や `Thread`)で発生した例外、またはUIスレッドをすり抜けた致命的な例外。
- 特徴: いわゆる「ラストチャンス」です。このイベントが発火した時点で、アプリは強制終了する運命にあります。ここでは終了直前のログ保存や管理者への通知を行います。
ざっくり言うと、どう使い分ける?
- UIの操作ミスや軽微なエラー: `ThreadException` でキャッチし、メッセージを出して「アプリを続行する」道を残す。
- 裏で動くスレッドの暴走や致命傷: `UnhandledException` でキャッチし、ログを吐き出して「安全にシャットダウン」する。
この2つを両方張ることで、初めて鉄壁の守りが完成します。
—
3. 実践!堅牢なエラーフック機構の実装コード
それでは、実際のVB.NETコードを見ていきましょう。
プロジェクトのスタートアップ(アプリが起動する瞬間)を担当する `ApplicationEvents.vb` または `Program.vb` (Sub Main)に記述します。
今回は、Windows Formsの標準的なエントリポイントである `Sub Main` を使った実装例をご紹介します。
.net
Imports System.Threading
Imports System.IO
Module Program
”’
”’
Sub Main()
‘ 1. UIスレッドの例外キャッチを設定
AddHandler Application.ThreadException, AddressOf UIThreadExceptionHandler
‘ 2. 非UIスレッド・全般の致命的例外キャッチを設定
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf UnhandledExceptionHandler
‘ 3. Windows Formsの視覚スタイルと設定を有効化
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
‘ 4. メイン画面の起動
Application.Run(New MainForm())
End Sub
”’
”’
Private Sub UIThreadExceptionHandler(sender As Object, e As ThreadExceptionEventArgs)
Dim ex As Exception = e.Exception
‘ ログ出力と管理者通知の処理(共通メソッドを呼ぶイメージ)
LogException(ex, “UI Thread Exception”)
‘ ユーザーに優しく通知し、アプリを継続させるか終了させるか判断を仰ぐ
Dim result As DialogResult = MessageBox.Show(
$”予期せぬエラーが発生しました。{Environment.NewLine}{ex.Message}{Environment.NewLine}{Environment.NewLine}作業を続行しますか?”,
“システムエラー”,
MessageBoxButtons.YesNo,
MessageBoxIcon.Error)
If result = DialogResult.No Then
‘ 安全に終了
Application.Exit()
End If
End Sub
”’
”’
Private Sub UnhandledExceptionHandler(sender As Object, e As UnhandledExceptionEventArgs)
Dim ex As Exception = CType(e.ExceptionObject, Exception)
‘ 致命的なのでログに必ず残す
LogException(ex, “FATAL: Unhandled Exception”)
MessageBox.Show(
“アプリケーションで致命的なエラーが発生しました。データを保存して終了します。”,
“致命的エラー”,
MessageBoxButtons.OK,
MessageBoxIcon.Stop)
End Sub
”’
”’
Private Sub LogException(ex As Exception, errorType As String)
Try
Dim logPath As String = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, “error.log”)
Dim logMessage As String = $”{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{errorType}] {ex.Message}{Environment.NewLine}{ex.StackTrace}{Environment.NewLine}————————————————–{Environment.NewLine}”
‘ ログファイルに追記
File.AppendAllText(logPath, logMessage)
Catch
‘ ログ書き込み自体が失敗した場合は無限ループを防ぐために握りつぶすかイベントビューアに書く
End Try
End Sub
End Module
—
4. コードの解説と「現場の知見」
① `AddHandler` によるイベントのフック
VB.NETでは、`AddHandler` を使うことで、システムの裏側で起きるイベントを自由につかまえることができます。アプリが起動した瞬間にこの網を張ることで、どこでエラーが起きても逃がさない体制を作ります。
② 無限ループや二次災害を防ぐ工夫
`LogException` メソッドの中を見てください。もし「ハードディスクの容量がいっぱいでログが書き込めない!」という状況のとき、ログ書き込み処理の中でまたエラー(例外)が発生してしまいます。
そのため、エラーログの書き込み自体をさらに `Try…Catch` で囲み、最悪の場合はアプリがクラッシュしないような防護壁(防御的プログラミング)を張るのがプロの技です。
③ ユーザー体験(UX)への配慮
いきなりアプリがプツンと消えるのは、ユーザーにとって最大のストレスです。
`ThreadException` の方では、「作業を続行しますか?」と聞くことで、ユーザーが入力中のデータを保持したまま何とか画面を閉じられる最後のチャンスを提供できます。
—
5. 初学者が陥りやすい罠と注意点
- 「とりあえず全部 Try…Catch で囲めばいいや」の罠
すべてのコードを `Try…Catch` で囲んで例外を握りつぶしてしまう(中身を空っぽにする)と、「どこで何が起きて動かなくなったのか」が完全に闇の中になってしまいます。エラーは隠すのではなく、「適切に捕まえて記録する」のが正解です。
- デバッグ時の挙動に注意
Visual Studioでデバッグ(F5実行)している最中は、ここで設定したグローバルハンドラよりも、デバッガの停止処理が優先されることがあります。「あれ?コード書いたのにブレークポイントで止まらないぞ?」と思ったら、デバッグなしで実行(Ctrl + F5)するか、設定を確認してみてください。
—
まとめ
いかがでしたでしょうか?
Visual Basic (VB.NET) でのWindows Formsアプリケーション開発において、グローバル例外ハンドリングは、アプリの「品質」と「信頼性」を担保するための極めて重要なアーキテクチャです。
- UIスレッドの例外は `Application.ThreadException` でキャッチし、継続の選択肢を与える。
- 致命的な例外は `AppDomain.CurrentDomain.UnhandledException` で確実にログに残す。
ここをしっかり押さえておけば、現場で「突然アプリが落ちて原因不明です」と慌てることはもうありません。ぜひあなたのプロジェクトにも取り入れて、ワンランク上の堅牢なアプリケーションを作り上げてくださいね!
