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:
- Requesting a COM Object from Another COM Object.
- 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.
- 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):
1234
Explorer first = Application.ActiveExplorer();
Explorer second = Application.ActiveExplorer();
Inspectors inspectors = Application.Inspectors;
- 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.
- 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.
- 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.
- 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.
- Handling Events Originating from a COM Object
- 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.
- The following is an example of such an event handler:
123456
private
void
HandleBeforeFolderSwitch(
object
newFolder,
ref
bool
cancel)
{
.
.
.
}
- 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):
- Casting a COM Instance to a Different Interface Type
- If an object implements several COM interfaces, and you cast it from one to another, your RCW will not increment its count.
- 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.
- Here’s an example of such a cast, this time using Redemption’s object model:
12
RDOMail message = session.GetMessageFromID(entryId, storeId);
RDOAppointmentItem appointment = (RDOAppointmentItem) message;
- If you look at both of the RDOMail and RDOAppointmentItem objects, you will see that they point to the same place in memory.
- 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.
- Moving the COM Instance Around in your Managed Code
- This is an important one to know for the paranoid out there — and probably the one that most find themselves thinking about.
- Passing an instance of a COM type to another managed method does not increment its reference count.
- Storing a reference to the COM instance in a class member, stuffing it into a collection, etc., does not increment its reference count.