8

I've a class in my .NET 3.5 C# WinForms application which has five methods. Each method uses different sets of C++ COM interfaces. Am using Marshal.FinalReleaseCOMObject for cleaning up these COM objects. This code works fine on this .NET platform without any issues. But when I move this application to .NET 4.0, I start getting this error in one of these methods at a line where I cast a variable from ICOMInterface1 to ICOMInterface2, i.e.:

ICOMInterface1  myVar= obj as ICOMInterface2; 

COM object that has been separated from its underlying RCW cannot be used.

And if I remove the line where am using Marshal.FinalReleaseCOMObject, I don't get this error.

What am I missing here? And how do I clean up these unmanaged COM objects from the memory on .NET 4.0 platform?

bluish
  • 26,356
  • 27
  • 122
  • 180
user74042
  • 729
  • 5
  • 14
  • 28

1 Answers1

15

The simple answer is to never use Marshal.FinalReleaseComObject unless you absolutely must. And if you do, there are some additional rules you must follow.

When a COM object is used in .NET, the runtime creates what's known as a "RCW" or "runtime callable wrapper" for that object. This RCW is just a normal object that holds a COM reference on the object. When this object is garbage collected, it will call IUnknown::Release() on the COM object, as you'd expect. What this means is unless your COM object requires that the last Release() is done at a very certain moment in time, just let the garbage collector take care of it. Many COM objects fall into this case, so absolutely verify that you must manage the call to Release() carefully.

So when you're calling FinalReleaseComObject, that is essentially decrementing the reference the RCW has on the COM object until it hits zero and the RCW then releases the COM object. At this point, this RCW is now zombied, and any use of it will give the exception you've seen. The CLR (by default) only creates a single RCW for any underlying COM object, so this means if the COM API you're using returned the same object twice, it's just going to have a single RCW. Calling FinalReleaseComObjectwould mean that suddenly all uses of that RCW are toast.

The only way to guarantee you have a unique Marshal.GetUniqueObjectForIUnknown, which prevents any RCW sharing. But like I said earlier, in most COM APIs this isn't neccessary to do in the first place, so just don't do it.

Paul Harrington wrote up a good blog post about [Final]ReleaseComObject and it's evils. It's a dangerous weapon that unless needed will only hurt you. Since you're asking this question, I'd suspect you don't actually need to be calling it at all. :-)

Jason Malinowski
  • 18,148
  • 1
  • 38
  • 55
  • Thanks Jason for your inputs.Does this explanation hold true for ReleaseCOMObject method as well?Because, when I use it instead of FinalReleaseCOMObject, I still see the same behaviour...ie.it works on 3.5 but not on 4.0.Also, was wondering what had .net framework version got to do with this behaviour?Is it because the way GC works is changed now in 4.0? – user74042 Apr 11 '12 at 16:55
  • 1
    So RCWs themselves have a refcount which is decremented when you call Marshal.ReleaseComObject. FinalReleaseComObject just calls that until the refcount hits zero. – Jason Malinowski Apr 12 '12 at 04:01
  • 1
    As far as what changed in the CLR which caused this...it's hard to say without a debugger. – Jason Malinowski Apr 12 '12 at 04:02
  • Jason, I'm unclear. 1, does the RCW decrease the ref count on the wrapped object (not the wrapper object) until it reaches 0? Or, does it dec the refcount (if there is one) on the wrapper until it reaches 0? 2, what? The CLR creates a single wrapper for any underlying COM object or COM object(s)? plural? You mean if I have some native COM object IBuffer, and I create two wrappers on that IBuffer, I'll have ... uh brain failing, 1 wrapper and 2 underlying IBuffers? This don't make sense. – eric frazer Apr 21 '17 at 06:26
  • here's what I want: Each time I create a COM object from C#, I want it to create a 1-1 RCW for that COM object. Let's call the C++ one IBuffer, and the C# one, IBuffer' (prime). When I pass around IBuffer' all over C#, I'm assuming it doesn't addref the underlying IBuffer object. When I know I will never access the underlying IBuffer any longer, I want to call a method (Marshal.XXX) which will free up the underlying native *, but all the wrappers in c# will now be holding an invalid or disposed object. Is this possible? – eric frazer Apr 21 '17 at 07:18
  • https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.releasecomobject.aspx explains the reference counting stuff in detail. Simply passing the object around multiple times doesn't increase the AddRef the underlying object, and you could use FinalReleaseComObject to have the precise operation you want to release the managed ref. The native object would be freed assuming no other unrelated AddRefs were done from anywhere else. – Jason Malinowski Apr 22 '17 at 21:59
  • @ericfrazer: this is also a _really_ good candidate for posting as a new question rather than asking follow ups in comments like this. – Jason Malinowski Apr 22 '17 at 21:59