进程外 COM 服务器卡住
Posted
技术标签:
【中文标题】进程外 COM 服务器卡住【英文标题】:Out-of-proc COM server stuck 【发布时间】:2013-01-20 03:27:06 【问题描述】:我正在使用进程外 COM 服务器(使用 DECLARE_CLASSFACTORY_SINGLETON 实现的 COM 单例“引擎”),它在 STA 中工作(CComSingleThreadModel,_ATL_APARTMENT_THREADED)。
COM 服务器客户端:
-
ActiveScript (JScript),(我使用 AddNamedItem 传递引擎引用)。
两个独立的 IE BHO。
BHO 定期调用 Engine::dispatchEvent,Engine 调用 ActiveScript 的 javascript 函数。 在我同时打开两个 BHO 之前,此架构运行良好。
如果我打开两个 BHO,当我调用 ActiveScript 的函数(使用 IDispatch/Invoke)时会发生卡住。 我不创建任何额外的线程。
一些注意事项:
如果我不将从 BHO 检索到的对象传递给 ActiveScript(或将其替换为在 Engine 中创建的相同对象),一切正常。 仅当 JScript 垃圾收集器尝试释放从 BHO 检索到的对象(调用堆栈中的 IUnknown_Release_Proxy)时才会发生卡住。调用栈:
> ntdll.dll!_ZwWaitForMultipleObjects@20() + 0x15 bytes
ntdll.dll!_ZwWaitForMultipleObjects@20() + 0x15 bytes
KernelBase.dll!_WaitForMultipleObjectsEx@20() + 0x100 bytes
kernel32.dll!_WaitForMultipleObjectsExImplementation@20() + 0x8e bytes
user32.dll!_RealMsgWaitForMultipleObjectsEx@20() + 0xe2 bytes
ole32.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled) Line 1222 C++
ole32.dll!ModalLoop(CMessageCall * pcall) Line 211 C++
ole32.dll!ThreadSendReceive(CMessageCall * pCall) Line 4979 C++
ole32.dll!CRpcChannelBuffer::SwitchAptAndDispatchCall(CMessageCall * * ppCall) Line 4454 + 0x6 bytes C++
ole32.dll!CRpcChannelBuffer::SendReceive2(tagRPCOLEMESSAGE * pMessage, unsigned long * pstatus) Line 4076 C++
ole32.dll!CCliModalLoop::SendReceive(tagRPCOLEMESSAGE * pMsg, unsigned long * pulStatus, IInternalChannelBuffer * pChnl) Line 899 + 0x17 bytes C++
ole32.dll!CAptRpcChnl::SendReceive(tagRPCOLEMESSAGE * pMsg, unsigned long * pulStatus) Line 583 + 0xd bytes C++
ole32.dll!CCtxComChnl::SendReceive(tagRPCOLEMESSAGE * pMessage, unsigned long * pulStatus) Line 734 + 0xa bytes C++
ole32.dll!NdrExtpProxySendReceive(void * pThis, _MIDL_STUB_MESSAGE * pStubMsg) Line 1932 C++
rpcrt4.dll!@NdrpProxySendReceive@4() + 0xe bytes
rpcrt4.dll!_NdrClientCall2() + 0x144 bytes
ole32.dll!ObjectStublessClient(void * ParamAddress, long Method) Line 474 + 0x8 bytes C++
ole32.dll!_ObjectStubless@0() Line 154 Asm
ole32.dll!RemoteReleaseRifRefHelper(IRemUnknown * pRemUnk, int fReleaseRemUnkProxy, int fProcessingPostedMessage, OXIDEntry * pOXIDEntry, unsigned short cRifRef, tagREMINTERFACEREF * pRifRef, IUnknown * pAsyncRelease) Line 6770 + 0xc bytes C++
ole32.dll!RemoteReleaseRifRef(CStdMarshal * pMarshal, OXIDEntry * pOXIDEntry, unsigned short cRifRef, tagREMINTERFACEREF * pRifRef) Line 6694 C++
ole32.dll!CStdMarshal::DisconnectCliIPIDs() Line 3964 C++
ole32.dll!CStdMarshal::Disconnect(unsigned long dwType) Line 3273 C++
ole32.dll!CStdIdentity::~CStdIdentity() Line 312 C++
ole32.dll!CStdIdentity::`scalar deleting destructor'() + 0xd bytes C++
ole32.dll!CStdIdentity::CInternalUnk::Release() Line 767 C++
ole32.dll!IUnknown_Release_Proxy(IUnknown * This) Line 1773 C++
oleaut32.dll!_VariantClear@4() + 0xac9 bytes
jscript.dll!VAR::Clear() + 0x50 bytes
jscript.dll!GcAlloc::ReclaimGarbage() + 0xa2 bytes
jscript.dll!GcContext::Reclaim() + 0x8e bytes
jscript.dll!GcContext::CollectCore() - 0x72f bytes
jscript.dll!GcContext::Collect() + 0x34 bytes
jscript.dll!CScriptRuntime::Run() - 0x864f bytes
jscript.dll!ScrFncObj::CallWithFrameOnStack() + 0xf3 bytes
jscript.dll!ScrFncObj::Call() + 0x84 bytes
jscript.dll!NameTbl::InvokeInternal() + 0x113 bytes
jscript.dll!VAR::InvokeByDispID() + 0x73 bytes
jscript.dll!CScriptRuntime::Run() + 0x1d89 bytes
jscript.dll!ScrFncObj::CallWithFrameOnStack() + 0xf3 bytes
jscript.dll!ScrFncObj::Call() + 0x84 bytes
jscript.dll!NameTbl::InvokeInternal() + 0x113 bytes
jscript.dll!VAR::InvokeByDispID() + 0x73 bytes
jscript.dll!CScriptRuntime::Run() + 0x1d89 bytes
jscript.dll!ScrFncObj::CallWithFrameOnStack() + 0xf3 bytes
jscript.dll!ScrFncObj::Call() + 0x84 bytes
jscript.dll!NameTbl::InvokeInternal() + 0x12c6 bytes
jscript.dll!VAR::InvokeByDispID() + 0x73 bytes
jscript.dll!NameTbl::GetVal() + 0x3b bytes
实现细节:
// Engine (out of process COM singleton)
class ATL_NO_VTABLE CEngine :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CEngine, &CLSID_Engine>,
public IDispatchImpl<IEngine, &IID_IEngine, &LIBID_EngineLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
DECLARE_CLASSFACTORY_SINGLETON(CEngine)
STDMETHOD(dispatchEvent)(BSTR name, IDispatch* pEvent, VARIANT_BOOL* pbSuccess)
// pEvent is CPropertyStore instance
ActiveScriptDispatch.Invoke1(L"FuncName", pEvent, &varResult);
// BHO
class CPropertyStore :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CPropertyStore, &CLSID_NULL>,
public IDispatch
BEGIN_COM_MAP(CPropertyStore)
COM_INTERFACE_ENTRY(IUnknown)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
BOOL SetProperty(CString strName, VARIANT *value)
// Store value in CAtlArray
// IDispatch impl
STDMETHOD(GetTypeInfoCount)(UINT *pctinfo);
STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
VARIANT *pVarResult,EXCEPINFO *pExcepInfo, UINT *puArgErr);
class ATL_NO_VTABLE CBHO :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CBHO, &CLSID_BHO>,
public IObjectWithSiteImpl<CBHO>,
public IDispatchImpl<IBHO, &IID_IBHO, &LIBID_Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IDispEventImpl<1, CBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 0>
void onEvent(...)
if(m_pEngine == NULL && SUCCEEDED(m_pEngine.CoCreateInstance(CLSID_Engine)))
CComObject<CPropertyStore> *pEvent = NULL;
HRESULT hRes = CComObject<CPropertyStore>::CreateInstance(&pEvent);
CComVariant varEvent(pEvent);
CComVariant varName(L"EventName");
CComVariant varResult;
m_pEngine.Invoke2(L"dispatchEvent", &varName, &varEvent, &varResult);
【问题讨论】:
是的,当 Javascript 尝试释放您的对象时,由于所需的线程上下文切换导致死锁。您需要查看最初创建对象的线程并了解它为什么没有响应。如果它被阻止,那么你需要 MsgWaitForMultipleObjectsEx 来允许这个封送调用完成。 BHO 线程没有响应,因为它正在等待 dispatchEvent 结果(oleaut32.dll!_IDispatch_Invoke_Proxy,user32.dll!_RealMsgWaitForMultipleObjectsEx 在调用堆栈中)。 我已经添加了实现细节。 看起来脚本引擎(它是垃圾收集器)正在释放属于不同公寓的对象。您的引擎是 STA,是在 MTA 中进行脚本操作,还是在另一个 STA 中?如果是 MTA,您应该重新设计以在调用期间不阻塞 STA(例如,来自工作线程的 dispatchEvent,如果没有看到代码就不确定这是否可能)。 脚本操作应在 STA 中,ActiveScript 在 Engine 中使用 CoCreateInstance(L"JScript") 创建。 【参考方案1】:您的 BHO(浏览器助手对象)位于单线程单元中。 STA 中对另一个 STA(不同线程)上的对象进行的每个 COM 调用在方法调用中“转换”之前由消息队列中的消息排序。
这通常不是问题,因为大多数时候调用都是由单线程的 GUI 触发的。 COM 调用与WM_LBUTTONUP
消息等一起等待。
在您的情况下发生的情况是,在为onEvent
提供服务时,您将 BHO 对象发送到另一个线程,在另一个进程中,即您的进程外 COM 对象。当您尝试从您的 MTA 公寓回调原始对象时,一条 Windows 消息将发布到托管它的 Internet Explorer 进程中的 BHO STA 线程。但是消息队列仍在忙于为原始请求提供服务。
这解释了你的死锁,以及为什么传递字符串有效。
【讨论】:
这并不完全正确。只有对 STA 对象的跨单元调用由消息队列处理开始。此外,常规 STA(相对于具有自定义IMessageFilter
或 WInRT 的 ASTA 的 STA)在从它进行跨公寓调用时处理消息,因此它是可重入的,它不应该像那样死锁。我认为操作没有提供足够的细节来解决问题,死锁在别处。
你说得对,只有跨公寓呼叫使用 Windows 消息,我会更新我的答案。但我记得我“偶然发现”了这样一个死锁,并且 Windows 消息的不可重入是问题所在。我承认那是 10 多年前的事了。 This post和你说的一样,我的记性一定很差……以上是关于进程外 COM 服务器卡住的主要内容,如果未能解决你的问题,请参考以下文章