簡體   English   中英

進程外COM服務器遷移到進程內COM服務器導致回調阻塞

[英]Out-of-process COM server migrated to in-process COM server causes callbacks to block

我們有一個現有的網絡消息傳遞服務器,該服務器實現了自定義的通信協議,該協議正在遷移到進程內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()

在CDialog派生類的內部'OnInitDialog'

  1. CoCreateInstance(消息COM對象)
  2. 分配回調指針。 客戶端回調是從IUnknown派生的接口。 然后,從客戶端回調派生一個回調類,並實現AddRef / Release接口。 當客戶端創建回調時,客戶端將指針傳遞給自身,以便回調可以調用客戶端。
  3. MessageComObject-> Register(回調指針)

在MessageCOMObject內部:

  1. MessageComObject將回調添加到GIT並保存cookie。
  2. MessageComObject啟動一個線程以將回調“發送”到客戶端。

某個時間點后,主應用程序通過回調指針接收到“消息”。 回調從MessageComObject內部啟動。 在MessageCOMObject內部:

  1. MessageComObject使用Cookie從GIT獲取回調
  2. MessageComObject在回調接口上調用一個函數

在回調接口的派生類中:

  1. 調用客戶端回調函數

在CDialog派生類的回調處理程序中:

  1. 在DLL上調用LoadLibrary(加載的是數據驅動的)
  2. 從DLL調用導出的函數。

DLL的導出函數內部:

  1. 創建CWnd對象
  2. CoCreateInstance(消息COM對象)
  3. 分配回調指針(與上面相同)
  4. MessageCOMObject->注冊

在MessageCOMObject內部:

  1. MessageComObject將回調添加到GIT並保存cookie。
  2. MessageComObject啟動一個線程以將回調“發送”到客戶端。

某個時間點之后,DLL收到一條消息:

  1. MessageComObject使用cookie從GIT獲取回調,GIT返回“災難性失敗”

在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(...) { ... }
}

這些回調如何注冊存在問題。 可能的原因可能是:

  • GUI線程中指向回調管理器接口的直接指針,因此您也將指向STA對象的直接指針提供給回調管理器。

    您添加的代碼中的Callback實例似乎正是在這樣做,它在銷毀時不能盲目調用原始指針的Release

  • 您的服務器對象與CoCreateFreeThreadedMarshaler封送處理(沒有太大區別)。

    使用FTM,您絕不能使用原始指針,並且必須始終封送要保留的接口指針,並取消封送以前保留的接口指針,最好使用GIT。 我的意思是永遠 ,如果您打算確保事情安全。

我建議您將服務器對象保留在MTA( ThreadingModel="Free" )或NA( ThreadingModel="Neutral" )中,確保通過CoCreateInstance[Ex]CoGetClassObjectIClassFactory::CreateInstance在GUI線程中以某種方式訪問​​它們(或任何其他對象激活API),然后讓“魔術”發生。 這是盡可能透明的,而無需使用GIT或手動在線程之間封送處理。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM