本来はイベント毎にTry Catch構文で例外を捕まえたらいいのでしょうが、なかなかそうはいかないこともあります。
デバッグ中にハンドルされない例外が発生すると下記のようになります。
デバッグ中でない場合に実行すると下記のようにエラーダイアログがでます。
さて、.Netにはこのようにハンドルされない例外を捕まえる方法があります。
その方法には下記の2つがあるようでです。
1. Application.ThreadExceptionイベントの活用
2. Thread.GetDomain().UnhandledExceptionイベントの活用
Application.ThreadExceptionはメインスレッドのハンドルされていない例外のみ捕まえますが、Thread.GetDomain().UnhandledExceptionはメインスレッド以外のスレッドや、コンソールアプリケーションの例外も捕まえれるようです。
詳しくは参考もとの@IT 適切に処理されなかった例外をキャッチするには?と捕捉されなかった例外がスローされたことを知るに載っています。
さて、これらのイベントハンドラをどこで定義するかですが、@ITの例だとVB.Netの場合 Sub Main() で定義しています。
Sub Main() というのは C とかのMain関数と同じで、VB.Netのエントリーポイント(プログラムの実行を開始する場所)のことっぽいですね。
ところが、VB2005からは アプリケーションフレームワーク たるものが導入されており、これが有効になっていると Sub Main() はコンパイル時に自動で生成されるため、開発者が勝手に Sub Main() を作れません。
(アプリケーションフレームワークの設定はプロジェクトのプロパティ→アプリケーションのところにあります。これがあると2重起動を簡単にやめさせることができます)
今回はアプリケーションフレームワークは欠かせせないのでこれを有効にしたままハンドルされない例外を捕まえるイベントハンドラを定義したかったので、スタートアップフォームのコンストラクタに書くことにしました。
下記がそのソースです。
ついでに、例外発生時は Windows のイベントビュアーにエラーイベントログとして書き出す処理も入れています。
Public Class スタートアップフォーム
Public Sub New()
' この呼び出しは、Windows フォーム デザイナで必要です。
InitializeComponent()
' InitializeComponent() 呼び出しの後で初期化を追加します。
' ThreadExceptionイベント・ハンドラを登録する
AddHandler Application.ThreadException, AddressOf Application_ThreadException
' UnhandledExceptionイベント・ハンドラを登録する
AddHandler System.Threading.Thread.GetDomain().UnhandledException, AddressOf Application_UnhandledException
End Sub
'''
''' 未処理例外をキャッチするイベントハンドラ。
''' メインスレッド用。(WindowsForm専用)
'''
'''
'''
'''
Public Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs)
'メッセージボックス表示
MessageBox.Show(e.Exception.Message)
'イベントログ出力
OutPutLogErr(e.Exception)
'予期せぬ例外時は強制終了
Environment.Exit(-1)
End Sub
'''
''' 未処理例外をキャッチするイベントハンドラ。
''' 別スレッド用。
'''
'''
'''
'''
Public Shared Sub Application_UnhandledException(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
Dim ex As Exception = CType(e.ExceptionObject, Exception)
If Not ex Is Nothing Then
'メッセージボックス表示
MessageBox.Show(ex.Message)
'イベントログ出力
OutPutLogErr(ex)
'予期せぬ例外時は強制終了
Environment.Exit(-1)
End If
End Sub
'''
''' エラーログをイベントビュアーに出力
'''
''' Exceptionオブジェクト
'''
Private Shared Sub OutPutLogErr(ByVal e As Exception)
Try
'ソース
Dim sourceName As String = "テストアプリケーション"
'ソースが存在していない時は、作成する
If Not System.Diagnostics.EventLog.SourceExists(sourceName) Then
'ログ名を空白にすると、"Application"となる
System.Diagnostics.EventLog.CreateEventSource(sourceName, "")
End If
'テスト用にイベントログエントリに付加するデータを適当に作る
Dim myData() As Byte = {}
Dim msg As String = "例外:" & vbNewLine & e.Message & vbNewLine & "例外スタックトレース" & vbNewLine & e.StackTrace & vbNewLine
If e.InnerException IsNot Nothing Then
msg = msg & "InnerException:" & vbNewLine & e.InnerException.Message & vbNewLine & "InnerExceptionスタックトレース:" & vbNewLine & e.InnerException.StackTrace
End If
'イベントログにエントリを書き込む
'ここではエントリの種類をエラー、イベントIDを1、分類を1000とする
System.Diagnostics.EventLog.WriteEntry(sourceName, msg, System.Diagnostics.EventLogEntryType.Error, 1, 1000, myData)
Catch ex As Exception
End Try
End Sub
End Class
ちなみに、Visual Studioで普通にデバッグ開始すると、最初のSSのように、例外が起こったソースの箇所で処理が止まります。
なので、Application.ThreadExceptionイベント、Thread.GetDomain().UnhandledExceptionイベントを捕まえるテストするには、Viual Studioのメニューでデバッグ→デバッグなしで開始を選択するとできます。
イベントログには下記のように出力されます。
参考: