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.

Public Class ComWrapper(Of T)

Private _value As T

Public ReadOnly Property Value() As T
Get
Return Me._value
End Get
End Property

Public Sub New(ByVal value As T)
Me._value = value
End Sub

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.

Private _disposed As Boolean = False ' To detect redundant calls

Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub

Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me._disposed Then
If disposing Then
' TODO: free other state (managed objects) if you have any.
End If

If Me._value IsNot Nothing Then
System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)
Me._value = Nothing
End If
End If
Me._disposed = True
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.

Protected Overrides Sub Finalize()
If Me._value IsNot Nothing Then
System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)
Me._value = Nothing
End If
MyBase.Finalize()
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.

Public Class ComWrapper(Of T)
Implements IDisposable

Private _value As T
Private _disposed As Boolean = False ' To detect redundant calls

Public ReadOnly Property Value() As T
Get
Return Me._value
End Get
End Property

Public Sub New(ByVal value As T)
Me._value = value
End Sub

Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub

Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me._disposed Then
If disposing Then
' TODO: free other state (managed objects) if you have any.
End If

If Me._value IsNot Nothing Then
System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)
Me._value = Nothing
End If
End If
Me._disposed = True
End Sub

Protected Overrides Sub Finalize()
If Me._value IsNot Nothing Then
System.Runtime.InteropServices.Marshal.ReleaseComObject(Me._value)
Me._value = Nothing
End If
MyBase.Finalize()
End Sub

End Class

' #### Now Some Examples
' #### Where "MyComObject" is the com librarys namespace
' #### and "MyComType" is the com object your creating an instance of

Public Sub Test1()
Dim com1 As New ComWrapper(Of MyComObject.MyComType)(New MyComObject.MyComType())
' ... Use com1.Value
com1.Dispose() ' Object disposed here
End Sub

Public Sub Test2()
Using com2 As New ComWrapper(Of MyComObject.MyComType)(New MyComObject.MyComType())
' ... Use com2.Value
End Using ' Object disposed here
End Sub

Public Sub Test3()
Dim com3 As New ComWrapper(Of MyComObject.MyComType)(New MyComObject.MyComType())

' 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
End Sub