views:

301

answers:

4

I'm writing a (C++) application that utilizes a single dialog box. After setting up a message pump and handler I started wondering how I would go about propagating C++ exceptions to my original code (i.e., the code that calls CreateDialogParam, for instance).

Here's a skeleton example of what I mean:

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;
}

As you can see, WinMain will handle "Exception 2" and "3", but not "Exception 1".

My fundemental question is simple; what would be an elegant way to propagate these sorts of errors to the original "calling" code?

I thought of maybe using custom messages and moving the actual throw-statements out to the message pump (in RunApp()), but I'm not sure how that would work yet as I have relatively little experience with Windows in general.

Perhaps I'm looking at this situation all wrong. How do you usually bail out when something fatal (i.e., an acquisition of a critical resource fails, and there's no chance for recovery) when you're in the message handler?

+1  A: 

I would stay away from registering custom Window messages for error-handling purposes. I mean this approach will work fine, but there's not really a need.

By the way, your catch handler above should catch all 3 exceptions. Your dialog procedure runs on the same thread that calls CreateDialog. Creating a modeless dialog doesn't spawn off a worker thread. The modeless dialog still gets its messages via your GetMessage/Translate/Dispatch loop. There's a stack frame there, which means when you throw, it should unwind all the way out to your WinMain try/catch block.

Is this not the behavior you're seeing?

James D
This is insanely weird, I don't why but after restarting VS I'm getting the desired behavior (i.e., the catch handler is invoked now). I'll need to investigate what happened further. Thanks for making me double-check!
@ssayq I am not sure of that. When calling WinAPI function you do not know what it does internally. It does not have to be prepared to handle C++ exceptions correctly because C++ exceptions implementation is internal to compiler while WinAPI implementation is common. So if a WinAPI function does anything more complicated (like allocating inner resources for something) it will not go well with exceptions (because it will not release those resources). AFAIK exceptions should not be propagated out of WinAPI callbacks (like the window/dialog procedure) and in many cases even between modules.
Adam Badura
A: 

In short, I never ever use exceptions. There are, however, several approaches to reporting any errors, all of which use logging in some way shape or form.

Approach 1. Use OutputDebugString().
This is good because only someone with debugger will actually notice something that in all reality shouldn't fail. There are obviously lots of cons to someone trying to use exception handling however

Approach 2. Use MessageBox.
This isn't much better than Approach 1, however it does allow non developers to see an error.

Approach 3. Use an error logger.
Rather than using 'throw' to then get caught later and then 'logged', you could add the logging at the time of failure and use standard Win32 return codes to exit the app:

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;
}
cmroanirgo
Thanks, approach No. 3 looks like a good alternative for me.
+1  A: 

AFAIK WinAPI callbacks (like window/dialog/thread procedures) must not propagate exceptions. This is because WinAPI internals (which call the callback) are not prepared to handle exceptions. They cannot be prepared because exceptions implementation is compiler specific while the code in WinAPI DLLs is fixed so it cannot handle all the possible exceptions propagation implementations.

In some simple cases (especially when you compile with Visual Studio) you may observe that exceptions are propagated as would seem right. This is however a coincidence. And even if your application does not crash you are not sure if the WinAPI functions called in between did not allocate any resources which they did not released due to exception they were not prepared for.

Additional layer of complexity is added by not knowing the source of the callback (in general – for some messages it can be deduced). Messages handled by your dialog procedure go through the message loop if and only if they were posted to your dialog. If they were sent then they skip the loop and are executed directly. Additionally if a message is sent then you don't know who sends the message – is it you? Or maybe Windows? Or some other window in other process trying to do something? (However this would be risky.) You don't know whether the calling site is prepared for an exception.

Some way of workaround is offered by Boost.Exception. The library allows to somehow store the caught exception and use (in particular rethrow) it latter. This way you could wrap your dialog procedure in a throw { ... } catch(...) { ... } where in catch you would capture the exception and store it for latter use.

However some problems still remain. You still have to recover somehow and return some value (for messages that require return value). And you would have to decide what to do with the stored exception. How to access it? Who will do it? What will he/she do with it?

In case of WM_INITDIALOG you may pass arbitrary parameters to the dialog procedure with this message (use appropriate form of dialog creation function) and this could be a pointer to structure which will hold the stored exception (if any). Then you could investigate that structure to see if dialog initialization failed and how. This however cannot be applied simply to any message.

Adam Badura