简体   繁体   English

RCW终结器访问冲突

[英]RCW Finalizer Access Violation

I am using COM interop for creating a managed plugin into an unmanaged application using VS2012/.NET 4.5/Win8.1. 我正在使用COM interop在使用VS2012 / .NET 4.5 / Win8.1的非托管应用程序中创建托管插件。 All the interop stuff seems to be going ok, but when I close the app I get an MDA exception telling me AV's have happened while Releasing COM objects the RCW's were holding onto during Finalizing. 所有的互操作似乎都没问题,但是当我关闭应用程序时,我得到了一个MDA异常,告诉我AV已经发生在释放COM对象时,RCW在Finalizing期间持有。

This is the call stack: 这是调用堆栈:

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    

My guess is that the Application has already destroyed its COM objects, of which some references were passed to the managed plugin - and the call to the IUnknown::Release the RCW makes makes it go boom. 我的猜测是应用程序已经销毁了它的COM对象,其中一些引用被传递给托管插件 - 而对RCW的IUnknown :: Release的调用让它变得繁荣。

I can clearly see in the output window (VS) that the app has already started unloading some of it's dll's. 我可以在输出窗口(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

So I though I would manage the lifetime my self and wrote a ComReference class that calls Marshal.ReleaseComObject. 所以我虽然会管理自己的生命,并写了一个调用Marshal.ReleaseComObject的ComReference类。 That did not work correctly and after reading up on it I have to agree that calling Marshal.ReleaseComObject in a scenrario where references are passed around freely, is not a good idea. 这不能正常工作,在阅读之后,我必须同意在一个自由传递参考文献的场景中调用Marshal.ReleaseComObject,这不是一个好主意。 Marshal.ReleaseComObject Considered Dangerous Marshal.ReleaseComObject被认为是危险的

So the question is: Is there a way to manage this situation in order not to cause AV's when exiting the host application? 所以问题是:有没有办法管理这种情况,以便在退出主机应用程序时不会导致AV?

There are only three real solutions to this problem, and I think that interpretting the "Marshall.ReleaseComObject considered dangerous" article as "Don't use Marshall.ReleaseComObject" can mislead you. 这个问题只有三个真正的解决方案,我认为将“Marshall.ReleaseComObject视为危险”文章解释为“不要使用Marshall.ReleaseComObject”会误导你。 Your takeaway could just as easily have been "don't share RCWs freely". 你的外卖可能很容易就是“不要自由分享RCW”。

Your three solutions are: 你的三个解决方案是:

1: Change the execution of your host application to unload plugins before it unloads itself. 1:在卸载插件之前,将主机应用程序的执行更改为卸载插件。 That's easier said than done. 这说起来容易做起来难。 If the plugin system of the host process includes a shutdown event, that would be a good place to deal with it. 如果主机进程的插件系统包含关闭事件,那么这将是一个处理它的好地方。 All of your services that are holding on to RCWs need to release them during shutdown. 持有RCW的所有服务都需要在关机期间释放它们。

2: Use Marshall.ReleaseComObject in a Dispose()-like pattern, ensuring that objects are only stored within a local scope in a manner similar to a using block. 2:在类似Dispose()的模式中使用Marshall.ReleaseComObject,确保对象仅以类似于using块的方式存储在本地作用域中。 This is straight-forward to implement, allows you to release the COM references deterministically, and is generally a very good first approach. 这是直接实现,允许您确定性地发布COM引用,并且通常是非常好的第一种方法。

3: Use a COM object broker that can hand out reference counted instances of RCWs and then release those objects when no one is using them. 3:使用COM对象代理,该代理可以分发RCW的引用计数实例,然后在没有人使用它们时释放这些对象。 Ensure that every consumer of those objects clean-up prior to the application unloading. 在应用程序卸载之前,确保清除这些对象的每个使用者。

Option #2 works fine as long as you don't store/share references to the managed RCW. 只要您不存储/共享对托管RCW的引用,选项#2就可以正常工作。 I would use #2 up until you identify that your COM object has high activation costs and that caching/sharing is relevant. 我会使用#2,直到您确定您的COM对象具有高激活成本并且缓存/共享相关。

This is a problem with the native COM reference counts. 这是本机COM引用计数的问题。 Your object is being Release() d from native code with refcount=1, it is destroyed, then the CLR comes along and tries Release() it. 您的对象是来自本机代码的Release() d,其refcount = 1,它被销毁,然后CLR出现并尝试Release()它。 You need to track down where the reference count is going wrong. 您需要追踪引用计数出错的位置。 It crashes in the CLR because it runs cleanup after the native code is finished. 它在CLR中崩溃,因为它在本机代码完成后运行清理。

First step is to track down the type of object that isn't being counted properly. 第一步是跟踪未正确计数的对象类型。 I did this by running gflags.exe against my .exe file and turn on "User mode stack traces". 我通过对我的.exe文件运行gflags.exe并打开“用户模式堆栈跟踪”来完成此操作。 "Full page heap" may help also. “整页堆”也可能有所帮助。

Run the application in windbg . windbg运行该应用程序。 Run .symfix . 运行.symfix Run bp clr!SafeReleasePreemp "r rcx; gc"; g 运行bp clr!SafeReleasePreemp "r rcx; gc"; g bp clr!SafeReleasePreemp "r rcx; gc"; g to log the interface pointers. bp clr!SafeReleasePreemp "r rcx; gc"; g记录接口指针。 When it crashes, the previous log entry should contain the interface pointer that was already destroyed. 当它崩溃时,前一个日志条目应该包含已经销毁的接口指针。 Run !heap -p -a [address of COM pointer] and it will print the stack of where it was released. 运行!heap -p -a [address of COM pointer] ,它将打印它被释放的位置的堆栈。

If you're unlucky, it won't crash right away, and the interface pointer that is causing problems won't be the most recent log. 如果你运气不好,它不会马上崩溃,导致问题的界面指针不会是最新的日志。 If you can run your native COM under the Debug configuration, it may help. 如果您可以在Debug配置下运行本机COM,它可能会有所帮助。

MS made the RCW header available. MS使RCW标头可用。 The members m_pIdentity (offset 0x88 on x64) and m_aInterfaceEntries (offset 0x8 on x64) are of interest. 成员m_pIdentity (x64上的偏移量0x88)和m_aInterfaceEntries (x64上的偏移量0x8)是有意义的。 The RCW is in @rdx on entry to SafeReleasePreemp 该RCW是@rdx在进入SafeReleasePreemp

Next step is to rerun with breakpoints on Interface::AddRef, Interface::QueryInterface, and Interface::Release to see which one is mismatched. 下一步是在Interface :: AddRef,Interface :: QueryInterface和Interface :: Release上重新运行断点,以查看哪一个不匹配。 _ATL_DEBUG_INTERFACES may help if you're using ATL. 如果您使用ATL, _ATL_DEBUG_INTERFACES可能会有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM