views:

652

answers:

5

In general I use exceptions to deal with errors, however the problem I have here is that the error applies to a different thread than the one that caused it.

Basicly the window has its own thread, and the direct3d device must be created and reset by the same thread that created the window. However creating the device may fail, so I need to throw an exception in the thread that was trying to create the instance, not the window code

The callback function:

void Callback(HWND hwnd, boost::function<void(HWND,LPARAM)> call, LPARAM lParam)
{
    //Make our stack allocated function object into a heap allocated one
    boost::function<void(HWND,LPARAM)> *callH = new boost::function<void(HWND,LPARAM)>(call);
    //send a message with a pointer to our function object in the WPARAM
    PostMessage(hwnd, WM_CALLBACK, (unsigned)callH, lParam);
}
LRESULT CALLBACK HookProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    //check for our custom message
    if(msg == WM_CALLBACK)
    {
        //retreive the function pointer from the WPARAM
        boost::function<void(HWND,LPARAM)> *callH = (boost::function<void(HWND,LPARAM)>*)wParam;
        //call it
        (*callH)(hwnd,lParam);
        //delete our heap allocated function object
        delete callH;
        return 0;
    }
    else
        //if there was nothing relevant to us, call the old message procedure
        return CallWindowProc(hooked[hwnd], hwnd, msg, wParam, lParam);
}
//std::map<HWND, WNDPROC> hooked;

The code that requests the window thread to create the device

Graphics::Graphics(IWindow *_window, Size2<unsigned> _size)
:lost(false), reset(false), refCnt(0), backCol(0xFF000000),
started(false), exited(false),
window(_window), size(_size)
{
    window->AddRef();
    HWND hwnd = *((HWND*)window->GetHandle());
    CallbackHook(hwnd);
    Callback(hwnd, boost::bind(&Graphics::create, this), 0);

    while(!started)Sleep(100);
}
void Graphics::create()
{
...code that may throw various exceptions
    started = true;
}

So basically I need the exceptions being thrown by the create() method to be caught by the exception handelers where the Graphics object was created which is another thread.

A: 

Well, the answer is that you can't. A viable alternative would be to do only necessary work that can't fail, like extractiong value from the parameters, in your callback function, and call the create() function in your main thread.

Also, you shouldn't busy wait for the creation, use a Semaphore, or a Mutex & Event etc.

Graphics::Graphics(IWindow *_window, Size2<unsigned> _size)
:lost(false), reset(false), refCnt(0), backCol(0xFF000000),
started(false), exited(false),
window(_window), size(_size)
{
    window->AddRef();
    HWND hwnd = *((HWND*)window->GetHandle());
    semaphore = CreateSemaphore(NULL, 0, 1, NULL);
    if (semaphore == NULL) throw whatever;
    CallbackHook(hwnd);
    Callback(hwnd, boost::bind(&Graphics::create, this), 0);

    WaitForSingleObject(semaphore);
    create();

    CloseHandle(semaphore); /* you won't need it */
}
void Graphics::create()
{
...code that may throw various exceptions
}
void Graphics::create_callback()
{
    ReleaseSemaphore(semaphore, 1, NULL);
}
jpalecek
That not possible, creating a reetting a Direct3D device MUST be done with the same thread that created the window, and 99% of the time its those that fail.
Fire Lancer
Ah, so. You should save your exception to some variable and check the variable in the other thread then. See the answer of dalle
jpalecek
+1  A: 

A simple solution would be to catch the exception in the thread that thrown it (or check the parameters before calling the function to decide if an exception would be thrown) and then pass the exception via a static variable tot the main thread where the main thread can throw it.

I would add a check after the WaitForSingleObject to discover if an exception is waiting to be thrown and in case it does - throw it.

Dror Helper
+2  A: 

I see you call PostMessage from Callback then actively wait using a loop and Sleep. Replace PostMessage by SendMessage, which will wait for the message processing to complete. That way the processor-intensive loop is gone. Moreover, you can use the return code of the SendMessage call (from the WM_CALLBACK arm of HookProc) to pass error information. Either use simple numeric codes (e.g. non-zero means error) or pass exception objects (in this case you'll have to allocate then on the heap from your exception handler, you can't pass the parameter directly as it is stack-allocated).

Last, using the synchronous SendMessage instead of the async PostMessage means you no longer have to build an intermediary callH function object from the HookProc, as you can pass the stack allocated call parameter instead. This results in much simpler code.

For example:

void Callback(HWND hwnd, boost::function<void(HWND,LPARAM)> call, LPARAM lParam)
{
    //send a message with a pointer to our function object in the WPARAM
    if (SendMessage(hwnd, WM_CALLBACK, &call, lParam))
    {
        // error handling.
    }
}
LRESULT CALLBACK HookProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    //check for our custom message
    if(msg == WM_CALLBACK)
    {
        //retreive the function pointer from the WPARAM
        boost::function<void(HWND,LPARAM)> *callH = (boost::function<void(HWND,LPARAM)>*)wParam;
        //call it, catching exceptions in the process.
        try 
        {
            (*callH)(hwnd,lParam);
            return 0;
        } 
        catch (...) 
        {
            return 1;
        }
    }
    else
        //if there was nothing relevant to us, call the old message procedure
        return CallWindowProc(hooked[hwnd], hwnd, msg, wParam, lParam);
}
//std::map<HWND, WNDPROC> hooked;

And:

Graphics::Graphics(IWindow *_window, Size2<unsigned> _size)
:lost(false), reset(false), refCnt(0), backCol(0xFF000000),
started(false), exited(false),
window(_window), size(_size)
{
    window->AddRef();
    HWND hwnd = *((HWND*)window->GetHandle());
    CallbackHook(hwnd);
    Callback(hwnd, boost::bind(&Graphics::create, this), 0);

    // No need to wait, as SendMessage is synchronous.
}
void Graphics::create()
{
...code that may throw various exceptions
    started = true;
}
fbonnet
+4  A: 

Perhaps you can wrap the call inside another function, using Boost.Exception.

The following code does not work though, but you'll probably get the idea.

class Context
{
public:
    Context(HWND hwnd, const boost::function<void(HWND,LPARAM)>& f, LPARAM lParam)
    {
        // TODO: reroute call through Wrapper function.
    }

    void wait()
    {
        mutex::scoped_lock l(m);
        while (!finished)
        {
            c.wait(l);
        }
        if (ex)
            rethrow_exception(ex);
    }

private:
    void Wrapper()
    {
        try
        {
            f(/*params*/);
        }
        catch (...)
        {
            ex = current_exception();
        }
        mutex::scoped_lock l(m);
        finished = true;
        c.notify_all();
    }

    boost::function<void(HWND,LPARAM)> f;
    exception_ptr ex;
    bool finished;
    mutex m;
    condition c;
};

void Callback(HWND hwnd, const boost::function<void(HWND,LPARAM)>& f, LPARAM lParam)
{
    Context ctx(hwnd, f, lParam);

    ctx.wait();
}
dalle