none
SEHによる例外キャッチについて RRS feed

  • 質問

  • いつもお世話になっております。

    今回は、以下のコードがアプリケーションの動作に影響を与えるかどうかの質問です。

     

    現在、関数が成功したかどうかを毎回チェックするコードをなくすために、SEHを使用しております。

    現状問題になっておりませんが、以下のコードで何か問題があるかどうかをチェックしていただきたいです。

    #include <windows.h>
    #include <dshow.h>
    #pragma comment(lib, "strmiids.lib")
    
    int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
    {
     ICreateDevEnum * sysDevEnum = nullptr;
     IEnumMoniker  * enumCat  = nullptr;
     __try {
      __try {
       ::CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
                 CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&sysDevEnum));
       // ↑が失敗し、sysDevEnumにnullptrが入っていると仮定
       sysDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
                         &enumCat, 0);
       // ↑でアクセス違反(例外をキャッチ)
      }
      __except(EXCEPTION_EXECUTE_HANDLER) {
      }
     }
     __finally {
      if (nullptr != enumCat) {
       enumCat->Release();
       enumCat = nullptr;
      }
      if (nullptr != sysDevEnum) {
       sysDevEnum->Release();
       sysDevEnum = nullptr;
      }
     }
    
     return 0;
    }
    
    

    以上、よろしくお願いいたします。

    以下の方法もありますが、現状上記の方法がよいと思っております。

     __try {
      HRESULT hr = ::CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
                      CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&sysDevEnum));
      if(S_OK != hr) __leave;
     }
     __finally {
    
    2011年2月28日 3:13

回答

  • CoCreateInstance API が返してくれる有益な戻り値を無視してしまうことになるので、個人的にはこのようなコーディングはすべきでないと思います。
    例外を使ってコードを簡略化したいのであれば、#import してスマート ポインターを使うのが COM の世界では常套手段ではないでしょうか。

     Dr. GUI、コンポーネント、COM、および ATL を使う
     http://msdn.microsoft.com/ja-jp/library/cc482719.aspx#drguion020298_p8
     (第 8 部 : スマートに行こう!スマート ポインタで COM オブジェクトを使おう)
    • 回答としてマーク dsa_ma 2011年3月2日 1:49
    2011年2月28日 5:06
  • CoCreateInstance は例外を発行しません。totojoさんも書かれていますが、エラーハンドリングが面倒なら、スマートポインタを活用することを検討するほうが良いと思います。

    スマートポインタにするとあちこち書き換えがあるので。。。ということとであれば

    #include <comdef.h>
    
    try{
     _com_util::CheckError( ::CoCreateInstance( ... ) );
     ...
    }
    catch( _com_error exp )
    {
     hRes = exp.Error();
    }
    

    という感じにしてやれば、エラーを掌握したまま、無理のない例外機構を利用することができます。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク dsa_ma 2011年3月2日 1:50
    2011年2月28日 6:29
  • 個人的な見解です。

    SEHをそのような目的で利用されるのは、あまりお勧めしません。
    SEHは、メモリの不正アクセス等の致命的なApplicationの不具合を検出し、
    後処理を行うなど意図しない(APIの戻り値のCheckなどError処理が行えない、Checkが漏れやすい)不具合に対して利用されることをお勧めします。
    致命的な不具合とは、Applicationが終了せざるを得ない不具合です。
    NULL PointerへのAccessがこの典型例です。

    そうすれば、自分が書いたCodeがどこで致命的な不具合を起こしているか検出しやすくなります。
    もしSEHで例外がThrowされたのなら、そこでcatchしてLogを吐くなど問題を検出しやすくなります。
    とりわけ再現性が低かったり、特定のユーザー環境でしか起きない問題など、発生箇所特定に役に立ちます。

    しかし、提示されている使い方だと、その特定は手間がかかるようになってしまいます。

    ”例外”と一口にいっても、些細なものから致命的なものまで多種多様です。
    Applicationにとってどの例外がどの程度の問題なのか、関数内で処理しきれる程度のものなのか判断し、使い分けることが大切です。
    また問題の特定のしやすさも、判断の基準となるでしょう。

    代替案はすでに皆さんがご提案されている方法が良いでしょう。
    別の案としては、MFCのCExceptionクラスみたいな例外クラスを作成し、try...throw...catchする方法です。
    ちなみに、SEHによる例外はC++の例外に変換してthrowしcatchすることができます。

    提示されているCodeがC++なら以下が参考になります。

    [Using Structured Exception Handling with C++]
      http://msdn.microsoft.com/ja-jp/library/7w0chfbk(v=VS.100).aspx
      it is not specifically designed for C++ and is not recommended.
      SEHはC++ではお勧めしない、と記述されています。

    [C++ Exception Handling]
      http://msdn.microsoft.com/ja-jp/library/4t3saedz.aspx

    [CException クラス]
      http://msdn.microsoft.com/ja-jp/library/yx1b5f5w(v=VS.100).aspx
      派生クラスが参考になります。

    それから例外処理機構は処理Costがそれなりにかかるため、Performanceを重視する処理は、その処理だけ例外処理機構を利用しないこともあります。

    というわけで、まずは一通りMSDNを呼んでみては如何でしょうか?
    様々なHintが書かれていますよ。

    少し話がズレますが、COM Interface MethodのError CodeにS_FALSEというのがあります。
    成功したが失敗、というError Codeになります。
    意味はそのMethodに依存しますし、その戻り値を受けてどうするかは、Applicationが判断することになります。
    そのままS_OKと同じ扱いをして処理を続行する場合もあります。
    S_OKだけで判断すると、意図しない結果になることもありますのでご注意ください。
    多くの場合、FAILEDやSUCCEEDED Macroを利用します。

    • 回答としてマーク dsa_ma 2011年3月2日 1:50
    2011年2月28日 12:16

すべての返信

  • CoCreateInstance API が返してくれる有益な戻り値を無視してしまうことになるので、個人的にはこのようなコーディングはすべきでないと思います。
    例外を使ってコードを簡略化したいのであれば、#import してスマート ポインターを使うのが COM の世界では常套手段ではないでしょうか。

     Dr. GUI、コンポーネント、COM、および ATL を使う
     http://msdn.microsoft.com/ja-jp/library/cc482719.aspx#drguion020298_p8
     (第 8 部 : スマートに行こう!スマート ポインタで COM オブジェクトを使おう)
    • 回答としてマーク dsa_ma 2011年3月2日 1:49
    2011年2月28日 5:06
  • CoCreateInstance は例外を発行しません。totojoさんも書かれていますが、エラーハンドリングが面倒なら、スマートポインタを活用することを検討するほうが良いと思います。

    スマートポインタにするとあちこち書き換えがあるので。。。ということとであれば

    #include <comdef.h>
    
    try{
     _com_util::CheckError( ::CoCreateInstance( ... ) );
     ...
    }
    catch( _com_error exp )
    {
     hRes = exp.Error();
    }
    

    という感じにしてやれば、エラーを掌握したまま、無理のない例外機構を利用することができます。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク dsa_ma 2011年3月2日 1:50
    2011年2月28日 6:29
  • 個人的な見解です。

    SEHをそのような目的で利用されるのは、あまりお勧めしません。
    SEHは、メモリの不正アクセス等の致命的なApplicationの不具合を検出し、
    後処理を行うなど意図しない(APIの戻り値のCheckなどError処理が行えない、Checkが漏れやすい)不具合に対して利用されることをお勧めします。
    致命的な不具合とは、Applicationが終了せざるを得ない不具合です。
    NULL PointerへのAccessがこの典型例です。

    そうすれば、自分が書いたCodeがどこで致命的な不具合を起こしているか検出しやすくなります。
    もしSEHで例外がThrowされたのなら、そこでcatchしてLogを吐くなど問題を検出しやすくなります。
    とりわけ再現性が低かったり、特定のユーザー環境でしか起きない問題など、発生箇所特定に役に立ちます。

    しかし、提示されている使い方だと、その特定は手間がかかるようになってしまいます。

    ”例外”と一口にいっても、些細なものから致命的なものまで多種多様です。
    Applicationにとってどの例外がどの程度の問題なのか、関数内で処理しきれる程度のものなのか判断し、使い分けることが大切です。
    また問題の特定のしやすさも、判断の基準となるでしょう。

    代替案はすでに皆さんがご提案されている方法が良いでしょう。
    別の案としては、MFCのCExceptionクラスみたいな例外クラスを作成し、try...throw...catchする方法です。
    ちなみに、SEHによる例外はC++の例外に変換してthrowしcatchすることができます。

    提示されているCodeがC++なら以下が参考になります。

    [Using Structured Exception Handling with C++]
      http://msdn.microsoft.com/ja-jp/library/7w0chfbk(v=VS.100).aspx
      it is not specifically designed for C++ and is not recommended.
      SEHはC++ではお勧めしない、と記述されています。

    [C++ Exception Handling]
      http://msdn.microsoft.com/ja-jp/library/4t3saedz.aspx

    [CException クラス]
      http://msdn.microsoft.com/ja-jp/library/yx1b5f5w(v=VS.100).aspx
      派生クラスが参考になります。

    それから例外処理機構は処理Costがそれなりにかかるため、Performanceを重視する処理は、その処理だけ例外処理機構を利用しないこともあります。

    というわけで、まずは一通りMSDNを呼んでみては如何でしょうか?
    様々なHintが書かれていますよ。

    少し話がズレますが、COM Interface MethodのError CodeにS_FALSEというのがあります。
    成功したが失敗、というError Codeになります。
    意味はそのMethodに依存しますし、その戻り値を受けてどうするかは、Applicationが判断することになります。
    そのままS_OKと同じ扱いをして処理を続行する場合もあります。
    S_OKだけで判断すると、意図しない結果になることもありますのでご注意ください。
    多くの場合、FAILEDやSUCCEEDED Macroを利用します。

    • 回答としてマーク dsa_ma 2011年3月2日 1:50
    2011年2月28日 12:16
  • 返信ありがとうございます。

    戻り値によるログ書き出しはやはり重要なのですね。今度から気をつけます。

    また、有益なurlありがとうございます。

    一通り読みます。

    2011年3月1日 1:39
  • 返信ありがとうございます。

    変更するコードが少ないので、スマートポインタに変更する予定です。

    また、_com_utilがあることを初めて知りました。ありがとうございます。

    2011年3月1日 1:41
  • 返信ありがとうございます。

    URLありがとうございます。ただ、私はあまり英語が読めないので・・。すいません。

    また、VisualStudio EEなので、MFCは使えません。参考にいたします。

    ありがとうございました。

     

    > NULL PointerへのAccessがこの典型例です

    堅牢性とコードの可読性がほしいため、SEHを使用しております。

    そのため、nullptrアクセスだけでアプリケーションは落としたくありません。

    また、文字列のコピーは、wcscpy_sのようなセキュア関数のほうがよいのでしょうね。エラー発生時にも戻り値チェックができるという利点も含め。(もちろん、そのため、_set_invalid_parameter_handlerの呼び出しが必要です)

     StringCch系は例外を送出するため、あまり使用しないほうがよいのでしょうかね。(切り詰め可の意味合いのほうが強い関数ですが)

     

    > 例外処理機構は処理Costがそれなりにかかるため

    ローカルアンワインドのために、コンパイラが生成するコードよにるCostが顕著なのは知っているのですが、それ以外にも顕著になるパターンがあるのでしょうか?

     

    > FAILEDやSUCCEEDED Macroを利用します

    msdnを見た後に、戻り値は決定しております。

     

    C++でのSEH(例外ハンドラ)は、リージョンのコミットぐらいがよいのでしょうかね。

    http://codepad.org/burFOyY5

    (sample::AddルーチンでSEHを使用しております。簡単に作ったコードですので汚いです)

    2011年3月1日 2:05
  • >私はあまり英語が読めないので
    せっかく有意義な情報が無料で存在するのに、もったいないですよ。
    最初は翻訳に苦労すると思いますが、そのうち慣れてきますから挑戦してみては如何でしょうか。

    >MFCは使えません
    設計手法(考え方)を参考にしてもらえれば十分ですよ。

    >StringCch系は例外を送出するため
    StringCch系全てでしょうか?
    例えば、StringCchCopyW関数自身は例外を送出しません。strsafe.hの実装を見てみてください。
    また、SEHとC++の例外は別ものですよ。

    >顕著になるパターンがあるのでしょうか?
    私の知る範囲では、ないです。

    >http://codepad.org/burFOyY5
    一瞥した限りですが、私なら事前にBufferの調整を行い、
    try...catchで括り、Bufferの調整処理に不具合があればthrowするような処理を書くと思います。
    SEHの例外はC++の例外に変換して投げます。
    この方法が良いかどうかは人によりますので、一例程度に捉えて頂ければと思います。

    ちなみに、VirtualAllocは必ず指定したAddressからMemoryを確保できるとは限りません。

    2011年3月1日 12:51
  • 返信ありがとうございます。

    > 有意義な情報が無料で存在するのに、もったいないですよ

    認識しているのですが、ほかにやれることが多いので後回しにしています・・・・。アルゴリズムや数学、C++などをある程度こなした後に英語をやるつもりです。

     

    > StringCCh系

    すいません。勘違いでした。リージョンのアクセス違反の場合は、例外がでるようです。

     

    > VirtualAllocは必ず指定したAddressからMemoryを確保できるとは限りません

    最初に4MBのリージョンを確保しており、そのリージョンアドレスがコミットすることによって、変わることがないため問題ないと思っているのですが、間違っているのでしょうか?

    コミット: ストレージの割り当て( VirtualAlloc(address, 確保する領域(ページ倍数), MEM_COMMIT...);

    2011年3月2日 1:49
  • > 有意義な情報が無料で存在するのに、もったいないですよ

    認識しているのですが、ほかにやれることが多いので後回しにしています・・・・。アルゴリズムや数学、C++などをある程度こなした後に英語をやるつもりです。

    機械翻訳とか使えばいいのではないでしょうか?例えば、http://www.microsofttranslator.com/ というサイトがあります。機械翻訳と呼ばれる機能をオンライン提供しているサイトです(MSDNフォーラムなのでMSのものを上げてみました。他にもいろいろあります)。

    IEをお使いなら7か8から、ページのコンテキストメニュー(右クリックで出るメニュー)に、Bingで翻訳という上記のページにデータ送って翻訳してくれる仕組みがあります。

    他のブラウザはどうか知りませんが(使ってないしw)、日本語の乗ってないサイトで情報を探し回らないとならない私にはかなり有効なツールとして使わせてもらってます。

    英語はもちろん苦手ですよ。中学高校のころは英語の成績が一番悪かったくらいなんで。。。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2011年3月2日 2:06
  • >間違っているのでしょうか?
    いえ。一般的な話です。
    address is rounded down to the next page boundaryを理解されているのなら、問題ないです。

    >リージョンのアクセス違反の場合は、例外がでるようです。
    Hardware exceptionになりますから、APIが例外を自分で送出するといったことはありません。(そのように見える、というだけです)
    例えば1/0など0割による例外の送出は、そのCodeがApplication内にあってもAPI内にあっても、0割が発生すれば例外を送出するCodeを書かなくても例外が投げられます。

    一方、C++は自分でthrowを書きますね。
    (SEHでもRaiseExceptionを呼べば、Software exceptionのSEHの例外を送出することができます。)

    細かいところは、やはりMSDNを読んでもらった方が良いです。

    2011年3月2日 13:23