RCW终结器访问冲突



我使用COM互操作在使用VS2012/.NET 4.5/Win8.1的非托管应用程序中创建托管插件。所有的互操作操作似乎都很正常,但当我关闭应用程序时,我会收到一个MDA异常,告诉我在释放RCW在Finalizing期间抓住的COM对象时发生了AV。

这是调用堆栈:

clr.dll!MdaReportAvOnComRelease::ReportHandledException()  + 0x91 bytes 
clr.dll!**SafeRelease_OnException**()  + 0x55 bytes 
clr.dll!SafeReleasePreemp()  + 0x312d5f bytes   
clr.dll!RCW::ReleaseAllInterfaces()  + 0xf3 bytes   
clr.dll!RCW::ReleaseAllInterfacesCallBack()  + 0x4f bytes   
clr.dll!RCW::Cleanup()  + 0x24 bytes    
clr.dll!RCWCleanupList::ReleaseRCWListRaw()  + 0x16 bytes   
clr.dll!RCWCleanupList::ReleaseRCWListInCorrectCtx()  + 0x9c bytes  
clr.dll!RCWCleanupList::CleanupAllWrappers()  + 0x2cd1b6 bytes  
clr.dll!RCWCache::ReleaseWrappersWorker()  + 0x277 bytes    
clr.dll!AppDomain::ReleaseRCWs()  + 0x120cb2 bytes  
clr.dll!ReleaseRCWsInCaches()  + 0x3f bytes 
clr.dll!InnerCoEEShutDownCOM()  + 0x46 bytes    
clr.dll!WKS::GCHeap::**FinalizerThreadStart**()  + 0x229 bytes  
clr.dll!Thread::intermediateThreadProc()  + 0x76 bytes  
kernel32.dll!BaseThreadInitThunk()  + 0xd bytes 
ntdll.dll!RtlUserThreadStart()  + 0x1d bytes    

我的猜测是,应用程序已经销毁了它的COM对象,其中一些引用被传递给了托管插件,而对IUnknown::Release the RCW的调用使它蓬勃发展。

我可以在输出窗口(VS)中清楚地看到,该应用程序已经开始卸载一些dll。

'TestHost.exe': Unloaded 'C:WindowsSystem32msls31.dll'
'TestHost.exe': Unloaded 'C:WindowsSystem32usp10.dll'
'TestHost.exe': Unloaded 'C:WindowsSystem32riched20.dll'
'TestHost.exe': Unloaded 'C:WindowsSystem32version.dll'
First-chance exception at 0x00000001400cea84 in VST3PluginTestHost.exe: 0xC0000005: Access violation reading location 0xffffffffffffffff.
First-chance exception at 0x00000001400cea84 in VST3PluginTestHost.exe: 0xC0000005: Access violation reading location 0xffffffffffffffff.
Managed Debugging Assistant 'ReportAvOnComRelease' has detected a problem in 'C:Program FilesSteinbergVST3PluginTestHostVST3PluginTestHost.exe'.
Additional Information: An exception was caught but handled while releasing a COM interface pointer through Marshal.Release or Marshal.ReleaseComObject or implicitly after the corresponding RuntimeCallableWrapper was garbage collected. This is the result of a user refcount error or other problem with a COM object's Release. Make sure refcounts are managed properly.  The COM interface pointer's original vtable pointer was 0x406975a8. While these types of exceptions are caught by the CLR, they can still lead to corruption and data loss so if possible the issue causing the exception should be addressed

因此,我认为我会自己管理自己的一生,并编写了一个调用Marshal.ReleaseComObject的ComReference类。这并没有正确工作,在阅读了它之后,我不得不同意,在引用可以自由传递的场景中调用Marshal.reeaseComObject不是一个好主意。Marshal.ReleaseCom被视为危险的对象

所以问题是:有没有办法管理这种情况,以免在退出主机应用程序时导致AV?

这个问题只有三个真正的解决方案,我认为将"Marshall.ReleaseComObject被认为是危险的"文章解释为"不要使用Marshall.RelaseComObject"会误导你。你的结论很容易就是"不要随意分享RCW"。

您的三个解决方案是:

1:将主机应用程序的执行更改为在卸载插件之前先卸载插件。说起来容易做起来难。如果主机进程的插件系统包含关闭事件,那么这将是一个处理它的好地方。您所有持有RCW的服务都需要在关闭期间释放它们。

2:在类似Dispose()的模式中使用Marshall.ReleaseComObject,确保对象只以类似于using块的方式存储在本地范围内。这是直接实现的,允许您确定地释放COM引用,通常是非常好的第一种方法。

3:使用COM对象代理,它可以分发RCW的引用计数实例,然后在没有人使用这些对象时释放这些对象。确保这些对象的每个使用者都在卸载应用程序之前进行清理。

只要不存储/共享对托管RCW的引用,选项#2就可以正常工作。我会使用#2,直到您确定COM对象的激活成本很高,并且缓存/共享是相关的。

这是本机COM引用计数的问题。您的对象是来自refcount=1的本机代码的Release() d,它被销毁,然后CLR出现并尝试Release()它。您需要跟踪引用计数出错的地方。它在CLR中崩溃,因为它在本机代码完成后运行清理。

第一步是追踪未正确计数的对象类型。我通过对.exe文件运行gflags.exe并打开"用户模式堆栈跟踪"来实现这一点。"整页堆"也可能有所帮助。

windbg中运行应用程序。运行.symfix。运行bp clr!SafeReleasePreemp "r rcx; gc"; g以记录接口指针。当它崩溃时,上一个日志条目应该包含已经被破坏的接口指针。运行!heap -p -a [address of COM pointer],它将打印其发布位置的堆栈。

如果你运气不好,它不会立即崩溃,导致问题的接口指针也不会是最新的日志。如果您可以在调试配置下运行本机COM,这可能会有所帮助。

MS使RCW标头可用。感兴趣的是成员m_pIdentity(x64上偏移量0x88)和m_aInterfaceEntries(x64上的偏移量0x8)。RCW在进入SafeReleasePreemp 时处于@rdx

下一步是在接口::AddRef、接口::QueryInterface和接口::Release上使用断点重新运行,以查看哪一个不匹配。如果您正在使用ATL,_ATL_DEBUG_INTERFACES可能会有所帮助。

最新更新