COM Objects and IDisposable?

I haven't dealt with a ton of COM interop up to this point in my .NET life.  I was surprised to find out that there is not a good story for deterministic deconstruction of COM objects...or maybe there is and I didn't see it.

What I've come up with is a bit of a hack to wrap a com object in a IDisposable wrapper so that I can use the using{...} syntax to determine Release() calls (not really destruction, but good enough for most cases).  The code looks like this:

/// 
/// Wraps an instance of a COM object in order to
/// provide deterministic deconstruction (or Release really).
/// 
public class DisposableCom : IDisposable
{
  /// 
  /// Public constructor to take a single COM object
  /// to call Release on upon Disposal.
  /// 
  /// 
  public DisposableCom(object comObject)
  {
    // Hack to allow us to Set a COM object that is initially null
    // but want only allow COM objects
    IntPtr typeInfo = Marshal.GetITypeInfoForType(comObject.GetType());
    if (typeInfo.Equals(null))
    {
      Marshal.Release(typeInfo);
      throw new ApplicationException("DisposableCom can only accept COM objects");
    }
    else
    {
      Marshal.Release(typeInfo);
    }

    _comObject = comObject;
  }

  ~DisposableCom()
  {
    ((IDisposable)(this)).Dispose();
  }

  private object _comObject = null;

  #region IDisposable Members
  void IDisposable.Dispose()
  {
    GC.SuppressFinalize(this);
    if (_comObject != null) Marshal.ReleaseComObject(_comObject);
  }
  #endregion
}

Can anyone find any holes in this idea?

Comments:

You really don't want to be touching that RCW in your finalizer - it may well already have been finalized

The only "hole" here is the cross-over between the reference and non-reference counted worlds.

Many .NET "references" to the object may exist, but this translates to just one COM reference. As long as you are sure to only put one usage inside a using block you are OK, but otherwise there'll be trouble.

This isn't really different than any other object that implements IDisposable though. Other than that I wonder what happens if releasecomobject is called multiple times on a single COM wrapper?

Rocky

The COM object's RCW may be ref counted above 1. IIRC, this can happen if the COM object is passed back and forth between managed and unmanaged code. ReleaseComObject will return the new ref count. So I typically do something like this:

if (_comObject != null) while (Marshal.ReleaseComObject(_comObject)>0);

For more info on this, see Adam Nathan's book on COM/NET interop pg. 300. I also agree with Richard's comment on not calling the RCW during finalizaion. I asked Adam about this once and he said IIRC that it works today but that behavior could change in the future. After all, the RCW itself is managed.

I have a suggestion to add. I usually put a debug.assert or atleast a debug.write in finalize alerting the programmer that he forgot to call a dispose where he shud've !! :)


 



 
Save Cancel