【问题标题】:COM + WaitForSingleObjectCOM + WaitForSingleObject
【发布时间】:2011-04-18 09:12:55
【问题描述】:

过去几天我一直在尝试为一个应用程序找到一个好的架构,经过一些研究我终于卡住了,原因是 COM。

相关应用将有多个 GUI 线程,它们会为工作线程安排工作项。工作线程将通过 CoInitialize(NULL); 初始化 COM,创建一些 COM 组件并将进入一个循环,该循环将等待 WaitForMultipleObjects(2, ...) (ExitEvent - 表示应用程序正在关闭,ManualResetEvent - 表示实际上有工作项要处理),并且在成功等待时,将处理这些项并将它们 PostMessage 回 GUI 线程。如果队列为空,ManualResetEvent 将在 worker 内部重置,并将发生在队列临界区中。

问题是 COM 像往常一样让一切变得困难 1000 倍...

如果我理解正确的话,CoInitialize(NULL);创建一个隐藏窗口,在 WaitForSingle/MultipleObject/s 期间发布的任何消息都可能导致死锁。

所以,我需要调用 MsgWaitForMultiple 对象。如果消息未正确发送,则反过来可能会失败。不幸的是,我不太明白如何以正确的方式泵送它们。我必须创建自己的消息循环吗?如果 COM 决定创建消息框,应用会崩溃吗?

到目前为止,我似乎必须这样进行?

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

但是隐藏窗口、消息框呢,我是不是走对了路?

【问题讨论】:

  • COM 对象是否在线程之间移动?还是他们在一个线程中死了?简而言之,COM 对象做了什么?我认为你的架构很遥远,因为你在 SO 上得到了一些不好的建议。
  • 我不明白这个问题。这里的实际问题是什么?
  • 另外,请解释“多个 GUI 线程”。你只是指隐藏的 COM 窗口,还是真的想拥有多个主 GUI 线程?
  • @Hans:查看 Madman 过去几天的帖子,了解这篇帖子的来源。
  • COM 对象在线程内生死存亡。 MSXML 就是其中之一,但还有其他一些我无法控制。不能保证它们是 COINIT_MULTITHREADED 安全的。

标签: c++ multithreading winapi com synchronization


【解决方案1】:

只需使用CoWaitForMultipleHandles,它就会在隐藏的 COM 窗口上进行必要的消息泵送以进行线程间同步。

隐藏窗口的类为OleMainThreadWndClass,标题为OleMainThreadWndName,但在win9x上它的类是WIN95 RPC Wmsg。它是隐藏的,这意味着您无法直接使用EnumThreadWindows 找到它。

【讨论】:

    【解决方案2】:

    看起来有点矫枉过正,但这对我有用:

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

    我必须为 VB6 以及它在 com 隔间上的线程交互编写这篇文章...。

    如果您使用CoInitializeEx( 0, COINIT_MULTITHREADED ) 初始化您的线程单元,您的COM 调用将不会排队到消息队列中。但是,您会遇到在不同 COM 单元中创建对象的问题。这些需要编组...。

    【讨论】:

    • 你能给我推荐一篇我可以尝试阅读的文章吗?我想更好地了解该路由的不同参数和内部结构,例如何时删除消息以及何时不删除消息。就像为什么 >// WM_USER for COM
    • 多年前,当我编写该代码时,我找不到任何关于这种行为的信息。我花了几天时间在 COM/VB6 中进行深入调试,才想出一个解决方案。现在我可能会通过创建一个(/几个)专用线程来分离 GUI/COM 对象来解决这个问题,只是为了绕过 COM 的整个公寓模型。
    • -1 用于会破坏的奇怪消息过滤 - 无缘无故 - 任何其他使用消息的消息窗口 > WM_USER(例如 WM_APP 范围)。
    • 是的,我知道。这非常奇怪,但是您的评论是错误的。仅发送消息
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多