KEN's .NET

[IL4] tryしてみよう

ホーム > KEN's .NET > [IL4] tryしてみよう

ここでは.NET Frameworkのアセンブリ言語MSIL(Microsoft Intermediate Language)を使ったプログラミングを紹介します。 読者は.NET プログラミング経験があることを想定しています。

1. はじめに

今までの「計算しよう」シリーズで放置してきた例外への対処をしたいと思います。 いわゆるtry〜catch〜finally(C#)、Try〜Catch〜Finally(VB)のキーワードで知られる構造化例外処理を行います。

2. .try〜catch

ILではtry(C#)/Try(VB)の代わりに.tryディレクティブを用いて例外処理を書きます。 .tryキーワードを用いた例外処理の典型的なパターンを図1のプログラム(TryCatch.il)で示します。

図1 .try〜catchの使用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.assembly TryCatch {}
.method public static void main()
{
    .entrypoint
    .maxstack 2
    .try
    {
        ldc.i4 0x7FFFFFFF
        ldc.i4.1
        add.ovf
        call void [mscorlib]System.Console::WriteLine(int32)
        leave fin
    }
    catch [mscorlib]System.OverflowException
    {
        call void [mscorlib]System.Console::WriteLine(object)
        ldstr "↑の例外が発生しました"
        call void [mscorlib]System.Console::WriteLine(string)
        leave fin
    }
fin:
    ret
}
System.OverflowException: 演算操作の結果オーバーフローが発生しました。
   at main()
↑の例外が発生しました
図2 .try〜catchの使用例の実行結果

このプログラムを説明すると以下のようになります。

  • 6行目の.tryディレクティブで例外処理を行うことを指定します。
  • 7〜13行目の{}で囲まれた部分をtryブロック、または、保護ブロックと呼びます。
  • 8〜10行目でint32の最大値+1の加算を行い、わざとオーバーフローが起きるようにします。
  • 10行目の命令で例外が発生し、処理は14行目のcatch句へと移ります。 catch句の後ろには補足する例外オブジェクトの型を指定します。 ここではmscorlib.dllのSystem.OverflowExceptionを指定しています。
  • 15〜20行目はこのcatch句に対するハンドラになっています。
  • 補足した例外は例外機構によりスタックに置かれるため、16行目のConsole::WriteLineメソッドで例外オブジェクトの内容をコンソールに表示します。
  • 19行目まで処理が進むとleave命令によりcatch句から抜けて"fin"という名前を付けたラベルの箇所(21行目)にジャンプします。
このプログラムで重要な点を補足しておきます。
  • tryブロックやcatchブロックから抜けるためにleave命令を使う必要があります。 C#のように{}で括られた表現になっていますが、「}」の箇所に来たからといって、自動で抜けてくれるわけではないので注意が必要です。 そのため、tryブロックの最後にもleave命令を記述しています。
  • leave命令は「leave target」の形で、targetにはラベル名を記述します。 ラベルは命令の前に記述することで、その命令のアドレスを意味します。命令の前にラベルを付加するには、 「ラベル名:」のように後ろに「:」を付けて表します。
  • tryブロックの開始地点のスタックの状態は空でなければなりません。 例えば、5行目と6行目の間に「ldstr "AAA"」といった1行を入れるとtryブロックに入るときにスタックが空ではないので、 アセンブルは通りますが、EXEを実行するとInvalidProgramExceptionが発生します。
命令命令書式説明スタック遷移図例外
leaveleave targetコードの保護ブロックを抜ける。leave命令はtry、catch、filterブロックから抜けられる点でbr命令と異なる。しかし、finallyブロックからは抜けるために使うことはできない。…, →なし

3. .try〜finally

今度はcatch句の代わりにfinally句を使ったパターンを図3のプログラム(TryFinally.il)で示します。

図3 .try〜finallyの使用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.assembly TryFinally {}
.method public static void main()
{
    .entrypoint
    .maxstack 2
    .try
    {
        ldstr "try start!"
        call void [mscorlib]System.Console::WriteLine(string)
        ldc.i4 0x7FFFFFFF
        ldc.i4.1
        add.ovf
        call void [mscorlib]System.Console::WriteLine(int32)
        leave fin
    }
    finally
    {
        ldstr "finally!"
        call void [mscorlib]System.Console::WriteLine(string)
        endfinally
    }
fin:
    ret
}
try start!

ハンドルされていない例外 : System.OverflowException: 演算操作の結果オーバーフロ
ーが発生しました。
   at main()
finally!
図4 .try〜finallyの使用例の実行結果

  • 図4の実行結果によりtryブロックが実行され(try start!)、例外が未処理(OverflowExceptionの未処理例外のメッセージ)で、 異常終了する前にfinallyブロックが実行されている(finally!)ことを確認できます。
  • tryブロックについては先の.try〜catchのパターンと変わりありません。
  • 17〜21行目がfinallyブロックです。
  • finallyブロックの終端にはendfinally命令が必要です。finallyブロックの終了であることを表します。 例によって、この記述がないとInvalidProgramExceptionになります。
  • ちなみにendfinallyはfinallyブロック中に何度でも書くことができます。 endfinallyが実行されるとfinallyブロックのそれ以降に命令があっても実行されずfinallyブロックを終了します。

4. try〜catch〜finallyは?

ここまででcatch、finallyの個別の使い方を見てきましたが、VBやC#のようにこれらを組み合わせるにはどうすればよいでしょうか? 残念ながらILの.tryディレクティブはその後ろにcatchブロックやfinallyブロックなどをただ1つしか取ることができません。 そのため、.try〜finallyの.tryブロックの中に入れ子で.try〜catchを含めるようにすることで実現します。 図5にcatchとfinallyの組合せのサンプルプログラム(TryCatchFinally.il)を掲載します。

図5 .try〜catch〜finallyの例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.assembly TryCatchFinally {}
.method public static void main()
{
    .entrypoint
    .maxstack 2
    .try
    {
        .try
        {
            ldstr "try start!"
            call void [mscorlib]System.Console::WriteLine(string)
            ldc.i4 0x7FFFFFFF
            ldc.i4.1
            add.ovf
            call void [mscorlib]System.Console::WriteLine(int32)
            leave try_inner_fin
        }
        catch [mscorlib]System.OverflowException
        {
            call void [mscorlib]System.Console::WriteLine(object)
            ldstr "↑の例外が発生しました"
            call void [mscorlib]System.Console::WriteLine(string)
            leave try_inner_fin
        }
try_inner_fin:
        leave try_outer_fin
    }
    finally
    {
        ldstr "finally!"
        call void [mscorlib]System.Console::WriteLine(string)
        endfinally
    }
try_outer_fin:
    ret
}

さて、ここまでの内容でVB/C#の例外処理機構はほぼ実現できるようになりました。 しかし、例外を補足するための機能は、これですべてではありません。 例えば、VBのCatch句のWhen節(例外発生時の該当のハンドラで処理する条件をより詳細に指定できる機能)にあたる内容として、 ILではfilter句がありますし、おそらくVB/C#のどちらにも対応する構文のないfault句というものもあります。 さらに言えば、今回説明した.try { 〜 } catch { 〜 } のようなC#風の表記以外にもアセンブラ的な記述方法もあります。 (特に今回は説明をしませんが、サンプルTryCatchAnother.ilにてダウンロードできます)

5. 学んだこと

  • .try〜catch、.try〜finallyという構文がある
  • .tryで保護ブロックを開始する時点でスタックは空でないといけない
  • .tryブロック、catchブロックでは処理の最後にleave命令でブロックを抜ける
  • finallyブロックの最後にはendfinallyが必要
  • .try〜の後に続く句はただ1つしか指定できないので、try〜catch〜finallyを実現するには入れ子構造を使う

A. サンプルダウンロード


ホーム > KEN's .NET > [IL4] tryしてみよう

[e-mail] yone_ken00@hotmail.com