A splat of all my blathering.

Wednesday, April 14, 2004

RCWs and reference counting 

Over the last couple of days I have been working with a couple of my team members to identify a problem with our DCOM server interacting with a managed class implementing a CCW. In the method call to the CCW the DCOM server passes a COM interface which the managed class uses via an RCW. We were seeing the DCOM server crashing at pretty regular intervals and they always coincided with a .NET garbage collection. It turns out that the problem was in the DCOM server code but during our testing I found some important things that I didn't know before about RCWs.

When an interface is passed to a managed object an RCW is used. As I understand it, in this simple case (no cross-AppDomain marshalling), the RCW will hold a single reference to this interface which effectively bumps the reference count by 1. When the RCW is no longer referenced it will be garbage collected and during this process Release will be called which will decrement the count by 1. Note, that this *could* result in the COM object being deleted. While inside the method call the interface is still being referenced so will not be garbage collected right away. This is true even if you call GC.Collect();GC.WaitForPendingFinalizers();. However, the very next time the GC is run this object could be collected.

Please note that this behavior is not a bug. It is a known feature of the .NET runtime and well documented. I'm just pointing out that it may not be immediately obvious.

Consider this example (error checking removed for brevity):

// in the DCOM server code
void CServer::ExecutePlugin()
  CComPtr<IPlugin> pPlugin = 0;
  HRESULT hr = pPlugin.CoCreateInstance(L"MyPlugin.MyPlugin", 0, CLSCTX_SERVER);
  CComPtr<ISession> pSession = 0;
  hr = pSession.CoCreateInstance(L"Server.Session.1", 0, CLSCTX_SERVER);
  hr = pPlugin->Execute(pSession);

// in the plug-in code
class Plugin : IPlugin
  public void Execute(MySession session)

After the call to pPlugin->Execute(pSession) has returned the reference count on the pSession object will be (at least) 2. One for the initialize CCI and one for the RCW created for the managed plug-in. After the local CComPtr is deleted the reference count will be 1. This means that the object will not be deleted within the scope (or thread) of the CServer::Execute method. In fact, with some trivial tracing added you can see that the destructor of the pSession object is called from a different thread altogether. This thread must be the GC thread that is cleaning up the RCW for the ISession object.

The reason why this is important to keep in mind is that if the DCOM server was not correctly following the rules of COM interface reference counting of the ISession object, then the resulting bug would not manifest itself so easily if the plug-in were written in unmanaged code as it would in the managed case.

Most, implementors of the IPlugin interface would realize that the ISession interface handed to their Execute method does not require an AddRef and Release since it is not being copied and so the reference count would remain at 1 for the entire lifetime of the CServer::Execute call and the object would be destroyed when the CComPtr left scope. In this case the object would be destroyed on the same thread as it was created and not some other .NET GC thread.

In the managed case the RCW will most likely be the last reference to the pSession object and therefore release it only when GC'd. If the COM object was deleted prematurely because of not following the rules of COM, this might manifest itself as some kind of null derefence exception or crash.

Posted at 4/14/2004 02:20:00 PM |
Are you the same Darin who took a flight from Portland to Europe? We spoke of your sister and it was finally a most unusual, philosophical conversation.
I hope everything has worked out for you.
Post a Comment

This page is powered by Blogger. Isn't yours?