7

我正在使用 COM 互操作将托管插件创建到使用 VS2012/.NET 4.5/Win8.1 的非托管应用程序中。所有互操作的东西似乎都正常,但是当我关闭应用程序时,我得到一个 MDA 异常,告诉我在释放 RCW 在完成期间持有的 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 的调用 RCW 使它变得繁荣起来。

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

'TestHost.exe': Unloaded 'C:\Windows\System32\msls31.dll'
'TestHost.exe': Unloaded 'C:\Windows\System32\usp10.dll'
'TestHost.exe': Unloaded 'C:\Windows\System32\riched20.dll'
'TestHost.exe': Unloaded 'C:\Windows\System32\version.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 Files\Steinberg\VST3PluginTestHost\VST3PluginTestHost.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.ReleaseComObject 不是一个好主意。 Marshal.ReleaseComObject 被认为是危险的

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

4

2 回答 2

2

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

您的三个解决方案是:

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

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

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

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

于 2014-01-13T15:53:33.150 回答
1

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

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

中运行应用程序windbg。运行.symfix。运行bp clr!SafeReleasePreemp "r rcx; gc"; g以记录接口指针。当它崩溃时,之前的日志条目应该包含已经被销毁的接口指针。运行!heap -p -a [address of COM pointer],它将打印释放它的堆栈。

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

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

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

于 2017-12-04T23:30:49.140 回答