迁移到进程内COM服务器的进程外COM服务器会导致回调阻塞

时间:2014-05-19 14:57:45

标签: c++ com

我们有一个现有的网络消息服务器,它实现了我们正在迁移到进程内COM对象的自定义通信协议。服务器实现为免费的线程进程外COM服务器。客户端可以向服务器注册(想想发布 - 订阅)以接收消息。

迁移后,我们在调用GUI相关函数时发现了几个死锁,例如: SetWindowPos,RedrawWindow,EnumWindows等。经过一些研究后我发现这是由于回调发生在主GUI线程(消息泵)以外的线程上。回调是从IUnknown派生的自定义COM回调,因此我们不使用连接点。

有趣的是,如果创建一个简单的MFC对话框项目,一切正常。但在我们的主要应用程序中失败了。我们的主要应用程序(20多年历史,现在从未设计过它)是一个MFC对话框项目。在响应消息时,MFC对话框项目加载DLL,该DLL又创建MFC对话框并向COM服务器注册消息。当DLL对话框收到消息时,它会调用上面三个示例GUI相关函数之一,并阻塞。

这用于处理进程外的COM服务器,所以我知道有一种方法可以使回调在客户端主GUI线程的上下文中,但是无法找到“魔术”代码让它起作用。我偶然发现SendMessageCallback但无法调用异步回调函数。

进入客户端的COM回调是通过进程内COM服务器内的线程处理的。线程用CoInitialize初始化,我的研究意味着STA。我已经尝试将其更改为CoInitializeEx并使用MTA进行测试。我也尝试将COM服务器线程模型更改为STA,MTA,Both和Free。如你所见,我真的开始投掷飞镖。这里。

任何帮助都将不胜感激。

我确实订购了Don Box书籍,Essential COM和Effective COM,但他们不会在本周晚些时候到达。

修改

我们的主要应用: 在CApp派生类的内部'InitInstance

  1. AfxOleInit()
  2. CDialog派生类'OnInitDialog

    内部
    1. CoCreateInstance(消息传递COM对象)
    2. 分配回调指针。客户端回调是一个派生自IUnknown的接口。然后,回调类派生自客户端回调并实现AddRef / Release接口。当客户端创建回调时,客户端会将指针传递给自身,以便回调可以调用客户端。
    3. MessageComObject->注册(回调指针)
    4. MessageCOMObject内部:

      1. MessageComObject将回调添加到GIT并保存cookie。
      2. MessageComObject启动一个线程,将回调发送给客户端。
      3. 稍后主应用程序通过回调指针接收“消息”。回调是从MessageComObject中启动的。 在MessageCOMObject内部:

        1. MessageComObject使用cookie
        2. 从GIT获取回调
        3. MessageComObject调用回调接口上的函数
        4. 在回调接口的派生类中:

          1. 调用客户端回调函数
          2. 在CDialog派生类'回调处理程序中:

            1. 在DLL上调用LoadLibrary(加载的内容是数据驱动的)
            2. 从DLL调用导出的函数。
            3. DLL内部导出的函数:

              1. 创建CWnd对象
              2. CoCreateInstance(消息传递COM对象)
              3. 分配回调指针(与上面相同)
              4. MessageCOMObject->注册
              5. MessageCOMObject内部:

                1. MessageComObject将回调添加到GIT并保存cookie。
                2. MessageComObject启动一个线程,将回调发送给客户端。
                3. 稍后,DLL会收到一条消息:

                  1. MessageComObject使用cookie从GIT获取回调,GIT返回'灾难性失败'
                  2. 代码

                    在IDL文件中声明回调:

                    [
                        object,
                        uuid(...),
                        pointer_default(unique)
                    ]
                    interface IMessageRouterCallback : IUnknown
                    {
                       ...
                    };
                    
                    [
                        object,
                        uuid(...),
                        pointer_default(unique)
                    ]
                    interface IMessageRouter : IUnknown
                    {
                        ...
                    };
                    
                    [
                        uuid(....),
                        version(1.0),
                    ]
                    library MessageRouterCOMLib
                    {
                        importlib("stdole2.tlb");
                        [
                            uuid(....)  
                        ]
                        coclass MessageRouter
                        {
                                [default] interface IMessageRouter;
                        };
                    };
                    

                    进程内COM DLL

                    class ATL_NO_VTABLE CMessageRouter :
                        public CComObjectRootEx<CComMultiThreadModel>,
                        public CComCoClass<CMessageRouter, &CLSID_MessageRouter>,
                        public IMessageRouter
                    {
                    public:
                    
                    DECLARE_GET_CONTROLLING_UNKNOWN()
                    
                    BEGIN_COM_MAP(CMessageRouter)
                        COM_INTERFACE_ENTRY(IMessageRouter)
                        COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
                    END_COM_MAP()
                    
                        CComPtr<IUnknown> m_pUnkMarshaler;
                    
                        DECLARE_PROTECT_FINAL_CONSTRUCT()
                    
                        DWORD callbackRegistrationId;
                    
                        HRESULT FinalConstruct()
                        {
                            //- TODO: Add error checking
                            IUnknown *unknown;
                            DllGetClassObject(IID_IMessageRouterCallback, IID_IUnknown, (void**)&unknown);
                            CoRegisterClassObject(IID_IMessageRouterCallback, unknown, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &callbackRegistrationId);
                            CoRegisterPSClsid(IID_IMessageRouterCallback, IID_IMessageRouterCallback);
                    
                            return CoCreateFreeThreadedMarshaler(GetControllingUnknown(), 
                        }
                    
                        void FinalRelease()
                        {
                            if (callbackRegistrationId)
                                CoRevokeClassObject(callbackRegistrationId);
                            callbackRegistrationId = 0;
                    
                            if (m_pUnkMarshaler)
                                m_pUnkMarshaler.Release();
                        }
                    }
                    

                    注册回调的地方:

                    boost::lock_guard<boost::mutex> guard(callbacksMutex);
                    
                    //- callback is the raw interface pointer from the client
                    //- The class Callback contains a pointer to the raw client callback
                    //- and the global process ID.  The raw pointer is AddRef/Release in
                    //- the Callback class' constructor and destructor.
                    ptr = Callback::Pointer(new Callback(callback));
                    DWORD callbackId = 0;
                    
                    HRESULT result = globalInterfaceTable->RegisterInterfaceInGlobal(callback, IID_IMessageRouterCallback, &callbackId);
                    if (SUCCEEDED(result))
                    {
                        ptr->globalCallbackId = callbackId;
                        callbackMap[callback] = ptr;
                        //- NOTE: uses raw pointer as key into map.  This key is only
                        //-       ever used during un-register.
                        //- callbackMap is a std::map of Callback instances indexed by the raw pointer.
                    }
                    

                    回调线程:

                    CoInitialize(NULL);
                    
                    while (processCallbackThreadRunning)
                    {
                        QueueMessage message = messageQueue.Pop();
                        if (!processCallbackThreadRunning)
                            break;
                    
                        //- Make a copy because callbacks may be added/removed during
                        //- this call.
                        CallbackMap callbacks;
                        {
                            boost::lock_guard<boost::mutex> guard(callbacksMutex);
                            callbacks = callbackMap;
                        }
                    
                        for (CallbackMap::iterator callback = callbacks.begin(); callback != callbacks.end(); ++callback)
                        {
                            try
                            {
                                IMessageRouterCallback *mrCallback = NULL;
                                HRESULT result = globalInterfaceTable->GetInterfaceFromGlobal(callback->second->globalCallbackId,IID_IMessageRouterCallback,(void **) &mrCallback);
                                if (SUCCEEDED(result))
                                {
                                    result = mrCallback->MessageHandler((unsigned char*)message.messageBuffer->Data(), message.messageBuffer->Length(), message.metaData.id, message.metaData.fromId, CComBSTR(message.metaData.fromAddress.c_str()));
                                    if (FAILED(result))
                                    {
                                        ... log debug
                                    }
                                }
                                else
                                {
                                    ... log debug
                                }
                            }
                            catch (...)
                            {
                                ... log debug
                            }
                        }
                    
                        MessagePool::Push(message.messageBuffer);
                    }
                    
                    CoUninitialize();
                    

                    客户端回调的实现:

                    template <class CALLBACKCLASS>
                    class CMessageRouterCallback :
                        public CComBase<IMessageRouterCallback>
                    {
                        CMessageRouterCallback( CALLBACKCLASS *pCallbackClass = NULL) :
                            m_pCallbackClass(pCallbackClass)
                        {
                            AddRef();   //- Require by CComBase.  This makes it so that this
                                        //- class does not get automatically deleted when
                                        //- Message Router is done with the class.
                        }
                    
                        STDMETHODIMP MessageHandler( UCHAR *szBuffer, int nSize, DWORD dwTransCode, DWORD dwSenderID, BSTR bstrFromIP )
                        {
                            if ( m_pCallbackClass != NULL )
                            {
                                m_pCallbackClass->MessageHandler( szBuffer, nSize, dwTransCode, dwSenderID, bstrFromIP );
                            }
                    
                            return S_OK;
                        }
                    }
                    

                    CComBase实现了IUnknown接口:

                    template < class BASE_INTERFACE, const IID* piid = &__uuidof(BASE_INTERFACE) >
                    class CComBase :
                        public BASE_INTERFACE
                    {
                    protected:
                        ULONG   m_nRefCount;
                    
                    public:
                    
                        CComBase() : m_nRefCount(0) {}
                    
                        STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
                        {
                            if (riid == IID_IUnknown || riid == *piid )
                                *ppv=this;
                            else
                                return *ppv=0,E_NOINTERFACE;
                            AddRef();
                    
                            return S_OK;
                        }
                    
                        STDMETHODIMP_(ULONG) AddRef()
                        {
                            return ++m_nRefCount;
                        }
                    
                        STDMETHODIMP_(ULONG) Release()
                        {
                            if (!--m_nRefCount)
                            {
                                delete this;
                                return 0;
                            }
                            return m_nRefCount;
                        }
                    
                        virtual ~CComBase() {}
                    
                    };
                    

                    客户端然后使用它:

                    class Client
                    {
                        CMessageRouterCallback<Client> *callback;
                    
                        Client(IMessageRouter *messageRouter)
                        {
                            callback = new CMessageRouterCallback<this>();
                    
                            messageRouter->Register(..., callback);
                        }
                    
                        void MessageHandler(...) { ... }
                    }
                    

1 个答案:

答案 0 :(得分:1)

这些回调的注册方式有问题。可能的原因可能是:

  • 指向GUI线程中回调管理器接口的直接指针,因此您也可以直接向STA对象提供指向回调管理器的指针。

    您添加的代码中的Callback实例似乎正是这样做的,它在销毁时无法盲目调用原始指针Release

    < / LI>
  • 您的服务器对象已与CoCreateFreeThreadedMarshaler封送(差别不大)。

    使用FTM,绝不能使用原始指针,必须始终编组您要保留的接口指针并解组之前保留的接口指针,最好使用GIT。我的意思是总是,如果你打算保证安全。

我建议您将服务器对象保留在MTA(ThreadingModel="Free")或NA(ThreadingModel="Neutral")中,确保您通过CoCreateInstance[Ex]或GUI线程以某种方式访问​​它们或CoGetClassObjectIClassFactory::CreateInstance(或任何其他对象激活API),并让&#34;魔法&#34;发生。这是透明的,因为它可以在不使用GIT或手动编组线程之间的情况下获得。