The mapping between interface pointers and runtime callable wrappers (RCWs)


The term COM object is frequently thrown around, but people really access interfaces off of objects. The objects themselves are hidden. Managed is quite a bit different since interfaces are part of the system, but people can also access objects directly. When COM objects are accessed from managed, they are wrapped by a managed object called a runtime callable wrapper (RCW). The type of this runtime callable wrapper is either System.__ComObject or a strongly typed derived type as discussed here.


 


Instead of dealing with objects at an interface level, the CLR attempts to map back to the actual COM object being wrapped. We rely on a fundamental principle of COM that if you are given two interface pointers and QueryInterface() each of them explicitly for IID_IUnknown, they are required to give back an identical value if they in fact refer to the same object. This is basically the canonical IUnknown for a COM object and creates the basis of identity in COM.


 


In managed, we have a per app domain (please see any CLR reference for a definition of app domain) cache mapping canonical IUnknowns back to RCWs. When an IUnknown enters the system (through a marshal call, through activation, as a return parameter from a method call, etc.), we check the cache to see if an RCW already exists for the COM object. If a mapping exists, a reference to the existing RCW is returned. Otherwise a new RCW is created and a cache mapping is added.


 


Many people don’t understand that RCWs are shared and that anything that is done to the interface pointers wrapped by the RCW or RCW wide operations like System.Runtime.InteropServices.Marshal.ReleaseComObject() affect all users of the RCW. If people explicitly want a new RCW to be created even if a mapping already exists in the cache, they should call System.Runtime.InteropServices.Marshal.GetUniqueObjectForIUnknown(). However, they need to understand that RCWs are expensive so there is a good reason why we do this sharing.


 


Once an RCW is created, it can be cast to any interface that can be reached through QueryInterface() on the underlying COM object. Under the covers, we keep a small stash of interface pointers for each interface that has been accessed up to the size of the cache. Once the cache is full, any additional calls on new interfaces will result in a QueryInterface() on each method call. This is pretty important because some operations (like CoSetProxyBlanket) have affects on a given interface pointer and will not work with RCWs since we might generate a new interface pointer on the fly per call.


 


It is also important to remember that interface pointers have COM apartment / COM+ activity affinity and need the help of the Global Interface Table or CoMarshalInterThreadInterfaceInStream() to cross boundaries. RCWs shield users from this by doing all of the gymnastics to make this work behind the scenes for the user. This is both a good and bad thing. RCWs are most efficient when accessed from the apartment from which they were created; however, it is important to remember that the RCW might be getting pulled from the app domain wide cache (and the use case that caused the RCW to be created could be in a different apartment and cause unexpected performance sapping apartment transitions).


 


Unfortunately, even though RCWs are managed objects, most of the functionality for them is buried in the native implementation of the CLR virtual machine so there isn’t a lot of end user transparency into how they operate. If you have any questions about additional RCW or other Interop issues that you would like to see addressed, please don’t hesitate to email me.

Comments (3)

  1. COM objects are represented in managed code by runtime callable wrappers. I have written a reasonable

  2. sherga says:

    Hi,

    I found this a very useful post but have some additional RCW / COM / .NET questions that I’d like to hear your thoughts on as we’re experiencing some problems that can probably be explained with a little more understanding about the gymnastics that RCWs do!  

    The problem I have (and I’m simplifying as much as possible here) is that I have an Excel sheet with 2 addin calls (in different cells).  One addin call runs wholly in the STA and ends up calling-in to some .NET code (F# in this case).  The .NET code then attempts to access a COM object that was created in the STA.  At this point I would expect, and see, that a RCW is created to wrap the COM object and everything works nicely.  The second addin call spawns a thread (in the MTA) and calls down to .NET code to access the same COM object seen in the first addin call.   In this case I would expect the same RCW to then use a proxy to manage the cross-apartment nature of the call to the COM – is that correct?  Is it also possible that between the two addin calls the first RCW is garbage-collected and so when the second addin call is invoked a new RCW is created?  

    The reason for asking these question is that I experience an intermittent problem which is due to the interface being called on the COM object.  It is marked as OLE-automation compliant but in fact it is not – this is clearly a problem to be fixed but it leads (I think) to some questions about RCWs & proxies.  As the interface is not OLE-automation compliant any attempt to marshal a call to the interface fails.  In the scenario above I only intermittently see such a marshalling problem (on the second addin call) and one theory is that the error only occurs when the exact same RCW is used between the two addin calls.  In this situation I imagine that the CLR/COM can detect that a cross-apartment call is being made and invoke proxies.  If GC kicks-in and I end up with a different RCW for the second addin call then it would not have visibility on the apartment affinity of the underlying COM object and so not require marshalling.  Is this a plausible explanation?

    Given this odd problem any more insight on how the RCWs work in this type of cross apartment situation would be really helpful and particularly any more detail on when a RCW might create a proxy.

    Many thanks!

    Adam

  3. Brain2000 says:

    Good article.

    I wish this were that easy in the Outlook model when calling it from managed code.  If you don't create a unique RCW around each iunknown you receive so they can be released immediately when done, you will get all kinds of strange behaviors.  Since Outlook is still single threaded to this day, even across all plugins that run, it seems to still have a lot of dangerous code lurking about that has yet to be rewritten.