7

単一のダイアログボックスを利用する(C ++)アプリケーションを作成しています。CreateDialogParamメッセージポンプとハンドラーを設定した後、元のコード(たとえば、を呼び出すコード)にC++例外を伝播する方法を考え始めました。

これが私が意味するもののスケルトンの例です:

BOOL CALLBACK DialogProc(HWND, UINT msg, WPARAM, LPARAM)
{
    if(msg == WM_INITDIALOG) //Or some other message
    {
        /*
            Load some critical resource(s) here. For instnace:

            const HANDLE someResource = LoadImage(...);

            if(someResource == NULL)
            {
            ---> throw std::runtime_error("Exception 1"); <--- The exception handler in WinMain will never see this!
                Maybe PostMessage(MY_CUSTOM_ERROR_MSG)?
            }
        */

        return TRUE;
    }

    return FALSE;
}

//======================

void RunApp()
{
    const HWND dlg = CreateDialog(...); //Using DialogProc

    if(dlg == NULL)
    {
        throw std::runtime_error("Exception 2"); //Ok, WinMain will see this.
    }

    MSG msg = {};
    BOOL result = 0;

    while((result = GetMessage(&msg, ...)) != 0)
    {
        if(result == -1)
        {
            throw std::runtime_error("Exception 3"); //Ok, WinMain will see this.
        }

        //Maybe check msg.message == MY_CUSTOM_ERROR_MSG and throw from here?

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

//======================

int WINAPI WinMain(...)
{
    try
    {
        RunApp();
        //Some other init routines go here as well.
    }

    catch(const std::exception& e)
    {
        //log the error
        return 1;
    }

    catch(...)
    {
        //log the error
        return 1;
    }

    return 0;
}

ご覧のとおり、WinMainは「例外2」と「3」を処理しますが、「例外1」は処理しません

私の基本的な質問は単純です。これらの種類のエラーを元の「呼び出し元」コードに伝播するためのエレガントな方法は何でしょうか。

カスタムメッセージを使用して、実際のthrowステートメントをメッセージポンプ(in RunApp())に移動することを考えましたが、一般的にWindowsの経験が比較的少ないため、それがどのように機能するかはまだわかりません。

おそらく私はこの状況をすべて間違って見ています。メッセージハンドラーにいるときに、何か致命的なもの(つまり、重要なリソースの取得が失敗し、回復の機会がない場合)が発生した場合、通常はどのように救済します

4

4 に答える 4

9

AFAIK WinAPIコールバック(ウィンドウ/ダイアログ/スレッドプロシージャなど)は、例外を伝播してはなりません。これは、WinAPI内部(コールバックを呼び出す)が例外を処理する準備ができていないためです。WinAPI DLLのコードが修正されている間、例外の実装はコンパイラ固有であるため、それらを準備することはできません。そのため、考えられるすべての例外伝播の実装を処理できません。

いくつかの単純なケース(特にVisual Studioでコンパイルする場合)では、例外が正しく表示されるように伝播されることがあります。ただし、これは偶然の一致です。また、アプリケーションがクラッシュしなくても、間に呼び出されたWinAPI関数が、準備されていない例外のために解放されなかったリソースを割り当てなかったかどうかはわかりません。

コールバックのソースがわからないため、複雑さがさらに増します(一般的に、一部のメッセージでは、コールバックを推測できます)。ダイアログプロシージャによって処理されるメッセージは、ダイアログに投稿された場​​合にのみメッセージループを通過します。それらが送信された場合、それらはループをスキップして直接実行されます。さらに、メッセージが送信された場合、誰がメッセージを送信したかわかりません–それはあなたですか?それともWindows?または、何かをしようとしている他のプロセスの他のウィンドウ?(ただし、これにはリスクが伴います。)呼び出し元のサイトが例外に備えているかどうかはわかりません。

回避策のいくつかの方法は、Boost.Exceptionによって提供されます。ライブラリを使用すると、キャッチされた例外を何らかの方法で保存し、後で使用(特に再スロー)することができます。throw { ... } catch(...) { ... }このようにして、ダイアログプロシージャを、例外をキャプチャして後で使用するために保存する場所にラップすることができます。

編集:C ++ 11以降、例外オブジェクトを格納するBoost.Exceptionの機能はSTDの一部です。あなたはそのために使うかもしれませんstd::exception_ptr

ただし、まだいくつかの問題が残っています。それでも、なんらかの方法で回復し、何らかの値を返す必要があります(戻り値が必要なメッセージの場合)。そして、保存された例外をどうするかを決める必要があります。アクセスする方法は?誰がやるの?彼/彼女はそれで何をしますか?

このメッセージを使用してダイアログプロシージャに任意のパラメータを渡すことがWM_INITDIALOGでき(適切な形式のダイアログ作成関数を使用)、これは、格納された例外(存在する場合)を保持する構造体へのポインタである可能性があります。次に、その構造を調べて、ダイアログの初期化が失敗したかどうか、およびその方法を確認できます。ただし、これはメッセージに単純に適用することはできません。

于 2009-07-23T07:24:11.923 に答える
1

つまり、例外を使用することはありません。ただし、エラーを報告するにはいくつかのアプローチがあり、そのすべてが何らかの形または形式でロギングを使用します。

アプローチ1.OutputDebugString()を使用します。
デバッガーを持っている人だけが、実際には失敗してはならない何かに実際に気付くので、これは良いことです。ただし、例外処理を使用しようとしている人には明らかに多くの短所があります

アプローチ2.MessageBoxを使用します。
これはアプローチ1よりもはるかに優れているわけではありませんが、開発者以外の人がエラーを確認できるようにします。

アプローチ3.エラーロガーを使用します。
'throw'を使用して後でキャッチし、' logged'を使用するのではなく、失敗時にログを追加し、標準のWin32リターンコードを使用してアプリを終了することができます。

if(msg == WM_INITDIALOG) //Or some other message
{
    /*
        Load some critical resource(s) here. For instnace:

        const HANDLE someResource = LoadImage(...);

        if(someResource == NULL)
        {
                  LogError("Cannot find resource 'foo');
        }
    */

    return TRUE;
}
于 2009-07-23T01:09:32.010 に答える
0

エラー処理の目的でカスタムウィンドウメッセージを登録することは避けたいと思います。つまり、このアプローチは問題なく機能しますが、実際には必要ありません。

ちなみに、上記のcatchハンドラーは、3つの例外すべてをキャッチする必要があります。ダイアログプロシージャは、CreateDialogを呼び出すのと同じスレッドで実行されます。モードレスダイアログを作成しても、ワーカースレッドは生成されません。モードレスダイアログは、GetMessage / Translate/Dispatchループを介してメッセージを取得します。そこにはスタックフレームがあります。つまり、投げると、WinMainのtry/catchブロックまで巻き戻されます。

これはあなたが見ている行動ではありませんか?

于 2009-07-23T01:09:15.177 に答える
0

http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.htmlを参照してください。

于 2010-01-14T07:46:15.903 に答える