Win32構造化例外処理その1

最近ここに何か書く暇があまり無くて調べたらすぐわかるようなAdaネタでお茶を濁していたわけですが、気がつくと何も書けなくなってきている気がしてきたので、リハビリがてらになにか短期シリーズやろうと思いました。
ネタは……探すか……きょろきょろ……うろうろ……(ネットサーフィンで半日経過)……はっ。いかんいかん、ええと、Binary Hacks読書中つながりのhttp://www.kmonos.net/wlog/67.html#_0031061124経由で、構造化例外処理です。
構造化例外処理というのはC++の例外処理とは異なり↓です。

__try {  ... } __except(GetExceptionCode() == 0x...){ ... }

嘘です。
この場合__tryも、__exceptも、VC++の拡張キーワードに過ぎないわけで、要するに普通のtry文とコンパイラの扱いは同じです。これ止まりの解説は意外に多いのですが、こんなのは「VC++C++ではなくCコンパイラとして使ってそれでも例外処理をしたい場合」以外は役に立ちません。
たとえば、http://d.hatena.ne.jp/ytqwerty/20060123#p1のような、構文に無い制御をアセンブラ埋めこみで後づけした場合、それを例外安全にしたければどうすればいいでしょうか?
……の前に、基本書いておきますか。
Win32では、例外は、(setjumpやDWARF-2利用のzero-cost-exceptionなどと違って)OSレベルで実現されています。アクセス違反やゼロ割算も「Win32の例外」として飛んできます。それでも、アクセス違反やゼロ割算なんて、受け止めて処理するような例外ではないわけですから、即落ちでいい、と考えられるかもしれません。そういうコンパイラは多いです。実際、Win32の構造化例外処理をまるで考えていないコンパイラは多いです。特にgのつくやつとその派生系。
アクセス違反やゼロ割算なんかは即落ちでいいのですが、受け止められることを期待した例外というものもあります。
たとえばDelphiでは、次のExitは例外で実現されます。……せんが、Win32例外処理の構造をなぞることでfinallyは実行されます。

procedure A;
begin
  try
    Exit; {Exit専用の例外が発生<ins>……しているわけではない(汗)</ins>}
  finally
    Beep; {実行される!}
  end;
end; {<del>ここで受け止められる</del><ins>ハンドラをなぞった後でここまでふつーにjmpしてくる</ins>}

あー…ええと…例外ハンドラの登録は所詮スタックとリストの操作に過ぎませんので同じ関数の中でハンドラの配置がわかっているならいくらでも最適化が効きます。gdgd
これに限らず、一般に、tryブロックを超えてのジャンプは、一旦例外を経由する実装が多いです。昔は多かったんですよっ!……記憶が定かならっ!……調べれば調べるほど、記憶が疑わしくなってきましたがっ!
他にも、コールバック関数から一連の処理を中断させるために例外を投げる使い方もあります。Win32 APIEnumなんちゃらはそれで中断できます。しかし、「Win32の構造化例外処理をまるで考えていないコンパイラで書かれたコールバック関数を受け取る関数」から呼ばれたコールバック関数で同じことをすると、暴走してくれるかもしれません。
Win32では、広域脱出を行いたい時は、setjumpではなくて構造化例外処理が作法ですので、対応できるものならしておくが吉です。
ああそうそう、先のリンクの「DelphiC#のyieldモドキ」は、Delphi for Win32のfor .. inが、元々IEnumeratorを開放するために例外ハンドラを使う仕様ですので、Breakに対応した時点で例外も大丈夫というか対応方法は同じというか、ともかく大丈夫の筈です。
つづく。
追記 tryブロックからの脱出
ずっと後と思ってたんですけど調べてみました。

最近のDelphi → System.@TryFinallyExit
C++Builder10 → __return_unwind
VC++Toolkit → __local_unwind2
dmd → finallyブロックを直接call

FreePascal → setjump使ってた;; (所詮これもg系か……)

例外投げてる奴無いなあ;;
Delphi5まで遡ったんですが、その頃は既に@TryFinallyExit存在してました。あまりにも情報古すぎるぞ俺。