.NET CLASSROOM
Ken Getz
スタック トレースへの反映
ほとんどの開発者がそうであるように、読者もまたアプリケーションを導入したところ障害が発生してしまったという経験があるでしょう(もちろん、原因は常にユーザーにあるのですが)。何がうまくいかず、どうしてそうなったのかを示すエラー ログを作成するには、スタック トレースを作成する何らかのメカニズムが必要です。スタック トレースとは、現在のエラー条件に至るまでにたどったプロシージャを示すものです。Visual Basic(R) のこれまでのバージョンには、[呼び出し履歴]ダイアログ ボックスという時代遅れの代物が付属していました。このダイアログ ボックスは、本格的なデバッグには役に立ちません。他の開発環境には、エラー条件を扱う独自のツールが付属しています。しかし、特に Visual Basic を使用するプログラマは、完全なスタック トレース情報を自由に取得できるメカニズムを切望していました。
Microsoft(R) .NET の世界では、求めていたツールがついに利用できるようになりました。System.Reflection ネームスペースの一部として提供されるクラスのおかげで、プロシージャ名、パラメータ情報、および( Microsoft 中間言語内のオフセットのレベルに至るまでの)その他の情報を取得できるのです。これらすべてを実現するのが、StackTrace クラスと、それに関連する StackFrame、MethodBase、ParameterInfo の各クラスです。
StackTrace 情報の表示
たとえば、Visual Basic.NET ベースのアプリケーションで例外が発生したときに、コンソール ウィンドウに情報を書き出し、例外が発生した時点でのコール スタックの状態を示したいとします。もちろん、Visual Basic 6.0 の開発システムでは、こんなことはできませんでした。Visual Basic.NET では簡単です。StackTrace オブジェクトは、StackFrame オブジェクトのイン デックス付きリストを提供します。各項目は、プロシージャ コールのスタックへの 1 回の「プッシュ」を表します。
StackFrame クラスは、オーバーロードされた複数のコンストラクタを提供します。これは、スタック フレームには何通りもの使い方があるからです。現在の位置からスタックを追跡したいこともありますが、一般には、エラーが発生したときのスタックの状態を、コール スタックを何レベルもさかのぼったエラー ハンドラから調べたい場合の方が多いでしょう。また、ファイル情報を含めたい場合もあるでしょう。それには、プロシージャ名だけの場合よりも多くの労力とデバッグ情報が必要です。各コンストラクタは、これらのそれぞれのケースに対応します。
次のようなコードを呼び出し側のプロシージャの Catch ブロックに追加すると、エラーが発生した時点でスタックを追跡できます( True パラメータは、ファイル情報も必要であることを示します。追加情報が不要な場合は、このパラメータを False に設定するか、無視します)。
Imports System.Reflection
Sub Main()
Try
' どこかのポイントでエラーを引き起こす
' プロシージャを呼び出す
AProcThatThrowsAnException()
Catch e As Exception
Dim st As StackTrace = _
New StackTrace(e, True)
End Try
End Sub
この StackTrace オブジェクトを設定したら、FrameCount プロパティを使って、エラーが発生した場所より上のスタック フ レームの数を取得することができます。この情報があれば、すべてのスタック フレームをループ処理できます。各スタック フレームごとに、MethodBase オブジェクトを返す GetMethod メソッドを呼び出して、特定のスタック フレーム内のメソッドの任意のプロパティを取得することができます。これには、Name プロパティも含まれます。
Dim fc As Integer = st.FrameCount
Dim i As Integer
Dim sf As StackFrame
For i = 0 To fc - 1
sf = st.GetFrame(i)
Console.WriteLine(sf.GetMethod.Name)
Next i
これまでに示したコードを実行すると、エラーが発生したポイントまでに呼び出したプロシージャの名前のリストが得られます。その他の情報が必要な場合もあるでしょう。MethodBase オブジェクトは、有益なデータを多数提供します。DeclaringType プロパティを使うと、メンバが宣言されていた型を返し、そこからネームスペースを取得することができます(複数のモジュールまたはネームスペースにわたってデバッグを実行する場合に便利です)。
GetParameters メソッドを使うと、メソッドのすべてのパラメータを示すインデックス付きのリストを取得することができます。リストの各項目は ParameterInfo オブジェクトです。
このオブジェクトは、各パラメータに関する有益な情報をすべて提供します。たとえば、パラメータの名前、型、オプションのパラメータかどうか、値渡しか参照渡しかなどの情報です。スタックのダンプにパラメータの名前と型を含めたい場合は、コードを図 1のように修正します。
図 1 : Microsoft Excel データに対応する XML
Sub Main()
Try
AProcThatThrowsAnException()
Catch e As Exception
' 例外が発生したポイントから
' スタック情報を取得する
' 新しい StackTrace を作成し、ファイル情報を要求する
Dim st As StackTrace = New StackTrace(e, True)
Console.WriteLine("Stack Trace:")
Dim fc As Integer = st.FrameCount
Dim i As Integer
Dim sf As StackFrame
Dim pi As ParameterInfo
Dim strParams As String
For i = 0 To fc - 1
sf = st.GetFrame(i)
Console.Write(sf.GetMethod.Name & "(")
Dim piList() As ParameterInfo = _
sf.GetMethod.GetParameters()
strParams = ""
For Each pi In piList
strParams = strParams & ", " & _
pi.Name & " As " & pi.ParameterType.Name
Next pi
If strParams.Length > 2 Then
Console.Write(strParams.Substring(2))
End If
Console.WriteLine(")")
Next i
Console.ReadLine()
End Try
End Sub
最初のプロシージャ コールによって、さまざまなパラメータを持つ B、C、D という名前のプロシージャが順に呼び出される場合(例外を発生させるプロシージャは D )、このコードを実行すると、コンソール ウィンドウの出力は次のようになります。
Stack Trace:
D(x As String)
C(x As String, y As Int32)
B()
AProcThatThrowsAnException()
Main()
必要に応じて、スタック フレームの GetFileName メソッドと GetFileLineNumber メソッドを使い、コール スタックがプッシュされた時点のファイル名と行番号に関する情報を追加することもできます。それには、次のようなループをエラー ハンドラに追加します。
Dim strFileName As String
strFileName = sf.GetFileName()
If strFileName.Length > 0 Then
Console.WriteLine(" at line " & _
sf.GetFileLineNumber() & " in " &_
strFileName)
End If
お気づきかもしれませんが、この情報は、アプリケーションのデバッグ バージョンをビルドした場合にのみ利用可能です。リリース ビルドでは、ファイル名と行番号の情報は実行可能ファイルに含まれません。
結 論
.NET プラットフォームが提供する機能を利用すれば、コードの実行に関して知っておかなければならないことをほぼすべて判断できます。.NET の多くのシステム クラスと同じく、StackTrace クラスと StackFrame クラスも新鮮で驚くほど奥が深いものです。これらのクラスをじっくり調べてみてください。これまでよりも格段に豊富なデバッグ情報が得られるでしょう。
Ken Getz は、MCW Technologies のシニア コンサルタントで、プログラミング、執筆、トレーニングを手がける。