3

What is best (or commonly accepted) practice for where to declare COM object variables (scope) and how to handle cleaning them up when using structured error handling?


I just spend a whole load of time learning about Garbage Collection and Reference Counting and dealing with releasing COM objects in .NET.

Working with COM is annoying...


The Dilemma

I realize it's important to release referenced COM objects after finishing with them. I also realize it's also important to anticipate and handle errors.

These two things together seem to be at odds with what keeps getting beaten into my head as best practice: declaring your variables where they are used.

Now, in order to ensure the references are released when done with them, it seems to make the most sense to do this cleanup within the Finally portion of a Try/Catch block.

However, doing so means that I cannot declare these variables within the Try or Catch portions of the block because they will not be accessible to the Finally portion.


SO....

Where does that leave me with regards to following best practices?

Should I continue to declare variables at the beginning of each routine in order to clean them up using Finally?

Is there some other method of accessing COM references at the end of a procedure?

Am I just picking pepper out of fly shit with regards to best practices?


Example Code

In Example 1, I've declared all my variables before entering the Try/Catch block. This way I can ensure they get cleaned up, regardless of any errors.

In Example 2, I've declared my variables where they are used, but this causes a problem with potential non-release of COM objects.

Imports System.Runtime.InteropServices

Module COMexamples   

    Sub Example1()
        Dim Com1 As SomeCOM
        Dim Com2 As AnotherCOM
        Try
            Com1 = New SomeCOM
            Com1.DoSomeStuff()
        Catch ex As Exception
            Com2 = New AnotherCOM
            Com2.CleanupTheMess()
        Finally
            If Not Com1 Is Nothing Then
                Com1.Close()
                Marshal.FinalReleaseComObject(Com1)
            End If
            If Not Com2 Is Nothing Then
                Com2.Quit()
                Marshal.FinalReleaseComObject(Com2)
            End If
        End Try
    End Sub

    Sub Example2()
        Try
            Dim Com1 As New SomeCOM
            Com1.DoSomeStuff()
            'If an exception is thrown, the object won't get released!
            Com1.Close()
            Marshal.FinalReleaseComObject(Com1)
        Catch ex As Exception
            Dim Com2 As New AnotherCOM
            Com2.CleanupTheMess()
            'Another exception here means we miss releasing this COM reference too!
            Com2.Quit()
            Marshal.FinalReleaseComObject(Com2)
        Finally
            'This is pretty much useless now as Com1 and Com2 are out of scope here
        End Try
    End Sub
End Module
1

You should declare and create the object before the try-block. This is not specifically for com, but a general principle when using a resource that must explicitly be deallocated in a finally block.

In other words - first create the resource, and if that succeeds you try to use the resource. On the other hand, if resource allocation fails, the try/finally block is never executed.

This also relieves you from checking if Com1 or Com2 is assigned in the finally block - you know for sure that if you enter the try block that the object has been initialized.

Sub Example()
    Dim Com1 As New SomeCOM
    Try
        Com1.DoSomeStuff()
    Catch
        Cleanup()
    Finally
        Com1.Close()
        Marshal.FinalReleaseComObject(Com1)
    End Try
End Sub

Sub Cleanup()
    Dim Com2 As New AnotherCOM
    Try
        Com2.CleanUpTheMess()
    Finally
        Com2.Quit()
        Marshal.FinalReleaseComObject(Com2)
    End Try
End Sub
4

Your example 1 is the right way to go because it is logically correct, which is far more important than a minor issue like limiting scope. Your scope is limited to this procedure, so that is good enough. If it really bothered you, you could break it up into three procedures, but I only would bother with this if it seemed like the procedure was getting too long anyway.

Imports System.Runtime.InteropServices

Module COMexamples   

Sub Example1()
    Try
        Example1A
    Catch
        Example1B
    End Try
 End Sub

Sub Example1A()
    Dim Com1 As SomeCOM
    Try
        Com1 = New SomeCOM
        Com1.DoSomeStuff()
    Finally
        If Not Com1 Is Nothing Then
            Com1.Close()
            Marshal.FinalReleaseComObject(Com1)
        End If
    End Try
End Sub

Sub Example1B()
    Dim Com2 As AnotherCOM
    Try
        Com2 = New AnotherCOM
        Com2.CleanupTheMess()
    Finally
        If Not Com2 Is Nothing Then
            Com2.Quit()
            Marshal.FinalReleaseComObject(Com2)
        End If
    End Try
End Sub
End Module

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.