COM in .NET world. Runtime wrappers

COM in .NET is very tricky to do properly, and it is source of a lot of confusions. It is quite extensive area and I will try to explain stuff I learnt from it. I would like to mention that all that I got from practice and from exploring source code of CLR and call stacks in WinDbg. But some conclusions could be wrong so use it at own risk.

Firstly, we have to start with basics. Let’s talk about case when .NET code consumes some external COM object.

.NET cannot consume COM interfaces directly, so when COM interface crosses .NET boundary runtime will create special structure called Runtime Callable Wrapper (RCW for short) and .NET native objects (it called ComObject) that will represent interface in .NET world. RCW will hold reference to COM interface. Native object will use RCW to call method from that COM interface. Usually RCW calls AddRef only once for that COM object. Please note that I’m talking about RCW structure and ComObject that are defined in src\vm\runtimecallablewrapper.h in source code of CLR. .NET documentation called ComObject RCW and they didn’t mention RCW structure at all.

.NET runtime has interfaces cache so usually the same COM instance will map to the same native object. RCW has counter of how many native objects created for that RCW.

As result of that design normally you cannot control when COM interface will be released. By default, it requires several garbage collects to release COM object and finally finalizer thread will call Release for that COM interface, so it could take quite some time if your application does not trigger garbage collect often. And this could be problem for huge COM objects like Word application.

You can call Marshal.ReleaseComObject that decrements native object counter and if it reaches zero it will release COM interface by executing Release method on that interface and assign it to null. Any further attempt to use that “released” object will throw exception that object is disconnected. At first it looks like good idea to control life time of object, but it could backfire very quickly. For example, let’s imagine that some .NET method called, and some COM interface passed as parameter:

        private ICOMInterface intf;

        public void SomeMethod1(ICOMInterface intf)
        {
            m_intf = intf;
        }

And you have another method that uses the same COM instance:

        public void SomeMethod2(ICOMInterface intf)
        {
            try
            {
                Use(intf);
            }
            finally
            {
                Marshal.ReleaseComObject(intf);
            }
        }

It looks correct and it will release COM interface. But problem here that it also makes  m_intf  unusable. It happens because when both methods were called it uses it mapped same COM instance to same native object. So, RCW will have native object count to 1. Call to Marshal.ReleaseComObject will decrement native object count and that counter became zero. Then RCW will release COM object. Any further attempt to use it will throw exception

        public void UseIntf()
        {
            m_intf.SomeMethod(); // <<throws exception here
        }

You can ask runtime to create unique native object by doing this:

        public void SomeMethod3(IntPtr intfPtr)
        {
            try
            {
                ICOMInterface intf = (ICOMInterface)Marshal.GetUniqueObjectForIUnknown(intfPtr);
                Use(intf);
            }
            finally
            {
                Marshal.ReleaseComObject(intf);
            }
        }

In this case parameter declared as IntPtr and in this case there is no automatic RCW creation. New native object will be created by calling Marshal.GetUniqueObjectForIUnknown. This function will not use RCW cache and will always create new native object. And that object could be released without worry that it will break someone’s code. But it will create new object each time increasing pressure on GC and GC have to do extra work related to managing RCW.

Another approach would be to create own wrapper around native object that implement IDisposable and it will call Marshal.ReleaseComObject when last reference is released. Also, if you have few projects that uses same COM objects then you will have to create global object registry to have single list of ever create objects. And this approach is quite laborious because you have to release all objects. For example, if you wrote code like this:

        public void UseIntf2()
        {
            m_intf.SomeAnotherIntf().SomeMethod();
        }

Then you will create another native object by executing m_intf.SomeAnotherIntf() but you didn’t release it and as result it still referenced from .NET runtime. Same happened if you pass some record to .NET which have references to interfaces and even you don’t use them .NET runtime will still create RCW for them and all these objects will be referenced from .NET runtime.

For nitpickers there are few other ways when COM object can be release from .NET. For example, if you have a lot of calls between .NET and COM usually COM objects can be released during other RCW creation to release GC pressure. Another example when domain is unloading.