[英]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中
在CDialog派生類的內部'OnInitDialog'
在MessageCOMObject內部:
某個時間點后,主應用程序通過回調指針接收到“消息”。 回調從MessageComObject內部啟動。 在MessageCOMObject內部:
在回調接口的派生類中:
在CDialog派生類的回調處理程序中:
DLL的導出函數內部:
在MessageCOMObject內部:
某個時間點之后,DLL收到一條消息:
碼
在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]
或CoGetClassObject
和IClassFactory::CreateInstance
在GUI線程中以某種方式訪問它們(或任何其他對象激活API),然后讓“魔術”發生。 這是盡可能透明的,而無需使用GIT或手動在線程之間封送處理。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.