When does RCW reference count is incremented?

I recently encountered a fun bug in Outlook addin I'm working on and it was interesting enough that I decided to create a few posts around what caused it, it will probably be 3 blogs starting with RCW reference count stuff, continuing with anonymous delegates and finishing the the bug itself.
Disclaimer: this post is based on a similar stackoverflow question I answered a while ago.
 

A short introduction to Runtime Callable Wrapper RCW [Ref 4]

The RCW is used to proxy a COM object in the managed world, Internally a RCW contains a reference count on the COM object that it wraps. This reference count is not the actual COM reference count but only used by .NET to know when the managed code is finished with it. As I understand when the RCW reference count reaches zero it will release the RCW and decrement the COM reference count and the COM will handle it from there, if COM reference count reaches zero the it will release the actual component. [Ref 5 – found it when was finished with this post, really good one]
 

When does RCW reference count is decremented?

Yes, I know it's funny to start from decrement but it easier and less interesting.
1. When you call Marshal.ReleaseComObject(obj) on RCW reference.
2. When the finalizer thread is finalizing the RCW after it has been collected by GC after no more references were found in code (the ref count is actually reset to zero in that case).

Either way when the reference count reaches zero the CLR will release the RCW instance and it is no longer valid to access the COM component using it. Note that the actual COM component might not be released as its COM reference count didn't reach zero, but the next time you will access the COM component you will get a new RCW instance to the same COM interface.
 

When does RCW reference count is incremented?

Every time the COM object is passed from COM environment to .NET environment.
Breaking it down to specific points:
1. For each COM object there is one RCW object [Test 1] [Ref 4]
2. Reference count is incremented each time the object is requested from within COM object (calling property or method on COM object that return COM object, the returned COM object reference count will be incremented by one) [Test 1]
3. Reference count is not incremented by casting to other COM interfaces of the object or moving the RCW reference around [Test 2]
4. Reference count is incremented each time an object is passed as a parameter in event raised by COM [Ref 1]
5. Reference count is incremented when you get an object from COM IUnknown interface using Marshal class [Test 3] (this actually what happens in the CLR)

 

Test 1 – reference count

private void  Test1( _Application outlookApp )      
{      
    // get the explorer RCW instance and release it so we get its current  count      
    var explorer1 =  outlookApp.ActiveExplorer();      
    var count1 =  Marshal.ReleaseComObject(explorer1);      
    MessageBox.Show("Count 1:" +  count1);      

    // get the explorer com object 3 times so its ref count incremented 3  times          
    var explorer2 = outlookApp.ActiveExplorer();      
    var explorer3 =  outlookApp.ActiveExplorer();      
    var explorer4 =  outlookApp.ActiveExplorer();      

    // check that there is really only one RCW instance      
    var equals = explorer2 == explorer3 &&  ReferenceEquals(explorer2, explorer4);      

    // release the explorer cases the RCW ref count to decremented by 1 and  it returns the new ref count      
    var count2 = Marshal.ReleaseComObject(explorer4);      
    MessageBox.Show("Count 2:" +  count2 + ", Equals: " + equals);      
}      

Output:
Count 1: 4
Count 2: 6, Equals: True
 

Test 2 – reference count with casts

private static  void Test2(_Application outlookApp)      
{      
    // get the explorer RCW instance and release it so we get its current  count      
    var explorer1 =  outlookApp.ActiveExplorer();      
    var count1 =  Marshal.ReleaseComObject(explorer1);      
    MessageBox.Show("Count 1:" +  count1);      

    var explorer2 =  outlookApp.ActiveExplorer();      

    // check different castings to different interfaces don't raise the ref  count      
    var explorer3 = explorer2 as _Explorer;      
    var explorer4 =  (ExplorerEvents_10_Event)explorer2;      
    var explorerObject = (object)explorer2;      
    var explorer5 = (Explorer)explorerObject;      

    // still there is really only one RCW instance      
    var equals = explorer2 == explorer3  && ReferenceEquals(explorer2, explorer5);      

    // release the explorer one so we expect the ref count to remain the  same      
    var count2 =  Marshal.ReleaseComObject(explorer4);      
    MessageBox.Show("Count 2:" +  count2 + ", Equals: " + equals);      
}      

Output:
Count 1: 4
Count 2: 4, Equals: True
 

Test 3 – reference count with IUnknown

private static  void Test3(_Application outlookApp)      
{      
    // get the explorer RCW instance and release it so we get its current  count      
    var explorer1 =  outlookApp.ActiveExplorer();      
    var count1 =  Marshal.ReleaseComObject(explorer1);      
    MessageBox.Show("Count 1:" +  count1);      

    var explorer2 =  outlookApp.ActiveExplorer();      

    // getting       
    var unknown = Marshal.GetIUnknownForObject(explorer2);      
    var explorer3 =  Marshal.GetObjectForIUnknown(unknown);      
    var explorer4 =  Marshal.GetObjectForIUnknown(unknown);      
    var explorer5 =  Marshal.GetObjectForIUnknown(unknown);      
    Marshal.Release(unknown);      

    // still there is really only one RCW instance      
    var equals = explorer2 == explorer3  && ReferenceEquals(explorer2, explorer5);      

    // release the explorer one so we expect the ref count to remain the  same      
    var count2 =  Marshal.ReleaseComObject(explorer4);      
    MessageBox.Show("Count 2:" +  count2 + ", Equals: " + equals);      
}      

Output:
Count 1: 4
Count 2: 5, Equals: True
 

Sources I relay on in addition to my experience and testing:

  1. Johannes Passing's – RCW Reference Counting Rules != COM Reference Counting Rules
  2. Eran Sandler – Runtime Callable Wrapper Internals and common pitfalls
  3. Eran Sandler – Marshal.ReleaseComObject and CPU Spinning
  4. MSDN – Runtime Callable Wrapper
  5. RCW Internal Reference Count
Advertisement

4 comments on “When does RCW reference count is incremented?

  1. […] continue my previous post on a bug I encountered. This time anonymous […]

  2. […] we learned about RCW ref count and anonymous methods I can finally explain the GC deadlock bug in our Outlook […]

  3. […] continue my previous post on a bug I encountered. This time anonymous methods. The best way to learn what happens under the […]

  4. […] we learned about RCW ref count and anonymous methods I can finally explain the GC deadlock bug in our Outlook add-in. […]

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 )

Facebook photo

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

Connecting to %s