Wrappers Unwrapped

6 01 2011
tl;dr; Avoid using Marshal.ReleaseComObject when possible.

When you use a COM object in .NET the .NET framework creates a Runtime Callable Wrapper (RCW) which shows up in the debugger as __ComObject. This allows interoperation between managed .NET and native COM code. One of the major differences between C++ COM and .NET is that in C++ you have deterministic destruction while in .NET objects are finalized at the garbage collector’s leisure. Which mode of operation is preferable is a matter of taste and not the topic of this post (I’ll just say that you can take my deterministic destruction when my cold dead hand goes out of scope) however mixing the two modes can cause problems.

The SOP is that the RCW calls AddRef[1] on the COM object it is holding so that it won’t disappear while it’s being used and then, when the RCW is collected by the GC, it will release this reference in its finalizer. This works if you don’t care exactly when the COM object is released and .NET exposes Marshal.ReleaseComObject for cases in which you want to manually control the object’s lifetime.

We recently re-implemented a few interfaces (that were previously native COM) in .NET and stuff stopped working. A bit of debugging later I find that we’re calling ReleaseComObject on an object that is now implemented in .NET, this causes an ArgumentException to be thrown. A cow-orker pointed me at a post titled Marshal.ReleaseComObject Considered Dangerous (well at least it’s not harmful), this post describes the problem we were facing and the solution I already put in place (check with Marshal.IsComObject before calling ReleaseComObject) but goes on saying that this isn’t enough since once you call ReleaseComObject the RCW is disconnected from the COM object and can’t be used (and that this can be a problem if the RCW is cached). I thought that this is a theoretical problem (I know we aren’t caching the RCWs) and kept trying to figure out why our code still wasn’t working.

It turns out that as in everything in programming it’s all about adding another level.  We had three levels, a .NET class (which we’ll call A) a native class (C) and class B which was native in a previous incarnation but is now .NET, A holds a reference to B which hands it a C (via a method or property), but why is this a problem?

For that we’ll need to dive a bit deeper into how RCWs work.

In order to preserve COM’s identity semantics whenever a COM object is passed into the CLR the .NET framework first checks if there is an active RCW for this object, if so it returns the existing object. You can see this by debugging your native object, after an object is returned from a native COM method (or property) it is followed by a QueryInterface for IUnknown and then by two calls to Release (one for the reference gained in the original function and one for the reference gained in QueryInterface). Additionally the RCW maintains a count of the times it has been reused, MSDN refers to this as a reference count but I’ll refer to it as marshal count in order to emphasise the difference between this number and the native COM object’s reference count. If an RCW is reused at the identity checking phase the marshal count is incremented. When ReleaseComObject is called on an RCW the marshal count is decremented. If the marshal count reaches zero the RCW releases its references to the native COM object and marks itself as invalid, any subsequent calls to the wrapped object will throw an InvalidComObjectException with the message “COM object that has been separated from its underlying RCW cannot be used”. If an RCW is collected by the GC while it is still in a valid state it will also release it’s wrapped object (regardless to its marshal count).

Now consider the situation we had when B was a native COM object. Whenever A got a C from B said C had crossed the CLR boundary and a new RCW was created (or at least the marshal count was incremented). Then when A was done with C it called ReleaseComObject and all was fine and dandy.

In order to highlight the point the following sequence diagram shows what happens if A acquires C several times in a loop. The RCWs (in blue) represent the boundary between native code and the CLR.

Sequence diagram of flow when B is a COM object

But now B is managed so A‘s RCW was created once when B acquired it. Now when B hands C to A it is passing a regular managed reference (that of the RCW), .NET references aren’t aware of being copied (there’s no copy-constructor to trigger a change in the marshal count) and when A will call ReleaseComObject it will invalidate A without B having a clue that its member is no longer valid. Note the fact that the incrementation and decrementation of the marshal count are not balanced (in respect to the loop).

Sequence diagram of flow when B is a .NET object

We have quite a few places which use ReleaseComObject, some of which are probably cargo cult programming but some are needed, how should we make sure that we detect all the problematic uses of ReleaseComObject?

The obvious solution is the one Microsoft chose, identify all the places you use ReleaseComObject and remove them unless they can be proven to be correct (this is also the route we chose). I’ll list a few other options we thought of so other people can be informed of all (?) their options.

Adopt .NET Semantics

Embrace the fact that destruction isn’t deterministic and make sure your code doesn’t depend on deterministic destruction, then remove all calls to ReleaseComObject.

  • Pro: You gain a consistent world view, your code is easier to reason about
  • Con: This can be very difficult and require a lot of work

Artificially Increment the Marshal Count

You can manually force the marshal count to be updated and do this for each RCW you hand out[2].

public static T AddRcwRef<T>(T t)
{
    IntPtr ptr = Marshal.GetIUnknownForObject(t);
    try {
        return (T)Marshal.GetObjectForIUnknown(ptr);
    }
    finally {
         Marshal.Release(ptr); // done with the IntPtr
    }
}
  • Pro: You don’t have to change your client code (perhaps you can’t)
  • Con: Every developer in the organization must be familiar with this (anti) idiom and maintain its use in the correct places. Also if you really do need deterministic destruction you’ll have to be sure to call ReleaseComObject on your member at the correct time otherwise the COM object won’t get destroyed.

Get the Finalizer to do the work

Instead of calling ReleaseComObject discard your reference to the object and let the GC check if it’s still in use.

public static void ReleaseRcw<T>(ref T t)
{
    t = default(T);
    GC.Collect();
    GC.WaitForPendingFinalizers();
}
  • Pro:
    • You gain a submission to TDWTF
    • If you thought of this you’ve probably exhausted all your other options and it’s time to choose the least bad one.
  • Con:
    • If the object in question is an argument to a function the call stack will have a reference and this won’t work
    • Performance killer
    • etc.

Conclusion

This brings us back to the tl;dr; when writing new code try to avoid using ReleaseComObject, it may save you a lot of trouble in the long run.


[1] Actually it may call AddRef several times but it calls Release for them all at the same point in time, in my test project the RCW held on to 4 references to the COM object.
[2] According to Eric Lippert on my stackoverflow question this function can be replaced with: Marshal.GetIUnknownForObject(o); // ignore return value and he should know what he’s talking about but I think the original version is more explicit.

Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: