Click here to Skip to main content
15,892,575 members
Articles / Operating Systems / Windows

COM and .NET: Cleaning up

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
7 Sep 2011CPOL3 min read 11.2K   4  
COM and .NET: Cleaning up

Introduction

Managing COM resources in an Office add-in is very important since ignoring it means Excel might not even close with your add-in installed. Ironically C++ is a bit easier this way because Visual Studio can generate wrapper classes for you that take care of releasing the COM objects as long as they are created on the stack or deleted explicitly. You might think that the .NET garbage collector takes care of the objects but it doesn’t entirely, it just frees up the proxy object, not the COM resource.

Freeing up COM Resources in .NET

There are two functions that you can use to release COM objects:

  • Marshal.ReleaseComObject(object)
  • Marshal.FinalReleaseComObject(object)

For both methods, pass the RCW (Runtime Callable Wrapper – proxy object). The first method decrements an internal counter and releases the resource if it reaches zero. It’s possible to have two RCWs pointing to the same COM object, so normally you wouldn’t want to release a COM object if some other part of the code still needed it.

Typically, you’d acquire a COM resource, do something with it, then release it when it’s no longer needed. Contrast that with the C++ way of doing things. In C++, you acquire the COM resource and immediately pass it to a wrapper object in its constructor that you create on the stack. When the wrapper object goes out of scope, its destructor is called and the destructor frees the COM resource. The nice thing about this is that you ensure the resource is released properly when you acquire the resource so it’s almost impossible to forget to clean things up. To learn more about how it can be done in C++, go here (codeproject.com – interesting way using ATL instead of MFC).

To achieve this in C# using wrapper classes is a bit impractical. There is no COM wrapper class generator and even if there were, it would still be a problem since resources are only released if your add-in is running, i.e., if you still have resources and Excel/Word/etc. has already called AddinBeginShutdown, they will never be released because the add-in code will not be called again.

One method is to acquire the resource and place it into a container at the same time. Then loop through the container and free up the resources as shown in exhibit 1.

Exhibit 1: Storing resources in a container and freeing them up.

C#
public double Foo(Excel._Worksheet sheet, int y, int x)
{
    Excel.Range r1, r2, cells;            
    List<object> comObjs = new List<object>();
    comObjs.Add(cells = sheet.Cells);
    comObjs.Add(r1 = cells[y, x] as Excel.Range);
    comObjs.Add(r2 = cells[y + 1, x] as Excel.Range);
    double result = (double)r1.Value * (double)r2.Value;
    foreach (object o in comObjs)
    {
        Marshal.ReleaseComObject(o);
    }
    return result;
}

Although this looks kinda ugly, it accomplishes some improvements. The big one is that the lines where we acquire COM resources are also the lines where we put the RCW in the list of objects that will be released. This way, if the function was very long, we’re less likely to forget to release the object and it makes it a bit easier since there’s less scrolling around.

The other issue this doesn’t address is what if there’s an exception before the release loop. In some cases, we might expect an exception and the caller might have this function call in a try, catch loop. If that were the case, we’d lose references to the RCWs and not be able to release the resources. If we change our code to use a try, finally block will take care of the problem.

Exhibit 2: Adding a try … finally block.

C#
public double Foo(Excel._Worksheet sheet, int y, int x)
{
    List<object> comObjs = new List<object>();
    try
    {
        Excel.Range r1, r2, cells;


        comObjs.Add(cells = sheet.Cells);
        comObjs.Add(r1 = cells[y, x] as Excel.Range);
        comObjs.Add(r2 = cells[y + 1, x] as Excel.Range);
        double result = (double)r1.Value * (double)r2.Value;
        return result;
    }
    finally
    {
        foreach (object o in comObjs)
        {
            Marshal.ReleaseComObject(o);
        }
    }
}

Conclusion

To take this a step further, you could take the foreach loop and put it in a separate function (e.g. a static method somewhere) so you wouldn’t need to repeat it constantly throughout your code. Also wrapping the ReleaseComObject call try catch wouldn’t be a bad idea either (it will throw an exception if o is already released).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Excel Adviser
Canada Canada
I am a freelance software developer with a variety of different interests. My main area of expertise is Microsoft Office add-ins (Excel/Outlook mostly) but I also develop Windows applications, Access Databases and Excel macros . I develop in VBA, C# and C++. My website exceladviser.com has articles on Excel, Access, Microsoft Office development, and general Windows programming (WPF, etc.).

Comments and Discussions

 
-- There are no messages in this forum --