进程外 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 服务器卡住的主要内容,如果未能解决你的问题,请参考以下文章

进程外 COM 服务器 - 无法创建文件

带有 Python 的 COM 本地服务器(进程外)

64 位托管进程:进程外 32 位 COM 服务器非默认接口不可用

如何加载 COM dll 模块并将其接口公开为进程外服务器

COM“进程外”服务器的 C++/CLI 等效项

从 TLB 导入的接口上的进程外 COM 服务器的 QueryInterface