在基于.NET的COM对象的CoCreateInstance期间挂起的应用程序

我有一个C ++ DLL,它创建一个在.NET中实现的COM对象的实例。 在许多情况下,这工作正常,但在某些情况下,它会挂起应用程序,我看到它停留在下面的调用堆栈(这只是我的DLL代码级别下面的部分):

ntdll.dll!_NtAlpcSendWaitReceivePort@32() rpcrt4.dll!LRPC_CASSOCIATION::AlpcSendWaitReceivePort(unsigned long,struct _PORT_MESSAGE *,struct _ALPC_MESSAGE_ATTRIBUTES *,struct _PORT_MESSAGE *,unsigned long *,struct _ALPC_MESSAGE_ATTRIBUTES *,union _LARGE_INTEGER *) rpcrt4.dll!LRPC_BASE_CCALL::DoSendReceive(void) rpcrt4.dll!LRPC_BASE_CCALL::SendReceive(struct _RPC_MESSAGE *) rpcrt4.dll!_I_RpcSendReceive@4() rpcrt4.dll!_NdrSendReceive@8() rpcrt4.dll!@NdrpSendReceive@4() rpcrt4.dll!_NdrClientCall2() combase.dll!ServerAllocateOXIDAndOIDs(void * hServer, void * phProcess, unsigned __int64 * poxidServer, unsigned long cOids, unsigned __int64 * aOid, unsigned long * pcOidsAllocated, const tagOXID_INFO * poxidInfo, tagDUALSTRINGARRAY * pdsaStringBindings, tagDUALSTRINGARRAY * pdsaSecurityBindings, unsigned __int64 * pdwOrBindingsID, tagDUALSTRINGARRAY * * ppdsaOrBindings) Line 246 combase.dll!CRpcResolver::ServerRegisterOXID(const tagOXID_INFO & oxidInfo, unsigned __int64 * poxid, unsigned long * pcOidsToAllocate, unsigned __int64 * arNewOidList) Line 1020 combase.dll!OXIDEntry::RegisterOXIDAndOIDs(unsigned long * pcOids, unsigned __int64 * pOids) Line 1631 combase.dll!OXIDEntry::AllocOIDs(unsigned long * pcOidsAlloc, unsigned __int64 * pOidsAlloc, unsigned long cOidsReturn, unsigned __int64 * pOidsReturn) combase.dll!CComApartment::CallTheResolver() Line 856 combase.dll!CComApartment::InitRemoting() Line 1166 combase.dll!CComApartment::StartServer() Line 1386 combase.dll!InitChannelIfNecessary() Line 1393 combase.dll!ClassicSTAThreadWaitForHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * pdwIndex) Line 34 combase.dll!CoWaitForMultipleHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * lpdwindex) mscorwks.dll!NT5WaitRoutine(int,unsigned long,int,void * *,int) mscorwks.dll!MsgWaitHelper(int,void * *,int,unsigned long,int) mscorwks.dll!Thread::DoAppropriateAptStateWait(int,void * *,int,unsigned long,enum WaitMode) mscorwks.dll!Thread::DoAppropriateWaitWorker(int,void * *,int,unsigned long,enum WaitMode) mscorwks.dll!Thread::DoAppropriateWait(int,void * *,int,unsigned long,enum WaitMode,struct PendingSync *) mscorwks.dll!CLREvent::WaitEx(unsigned long,enum WaitMode,struct PendingSync *) mscorwks.dll!CLREvent::Wait(unsigned long,int,struct PendingSync *) mscorwks.dll!CExecutionEngine::WaitForEvent(void *,unsigned long,int) mscorwks.dll!ClrWaitEvent(void *,unsigned long,int) mscorwks.dll!FusionSink::Wait(void) mscorwks.dll!AssemblySink::Wait(void) mscorwks.dll!FusionBind::RemoteLoad(struct IApplicationContext *,class FusionSink *,struct IAssemblyName *,struct IAssembly *,unsigned short const *,struct IAssembly * *,struct IHostAssembly * *,struct IAssembly * *,int) mscorwks.dll!FusionBind::LoadAssembly(struct IApplicationContext *,class FusionSink *,struct IAssembly * *,struct IHostAssembly * *,struct IAssembly * *,int) mscorwks.dll!AssemblySpec::FindAssemblyFile(class AppDomain *,int,struct IAssembly * *,struct IHostAssembly * *,struct IAssembly * *,struct IFusionBindLog * *,enum StackCrawlMark *) mscorwks.dll!AppDomain::BindAssemblySpec(class AssemblySpec *,int,int,enum StackCrawlMark *) mscorwks.dll!AssemblySpec::LoadDomainAssembly(enum FileLoadLevel,class Object * *,class Object * *,int,int,int,enum StackCrawlMark *) mscorwks.dll!AssemblySpec::LoadAssembly(enum FileLoadLevel,class Object * *,class Object * *,int,int,int,enum StackCrawlMark *) mscorwks.dll!AppDomain::LoadAssemblyHelper(unsigned short const *,unsigned short const *) mscorwks.dll!AppDomain::LoadCOMClass(struct _GUID,int,int *) mscorwks.dll!GetTypeForCLSID(struct _GUID const &,int *) mscorwks.dll!EEDllGetClassObject(struct _GUID const &,struct _GUID const &,void * *) mscorwks.dll!_InternalDllGetClassObject@12() mscorwks.dll!_DllGetClassObjectInternal@12() mscoreei.dll!_DllGetClassObject@12() combase.dll!CClassCache::CDllPathEntry::GetClassObject(const _GUID & pClsid, const _GUID & pIid, void * * ppv) Line 2691 combase.dll!CClassCache::CDllPathEntry::DllGetClassObject(const _GUID & rclsid, const _GUID & riid, IUnknown * * ppUnk, int fMakeValid) Line 3892 combase.dll!CClassCache::CDllFnPtrMoniker::BindToObjectNoSwitch(const _GUID & riid, void * * ppvResult) Line 4406 combase.dll!CClassCache::GetClassObject(const ACTIVATION_PROPERTIES & ap) Line 5816 combase.dll!CServerContextActivator::CreateInstance(IUnknown * pUnkOuter, IActivationPropertiesIn * pInActProperties, IActivationPropertiesOut * * ppOutActProperties) Line 999 combase.dll!ActivationPropertiesIn::DelegateCreateInstance(IUnknown * pUnkOuter, IActivationPropertiesOut * * ppActPropsOut) Line 1854 combase.dll!CApartmentActivator::CreateInstance(IUnknown * pUnkOuter, IActivationPropertiesIn * pInActProperties, IActivationPropertiesOut * * ppOutActProperties) Line 2323 combase.dll!CProcessActivator::CCICallback(unsigned long dwContext, IUnknown * pUnkOuter, ActivationPropertiesIn * pActIn, IActivationPropertiesIn * pInActProperties, IActivationPropertiesOut * * ppOutActProperties) combase.dll!CProcessActivator::AttemptActivation(ActivationPropertiesIn * pActIn, IUnknown * pUnkOuter, IActivationPropertiesIn * pInActProperties, IActivationPropertiesOut * * ppOutActProperties, HRESULT (unsigned long, IUnknown *, ActivationPropertiesIn *, IActivationPropertiesIn *, IActivationPropertiesOut * *) * pfnCtxActCallback, unsigned long dwContext) Line 1673 combase.dll!CProcessActivator::ActivateByContext(ActivationPropertiesIn * pActIn, IUnknown * pUnkOuter, IActivationPropertiesIn * pInActProperties, IActivationPropertiesOut * * ppOutActProperties, HRESULT (unsigned long, IUnknown *, ActivationPropertiesIn *, IActivationPropertiesIn *, IActivationPropertiesOut * *) * pfnCtxActCallback) Line 1539 combase.dll!CProcessActivator::CreateInstance(IUnknown * pUnkOuter, IActivationPropertiesIn * pInActProperties, IActivationPropertiesOut * * ppOutActProperties) Line 1417 combase.dll!ActivationPropertiesIn::DelegateCreateInstance(IUnknown * pUnkOuter, IActivationPropertiesOut * * ppActPropsOut) Line 1854 combase.dll!CClientContextActivator::CreateInstance(IUnknown * pUnkOuter, IActivationPropertiesIn * pInActProperties, IActivationPropertiesOut * * ppOutActProperties) Line 713 combase.dll!ActivationPropertiesIn::DelegateCreateInstance(IUnknown * pUnkOuter, IActivationPropertiesOut * * ppActPropsOut) combase.dll!ICoCreateInstanceEx(const _GUID & OriginalClsid, IUnknown * punkOuter, unsigned long dwClsCtx, _COSERVERINFO * pServerInfo, unsigned long dwCount, unsigned long dwActvFlags, tagMULTI_QI * pResults, ActivationPropertiesIn * pActIn) Line 1645 combase.dll!CComActivator::DoCreateInstance(const _GUID & Clsid, IUnknown * punkOuter, unsigned long dwClsCtx, _COSERVERINFO * pServerInfo, unsigned long dwCount, tagMULTI_QI * pResults, ActivationPropertiesIn * pActIn) Line 376 combase.dll!CoCreateInstance(const _GUID & rclsid, IUnknown * pUnkOuter, unsigned long dwContext, const _GUID & riid, void * * ppv) Line 120 

发生悬挂的情况是满足以下所有条件时:

  1. 该应用程序在干净的Windows 2012 R2系统上运行,我刚刚运行了一个安装程序,试图安装应用程序运行的最小组件集。
  2. 在创建不相关的COM对象之前,应用程序不会创建和初始化Microsoft_InteropFormTools.InteropToolbox的实例。
  3. 应用程序以无reg方式安装,而不是使用具有更多开销的旧安装程序,并在注册表中注册COM DLL和GAC中的.NET DLL,并且可能包括最小安装程序忽略的文件。

如果我更改第一个条件并在我的本地Windows 7开发计算机而不是干净的Windows 2012服务器上运行,则不会发生此问题。 如果我更改第二个条件,以便代码在创建COM对象之前初始化InteropformTools,那么问题也不会发生。 如果我更改第三个条件以便使用旧的综合安装程序安装产品,则不会出现此问题。

如何查找此问题的来源和/或修复此问题?

在Microsoft支持和DebugDiag的帮助下,我们确定问题的原因似乎与Loader Lock有关。 Loader Lock详细记录在https://msdn.microsoft.com/en-us/library/ms173266(v=vs.120).aspx但基本上,某些限制适用于在DllMain或动态初始化静态未托管代码对象,其实例在加载DLL时需要动态初始化(因为它们位于全局范围内)。 解决这个问题的一种方法是告诉C ++编译器应该使用CLR支持编译代码,这样它就不会处理DllMain中的初始化,而是另一个不保留加载器锁的函数。

在我们的代码中,我们有一个全局声明:

 CFSCoCultureWrapper cultureWrapper; 

其中有一个构造函数在托管COM对象上调用CoCreateInstance ,后者又引用了Microsoft.InteropToolbox。 将/clr开关应用于该源文件允许在不挂起的情况下加载DLL。

目前尚不清楚为什么行为在不同的部署中发生了变化,但是,当文章链接状态时,挂起并不一定总是发生,因此调试这些问题可能很困难。 为了说明,在我们遇到问题之前,即使是我们的简单测试用例也是4级深度加载DLL – EXE加载(LoadLibrary)非托管DLL加载(CoCreateInstance)托管DLL加载Microsoft DLL。 我们决定了这些问题涉及的复杂程度,它已经足够了解,并没有进一步理解为什么问题只发生在某些部署中。

简单的回答,不要创建在构造函数期间从非托管代码加载托管代码的对象的全局实例。 使用延迟初始化或切换代码文件以使用/clr开关或使用某些方法阻止在DllMain-time初始化期间执行托管代码。 我们发现的另一种解决方法是将托管代码切换为使用.NET 4.5而不是2.0。