Внекорректный COM-сервер застрял
Я использую COM-сервер вне процессора (COM singleton "Engine", реализованный с использованием DECLARE_CLASSFACTORY_SINGLETON), он работает в STA (CComSingleThreadModel, _ATL_APARTMENT_THREADED).
Клиент COM-сервера:
- ActiveScript (JScript), (передаю ссылку Engine с помощью AddNamedItem).
- Два независимых IE BHOs.
BHOs периодически вызывает Engine:: dispatchEvent, Engine вызывает функции JavaScript ActiveScript.
Эта архитектура работала отлично, пока я не включил два BHOs одновременно.
Если я включаю два BHOs, застревание происходит, когда я вызываю функцию ActiveScript (используя IDispatch/Invoke).
Я не создаю никаких дополнительных потоков.
Некоторые примечания:
- Если я не передаю объект, полученный из BHO, в ActiveScript (или заменит его на тот же объект, созданный в Engine), все работает нормально.
- Stuck происходит только тогда, когда сборщик мусора JScript пытается освободить объект, полученный из BHO (IUnknown_Release_Proxy в стеке вызовов).
CallStack:
> [email protected]() + 0x15 bytes
[email protected]() + 0x15 bytes
[email protected]() + 0x100 bytes
kernel32.dll!_WaitForM[email protected]() + 0x8e bytes
[email protected]() + 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++
[email protected]@4() + 0xe bytes
rpcrt4.dll!_NdrClientCall2() + 0x144 bytes
ole32.dll!ObjectStublessClient(void * ParamAddress, long Method) Line 474 + 0x8 bytes C++
[email protected]() 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++
[email protected]() + 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);
}
}
}
Ответы
Ответ 1
Ваш BHO (объект-помощник браузера) находится в одной нитевой квартире. Каждый COM-вызов в STA, который делается для объекта в другой STA (другой поток), упорядочивается сообщением в очереди сообщений перед его "преобразованием" в вызове метода.
Это обычно не проблема, потому что большинство вызовов времени запускается графическим интерфейсом, который является однопоточным. COM-вызовы ожидают своей очереди вместе с сообщениями WM_LBUTTONUP
и т.д.
Что происходит в вашем случае, так это то, что при обслуживании onEvent
вы отправляете свой объект BHO в другой поток, в другом процессе - ваш COM-объект вне процесса. Когда вы пытаетесь перезвонить к исходному объекту из своей квартиры MTA, сообщение Windows отправляется в поток BHO STA в процессе Internet Explorer, который его размещает. Но очередь сообщений по-прежнему занята обслуживанием исходного запроса.
Это объясняет ваш тупик, и почему проходит цепочка.