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