I am using COM interop for creating a managed plugin into an unmanaged application using 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.
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.
I can clearly see in the output window (VS) that the app has already started unloading some of it's dll's.
'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. 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 Considered Dangerous
So the question is: Is there a way to manage this situation in order not to cause AV's when exiting the host application?
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. Your takeaway could just as easily have been "don't share RCWs freely".
Your three solutions are:
1: Change the execution of your host application to unload plugins before it unloads itself. 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.
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. This is straight-forward to implement, allows you to release the COM references deterministically, and is generally a very good first approach.
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. 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. I would use #2 up until you identify that your COM object has high activation costs and that caching/sharing is relevant.