Monday, 13 July 2009

Clean Up Your Unmanaged Objects

After working for a few days on a project which had partially been started, I hit across a gem of a piece of code to help myself clean up those nasty COM components. Yes, the dreaded COM! Due to the fact that this was the actual core of the system (having no other choice) I set about at least trying to make it tidy. Quickly I found references to objects, destruction of instances and the good old .Net Garbage Collector couldn't handle what had been written. So time for a rethink.

I was quite lucky in the fact that the COM library in question was written pretty well and most of it was named sensibly (full marks to the bloke who did the naming).

So to business. The requirements, simply to allow disposal of the object if I manually call it and/or if the so called variable is no longer in scope or referenced anywhere I want the .Net Garbage Collector to clean it up.

Step 1. The class

Due to not being able to inherit from this COM library I set about wrapping up the instances of each type in a class using Generics to pass the objects type through at runtime.
  1. Public Class ComWrapper(Of T)  
  2.   
  3. Private _value As T  
  4.   
  5. Public ReadOnly Property Value() As T  
  6.   Get  
  7.       Return Me._value  
  8.   End Get  
  9. End Property  
  10.   
  11. Public Sub New(ByVal value As T)  
  12.   Me._value = value  
  13. End Sub  
  14.   
  15. End Class  

Step 2. IDisposable

Having the base class in shape I now implemented the .Net IDisposable interface to follow the .Net IDisposable pattern, meaning any using statements would clean the object but also I could call "Dispose()" manually and simply follow the .Net way. The proper way to dispose of COM objects is to use the "Marshal" class found in the namespace "System.Runtime.InteropServices". Calling "ReleaseComObject()" will force the object to be cleaned safely.
  1. Private _disposed As Boolean = False ' To detect redundant calls  
  2.   
  3. Public Sub Dispose() Implements IDisposable.Dispose  
  4.   Dispose(True)  
  5.   GC.SuppressFinalize(Me)  
  6. End Sub  
  7.   
  8. Protected Overridable Sub Dispose(ByVal disposing As Boolean)  
  9.   If Not Me._disposed Then  
  10.       If disposing Then  
  11.           ' TODO: free other state (managed objects) if you have any.  
  12.       End If  
  13.   
  14.       If Me._value IsNot Nothing Then  
  15.           System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)  
  16.           Me._value = Nothing  
  17.       End If  
  18.   End If  
  19.   Me._disposed = True  
  20. End Sub  

Step 3. Finalize it!

Finally :P we override the "Finalize" method making sure to implement the base finalize but also clear up the COM object. This means if the variable goes out of scope and gets flagged for the Garbage Collector to dispose of it will clean it up nicely and not leave unmanaged instances everywhere.
  1. Protected Overrides Sub Finalize()  
  2.   If Me._value IsNot Nothing Then  
  3.       System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)  
  4.       Me._value = Nothing  
  5.   End If  
  6.   MyBase.Finalize()  
  7. End Sub  

So that's it, you should have nicely managed objects now, and for those who need to be nursed hand in hand a sample below shows the full class and some examples of usage and when they will be cleaned up.
  1. Public Class ComWrapper(Of T)  
  2. Implements IDisposable  
  3.   
  4. Private _value As T  
  5. Private _disposed As Boolean = False ' To detect redundant calls  
  6.   
  7. Public ReadOnly Property Value() As T  
  8.     Get  
  9.         Return Me._value  
  10.     End Get  
  11. End Property  
  12.   
  13. Public Sub New(ByVal value As T)  
  14.     Me._value = value  
  15. End Sub  
  16.   
  17. Public Sub Dispose() Implements IDisposable.Dispose  
  18.     Dispose(True)  
  19.     GC.SuppressFinalize(Me)  
  20. End Sub  
  21.   
  22. Protected Overridable Sub Dispose(ByVal disposing As Boolean)  
  23.     If Not Me._disposed Then  
  24.         If disposing Then  
  25.             ' TODO: free other state (managed objects) if you have any.  
  26.         End If  
  27.   
  28.         If Me._value IsNot Nothing Then  
  29.             System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)  
  30.             Me._value = Nothing  
  31.         End If  
  32.     End If  
  33.     Me._disposed = True  
  34. End Sub  
  35.   
  36. Protected Overrides Sub Finalize()  
  37.     If Me._value IsNot Nothing Then  
  38.         System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)  
  39.         Me._value = Nothing  
  40.     End If  
  41.     MyBase.Finalize()  
  42. End Sub  
  43.   
  44. End Class  
  45.   
  46. ' #### Now Some Examples  
  47. ' #### Where "MyComObject" is the com librarys namespace  
  48. ' #### and "MyComType" is the com object your creating an instance of  
  49.   
  50. Public Sub Test1()  
  51. Dim com1 As New ComWrapper(Of MyComObject.MyComType)(New MyComObject.MyComType())  
  52. ' ... Use com1.Value  
  53. com1.Dispose() ' Object disposed here  
  54. End Sub  
  55.   
  56. Public Sub Test2()  
  57. Using com2 As New ComWrapper(Of MyComObject.MyComType)(New MyComObject.MyComType())  
  58.     ' ... Use com2.Value  
  59. End Using ' Object disposed here  
  60. End Sub  
  61.   
  62. Public Sub Test3()  
  63. Dim com3 As New ComWrapper(Of MyComObject.MyComType)(New MyComObject.MyComType())  
  64.   
  65. ' Object is not disposed of but once the variable is out of scope or in other words the methods ends the garbage collector flags "com3" for finalizing. This will not happen instantly so dont rely on it, but it should be fine for those of us who get a bit lazy :P  
  66. End Sub