I’ve been swimming in a sea of COM Interop lately. Some time ago, I wrote an article that had some important tidbits regarding the nature of Runtime Callable Wrappers, or RCWs.

I’d thought I’d bring to the surface one of the more important questions I answered in that article, specifically: What actions, when executed, incur an increment to an RCW’s reference count?

RCW Reference Counting Rules

When dealing with COM Interop, we find ourselves no longer in the safe confines provided by the .NET garbage collector. Sure, garbage collection will still occur eventually, however great care must be taken if our software is both making use of various COM types and expected to still operate stably.

If our code happens to be making use of (or being made use of by) something like a COM automation server, where perhaps our product is a minor act in a larger show, this becomes even more important.

The following is a listing of the actions that DO cause the reference count on RCWs to be incremented:

  1. Requesting a COM Object from Another COM Object.
    1. Whenever we seek treasures from COM-land, the reference counting gods take note and record it in their books. This applies to objects returned by both methods and properties.
    2. The following is several examples of the reference count being incremented due to the calling of methods and properties (we’ll be using Outlook’s object model for these examples):
      Explorer first = Application.ActiveExplorer();
      Explorer second = Application.ActiveExplorer();
      
      Inspectors inspectors = Application.Inspectors;
      
    3. If I put one of these Explorer instances through Marshal.ReleaseComObject, you should expect to get a return value of 1, indicating one reference yet remains. And, in regards to the Outlook Object Model, I know that this will be the case.
    4. However, this will not necessarily occur with any other type library. Some COM objects, depending on their own internal design, will appear to allocate new memory each time you access a property. When this is the case, the .NET runtime will think that it is dealing with a never-before-seen COM object, and will generate a new RCW for it.
      1. What actually is most likely happening under the hood with this is that the COM object, when returning some additional COM data, is actually returning a proxy to that data. When a type happens to do this, you will actually end up with multiple RCWs (indirectly) pointing to a single location in memory. When the RCW is released, it’ll clean up the proxy, but not until all of those are gone will the actual COM instance living in memory will go bub-bye.
      2. Unfortunately, this revelation has ill tidings for the use of Marshal.FinalReleaseComObject. You cannot be assured you are actually releasing a COM object simply by using this method unless you know that the Interop you’re dealing with behaves in a manner allowing such a thing. This should lend credence to the idea that vigilance must be maintained when dealing with COM Interop.
  2. Handling Events Originating from a COM Object
    1. If you have an event handling method subscribed to an event published by a COM object, then any COM type parameters will have their reference counts incremented when that handler is called.
    2. The following is an example of such an event handler:
      private void HandleBeforeFolderSwitch(object newFolder, ref bool cancel)
      {
        .
        .
        .
      }
      
    3. The above event handler is handles the Explorer.BeforeFolderSwitch event. Although the first parameter is (for some reason) an object type, it is actually a Folder type. Because your event handler (should) only be called from COM-land itself, you should treat these objects as newly created or recently reference-count-incremented RCWs.

The following is a listing of the actions that DO NOT cause the reference count on RCWs to be incremented (obviously not exhaustive):

  1. Casting a COM Instance to a Different Interface Type
    1. If an object implements several COM interfaces, and you cast it from one to another, your RCW will not increment its count.
    2. Indeed, you can be sure that when you do make the cast, that what you are getting back is the very same RCW, yet without any count having been incremented.
    3. Here’s an example of such a cast, this time using Redemption’s object model:
      RDOMail message = session.GetMessageFromID(entryId, storeId);
      RDOAppointmentItem appointment = (RDOAppointmentItem) message;
      
    4. If you look at both of the RDOMail and RDOAppointmentItem objects, you will see that they point to the same place in memory.
    5. If you pass one of the two items to a Marshal.ReleaseComObject call, you will also see that a 0 will be returned, indicating that there was only ever a reference count of one.
  2. Moving the COM Instance Around in your Managed Code
    1. This is an important one to know for the paranoid out there — and probably the one that most find themselves thinking about.
    2. Passing an instance of a COM type to another managed method does not increment its reference count.
    3. Storing a reference to the COM instance in a class member, stuffing it into a collection, etc., does not increment its reference count.

Matt Weber

I'm the founder of Bad Echo LLC, which offers consulting services to clients who need an expert in C#, WPF, Outlook, and other advanced .NET related areas. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

  12 Responses to “Minding Your RCWs and Reference Counts”

  1. hi matt
    i’m getting stuck into the COM interop thing (swimming, as you say) and cannot understand the event situation. Im producing tests so that i understand the RCW reference increment/decrement and dont seem to be able to produce a satisfactory test for a COM event.
    As you state, and everyone else does also, that a COM object passed to an event handler for a COM object will have it’s RCW incremented, but i cant produce that situation. in summary, everytime i release the object i get 0. have i misunderstood this, and if so, can you elaborate or tell me what i’m not understanding.

    i’ve attached my test code below. it’s using Word. if i comment out the ReleaseCOMObject code in the NewDocument event handler then i get an RCW count of 1 in the DocumentBeforeClose event, which would indicate that the Doc objects passed into both events are the same (or rather, share the same RCW).

    but why do they not share the same RCW as my original instance that i get when executing doc = app.Documents.Add();?
    if i comment out the ReleaseCOMObject code in both events i’d expect the RCW count of the doc object to be 2 when it’s released, and not 0.
    any thoughts or pointers much appreciated.
    matt

    public void Test4_HandlingCOMEvent(msoWord.Application app)
    {
    // 4) Reference count is incremented each time an object is passed as a parameter in event
    // raised by COM

    Console.WriteLine(“———————————”);
    Console.WriteLine(“Handling an event raised from COM”);
    Console.WriteLine(“———————————”);

    Console.WriteLine(“Create and new document event handler”);
    ((msoWord.ApplicationEvents4_Event)app).NewDocument += new msoWord.ApplicationEvents4_NewDocumentEventHandler(Word_NewDocument);
    app.DocumentBeforeClose += new msoWord.ApplicationEvents4_DocumentBeforeCloseEventHandler(Word_DocumentBeforeClose);

    Console.WriteLine(“Create a document object, passed in from COM…”);
    msoWord.Document doc = app.Documents.Add();

    Console.WriteLine(“Add a pause in the process to enable some manual intervention in Word (and get some events fired)”);
    System.Threading.Thread.Sleep(30000);

    Console.WriteLine(“Releasing the first instance will set the RCW count to 0…”);
    int rcwCount = 0;
    rcwCount = System.Runtime.InteropServices.Marshal.ReleaseComObject(doc);
    Console.WriteLine(“Document 1 RCW count: {0}”, rcwCount);

    app.DocumentBeforeClose -= new msoWord.ApplicationEvents4_DocumentBeforeCloseEventHandler(Word_DocumentBeforeClose);
    ((msoWord.ApplicationEvents4_Event)app).NewDocument -= new msoWord.ApplicationEvents4_NewDocumentEventHandler(Word_NewDocument);

    }

    void Word_DocumentBeforeClose(msoWord.Document Doc, ref bool Cancel)
    {
    Console.WriteLine(“Word_DocumentBeforeClose: {0}”, Doc.Name);

    Console.WriteLine(“Unless we keep hold of this instance we need to release it and decrease the RCW count…”);
    int rcwCount = System.Runtime.InteropServices.Marshal.ReleaseComObject(Doc);
    Console.WriteLine(“Word_DocumentBeforeClose RCW count: {0}”, rcwCount);
    }

    void Word_NewDocument(msoWord.Document Doc)
    {
    Console.WriteLine(“Word_NewDocument: {0}”, Doc.Name);

    Console.WriteLine(“Unless we keep hold of this instance we need to release it and decrease the RCW count…”);
    int rcwCount = System.Runtime.InteropServices.Marshal.ReleaseComObject(Doc);
    Console.WriteLine(“Word_NewDocument RCW count: {0}”, rcwCount);
    }

Leave a Reply to Matt Cope Cancel reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 
   
© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.