構造化例外処理（SEH）について with DirectX


# 普通の例外

ゼロ徐算やアクセス違反などの低レベルな例外（SE）を取ることができません。クラッシュしてしまいます。

```cpp
try {
  throw std::exception("hoge");
} catch (exception capture) {
  std::cout << capture.what() << std::endl;
} catch (...) {
  std::cout << "Unknown" << std::endl;
} finally {
  ...
}
```

#構造化例外

これでSEを補足できるようになります。
ただし、関数内でtry-catchと併用はできません。
`std::function`で主実装を外に出しておけば幾分かマシかもしれません。

```cpp
_EXCEPTION_POINTERS* info = nullptr;
__try {
  auto hoge = 1 / ZERO; // 0徐算
  nullObject->Invoke(); // アクセス違反
} __except(info = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) {
  auto code = info->ExceptionRecord->ExceptionCode;
  std::cout << code << std::endl;
  throw exception(std::to_string(code)); // これで外側のtry-catchに伝播できる（※）
} __finally {
  ...
}
```

# SEを自動で例外に変換するオプション

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280975/3a09f332-b14e-5040-ab48-97a4a848b1b1.png)

このオプションを点けておくことで、パフォーマンス上のペナルティと引き換えに、SE発生時に自動でC++の例外に変換してくれます。ただし、SEの中身は取れません。
また、Debugビルド時はオプションを無効にしていても変換してしまうようです。（お節介かな…）

```cpp
try { ... }
catch(std::exception e) { ... }
catch(...){
  // ここに来る、「何が起こったか」は分からない
}
```

#_set_se_translator

EHaを有効化したうえでこれを登録しておくことで、好きな例外の形式に変換できます。

```cpp
_set_se_translator([](unsigned int code, _EXCEPTION_POINTERS* ep) -> void {
  throw exception(std::to_string(code));
});

try { ... }
catch(std::exception e) {
  // ここに来る、_set_se_translatorで、例外オブジェクト内に入れた内容が参照できる
} catch(...){ ... }
```

# DirectXのSEH

DirectXはCOMなので、本来エラーはSEではなくHRESULTにて報告されます。
しかし、HRESULTを返さないメソッドや、COM ObjectのRelease時に問題が発生するとSEを飛ばすこともあるようです。

# SEHブロック/関数内でthrowできない

これが本題です。
もしかするとCOM全般の話なのかもしれませんが、throwをしてもそれ自体がSEになり、呼び出し元の関数に制御が戻りません。

C++例外が使えないとなると、もう引数や戻り値でSEを伝えるしかありません。引数を操作できない`_set_se_translator`は使い物になりません。（グローバル変数を使えばできなくはないですが…）

# 妥協案

上記の制約の中、私が思うコードの劣化を最小限に抑えつつ対応できる方法かなという書き方です。

```cpp
void HandleStructuredException(std::function<void()> *callback, unsigned int &code) {
    __try { callback->operator()(); }
    __except (EXCEPTION_EXECUTE_HANDLER) { code = GetExceptionCode(); }
}

unsigned int seCode = 0;
std::function<void()> callback = [&]() -> void {
  // メイン処理
};
HandleStructuredException(&callback, seCode);
if(seCode != 0) throw std::exception(std::to_string(seCode).data());
```

`GetExceptionCode`の代わりに`GetExceptionInformation`を使うことで、より詳細な（アドレスなど）を取ることもできますが、ポインタなのでDeepコピーが必要です。

callbackをポインタで渡していますが、SEHを実装する関数ではオブジェクトアンワインドというスコープ脱出時のデストラクタが使えないためです。
[MSDN コンパイラエラー C2712](https://docs.microsoft.com/ja-jp/cpp/error-messages/compiler-errors-2/compiler-error-c2712?view=msvc-160)

# Widnowメッセージループ内外の例外伝搬（おまけ）

メッセージループ内では発生した例外をループ外に伝搬することはできません。より正確には、`DispatchMessage → ... → WNDCLASSEX.lpfnWndProc`と呼び出されますが、ここを例外で駆け上がることができません。

メッセージループでは複数のWindowがコンカレントに同居していることを考えればまぁ仕方ないような気もします。
こちらはMSG構造体のWHNDにWindowオブジェクトを紐づけておけば、インスタンスフィールドを使って例外の受け渡しができる分`_set_se_translator`に比べれば幾分かマシな気がします。

```cpp
int main(int argc, char* argc[]) {
  ...
  auto windowHandle = CreateWindow( ... );
  SetWindowLongPtr(windowHandle , GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
  ...
  try {    
    MSG message;
    while (GetMessage(&message, NULL, 0, 0)) {
      auto window = reinterpret_cast<Window *>(GetWindowLongPtr(message.hwnd, GWLP_USERDATA));
      if(window != nullptr && window->Error() != nullptr) {
        throw *window->Error();
      }
      ...
    }
  } catch { ... }
  ...
}
```

[MSDN WindowProc callback function](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85)?redirectedfrom=MSDN#remarks)

# まとめ

そもそも、どこまでハンドリングすべきなのかという問題があります。
SEHしなきゃいけない時点でバグですし…ハンドルして回復できるとも限りません。
とはいえ、仮にできることが無かったとしてもクラッシュ前にごめんねダイアログを出したいものです。

世に出回るC++で描かれたWindowsアプリはこの辺りどうしているのでしょうかね。

# 参考

* [構造化例外処理 (ひしだま's 技術メモページ)](https://www.ne.jp/asahi/hishidama/home/tech/vcpp/seh.html)
* [WindowProc callback function (MSDN)](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85)?redirectedfrom=MSDN#remarks)
* [WindowsのウィンドウプロシージャでC++例外をstd::exception_ptrで扱うお話 (Qiita)](https://qiita.com/LNSEAB/items/c76ed1a4e4d69b824655)
* [_set_se_translator (MSDN)](https://docs.microsoft.com/ja-jp/cpp/c-runtime-library/reference/set-se-translator?view=msvc-160)
* [SEH 例外と C++ 例外 (想像力の欠如は深刻な欠点の一つである。)](https://noqisofon.hatenablog.com/entry/20091010/1255184836)
* [コンパイラエラー C2712 (MSDN)](https://docs.microsoft.com/ja-jp/cpp/error-messages/compiler-errors-2/compiler-error-c2712?view=msvc-160)
* [ ()]()