我们有一个现有的网络消息服务器,它实现了我们正在迁移到进程内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(...) { ... }
}
答案 0 :(得分:1)
这些回调的注册方式有问题。可能的原因可能是:
指向GUI线程中回调管理器接口的直接指针,因此您也可以直接向STA对象提供指向回调管理器的指针。
您添加的代码中的Callback
实例似乎正是这样做的,它在销毁时无法盲目调用原始指针Release
。
您的服务器对象已与CoCreateFreeThreadedMarshaler
封送(差别不大)。
使用FTM,绝不能使用原始指针,必须始终编组您要保留的接口指针并解组之前保留的接口指针,最好使用GIT。我的意思是总是,如果你打算保证安全。
我建议您将服务器对象保留在MTA(ThreadingModel="Free"
)或NA(ThreadingModel="Neutral"
)中,确保您通过CoCreateInstance[Ex]
或GUI线程以某种方式访问它们或CoGetClassObject
和IClassFactory::CreateInstance
(或任何其他对象激活API),并让&#34;魔法&#34;发生。这是透明的,因为它可以在不使用GIT或手动编组线程之间的情况下获得。