views:

58

answers:

2

I've been trying to find a good architecture for one application for the last few days, and after some research I'm finally stuck, and the reason is COM.

The app in question will have multiple GUI threads, and they will schedule work items for worker thread. The worker thread will initialize COM via CoInitialize(NULL);, create few COM components and will go into a loop which will wait for WaitForMultipleObjects(2, ...) (ExitEvent - to indicate that app is shutting down and ManualResetEvent - to indicate that there are actually work items to process), and on successful wait, will process the items and PostMessage them back to GUI threads. The ManualResetEvent will be reset inside worker if the queue will be empty and will happen inside queue critical section.

The problem is that COM, as usual, makes EVERYTHING 1000x harder...

If I understand correctly, CoInitialize(NULL); creates a hidden window, and any message posted during WaitForSingle/MultipleObject/s can cause a deadlock.

So, I need to call the MsgWaitForMultiple objects. Which in turn can fail if messages are not pumped correctly. Unfortunately, I can't quite understand how to pump them in a correct way. Do I have to create my own message loop? Will the app crash if COM decides to create a messagebox?

So far it seems I have to proceed like this?

HANDLE hEvents[2] = {};

int ThreadProc(LPVOID lpParam) {
    int nRetVal = 0;

    CoInitialize(NULL);

    CComPtr<ISomething> smthn;
    smthn.CoCreateInstance(...);

    MSG msg = {};

    bool bRun = true;

    while(bRun) {
        while(PeekMessage(&msg, ??NULL/-1??, 0, 0, PM_REMOVE)) { /*Which one here?*/
            if(msg.Message == WM_QUIT) {
                bRun = false;
                nRetVal = msg.wParam;
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        if(MsgWaitForMultipleObjects(2, &hEvents, ...)) {
            if(exitevent) { bRun = false; nRetVal = 0; }
            else if(processevent) { [processdata] }
        }
    }

    smthn.release();

    CoUninitialize();
    return nRetVal;
}

But what about hidden window, messageboxes, am I even on the right path?

+1  A: 

Seems like overkill, but this worked for me :

int     waitAndDispatch( HANDLE* event_handle, unsigned int ev_count, DWORD timeout )
{
    int     rval = -1;
    bool    bLoop = true;       // if the loop should terminate

    HANDLE* pHList = new HANDLE[ev_count];
    for( unsigned int i = 0; i < ev_count; ++i )
    {
        pHList[i] = event_handle[i];
    }

    while( bLoop )
    {
        DWORD res = ::MsgWaitForMultipleObjects( ev_count, pHList, false, timeout, QS_ALLPOSTMESSAGE | QS_SENDMESSAGE );
        if( res == WAIT_OBJECT_0 + ev_count )       // messages arrived
        {
            MSG tmpMsg;
            bool hasMsg = true;
            while( bLoop && hasMsg )
            {
                ::PeekMessage( &tmpMsg, 0, 0, 0, PM_NOREMOVE );
                if( ::PeekMessage( &tmpMsg, 0, WM_USER, WM_USER, PM_REMOVE ) ||     // WM_USER for COM
                    ::PeekMessage( &tmpMsg, 0, 0, WM_KEYFIRST - 1, PM_REMOVE )      // all destroy update, ...
                    )
                {
                    DWORD val = ::WaitForMultipleObjects( ev_count, pHList, false, 0 );
                    if( val >= WAIT_OBJECT_0 && val <= (WAIT_OBJECT_0 + ev_count) )
                    {
                        rval = val - WAIT_OBJECT_0;
                        bLoop = false;
                    }
                    ::DispatchMessage( &tmpMsg );
                }
                else
                {
                    hasMsg = false;
                }
            }
        }
        else if( res >= WAIT_OBJECT_0 && res < (WAIT_OBJECT_0 + ev_count) )
        {
            rval = res - WAIT_OBJECT_0;
            bLoop = false;
        }
        else if( res == WAIT_TIMEOUT )
        {
            rval = ev_count;
            bLoop = false;
        }
        else
        {
            rval = -1;
            bLoop = false;
        }
    }
    delete[] pHList;

    return rval;
}

I had to write this piece for VB6 and its thread interactions over com compartments ... .

If you initialize your thread apartment with CoInitializeEx( 0, COINIT_MULTITHREADED ), your COM calls will not be queued into an message queue. But then you have the problem of objects created in different COM apartments. These need to be marshaled ... .

Christopher
Can you recommend me an article I could try to read? I would like to understand the different arguments and internals of that routing better, like when to remove messages and when not. Like why >// WM_USER for COM
Madman
Years ago, when I wrote that code, i couldn't find anything on this behavior. It took me days of debugging deep in COM/VB6 to come up with a solution. Nowadays I would likely approach problems with this, by creating a(/several) dedicated threads separating GUI/COM objects, just to get around the whole apartment model of COM.
Christopher
-1 for bizzare message filtering thats going to break - for no good reason - any other message windows that use messages > WM_USER (such as the WM_APP range).
Chris Becke
Yes, I know. This is extremely bizarre, but your comment is wrong. Only messages < WM_KEYFIRST (0x00FF) and the message WM_USER are dispatched. Sure, do it the right way. Do not let COM objects and COM calls cross thread/apartment boundaries.
Christopher
+1  A: 

Just use CoWaitForMultipleHandles and it will do the necessary message pumping on the hidden COM window for inter-thread syncing.

The hidden window is of class OleMainThreadWndClass with OleMainThreadWndName as caption but on win9x its class is WIN95 RPC Wmsg. It's hidden which means you can't use straight EnumThreadWindows to find it.

wqw