Deterministic disposal of your COM objects in .Net

In our migration process, we encounter a few(~6) VB6 classes with heavy logic in their destructors. In the old world of VB6, the destructor was called after setting the object to Nothing (deterministic destructor) which is quite the opposite than the new world of .Net where the Garbage Collector is in charge of disposing the objects and you can’t never know when the destructor will be called. This means that wrapping an old VB6 dll with an Interop and using one of it’s classes in our .Net classes, will now make its destructors non-deterministic(The proxy is also managed in the CLR, so it behaves by the same rules every .Net class follow) which is very bad for our application – performance-wise, memory leaks etc. Our solution was to force the disposal of the object by calling Marshal.ReleaseComObject in a “Interop Wrapper”, and here is the concept:


Let’s say that we have a VB6 dll named Class1.dll (COM dll) which contains MyService class with some logic in its destructor.
We create an Interop for it which will be called Interop.Class1.dll (this is .Net assembly).
And here is our “Interop Wrapper” (named ComObjectScope) and our consumer(MyClass).


/// <summary>
/// Wrap a COM object in order to control its life cycle (deterministic disposal).
///
/// Usage:
/// MyComObject obj = new MyComObject();
/// using (new ComObjectScope(obj))
/// {
/// // Use obj here.
/// } // here obj will be disposed. Don’t use it outside of the using block.
/// </summary>
/// <example>
/// MyComObject obj = new MyComObject();
/// using (new ComObjectScope(obj))
/// {
/// // Use obj here.
/// } // here obj will be disposed. Don’t use it outside of the using block.
/// </example>
public class ComObjectScope : IDisposable
{
   private bool _isDisposed = false;
   private object _comObject;

   public ComObjectScope(object comObject)
   {
      if (!comObject.GetType().IsCOMObject)
         throw new ArgumentException(“The provided object must be of COM object type.”);

      _comObject = comObject;
   }

   protected virtual void Dispose(bool disposing)
   {
      if (_isDisposed)
         return;

      if (!_isDisposed)
      {
         if (disposing)
         {
            Marshal.ReleaseComObject(_comObject); // This baby release the COM object for good.
            _comObject = null;
         }
      }

      this._isDisposed = true;
   }

   public void Dispose()
   {
      Dispose(true);
      GC.SuppressFinalize(this);
   }

   ~ComObjectScope()
   {
      Dispose(false);
   }
}


public class MyClass
{
    public void DoSomethingWithComObject()
    {
        Interop.Class1.MyService service = new Interop.Class1.MyService();
        using (new ComObjectScope(service))
        {
            // …
            // use service here as needed.
            //….
        
        } // here service will die and its destructor will be called

        // don’t use service here! you’ll get NullReferenceException
    }    
}

 

Oren Ellenbogen