views:

263

answers:

3

Question:

I want to ask a question in response to Mike Rosenblum's answer to this question. The question was about cleaning up Excel interop objects. Several solutions where suggested (e.g. wrappers, not using more than one dot, killing the excel process), but I liked Mike Rosenblum's solution to this problem the most (lengthy article about the topic).

What it basically says is that you don't worry too much about all the references floating around. You just keep some main ones (like ApplicationClass, Workbook and Worksheet). You first invoke garbage collection to clean up all the objects floating around and then explicitly clean up the main references you still have by calling Marshal.FinalReleaseComObject (in reverse order of importance).

Now I have two questions about this.
First: How do I determine to which objects I need to keep a reference? In Mike Rosenblum's example he only keeps Ranges, Worksheets, Workbooks and ApplicationClasses.
Second: If there are more objects, how do I determine the order of cleaning them up (i.e. the "order of importance")?

Thanks in advance.


Update 1:

It has been suggested by MattC that for the order, the only thing that is important is that the app is released last. Although in my reference the following sentence:"You should also release your named references in reverse order of importance: range objects first, then worksheets, workbooks, and then finally your Excel Application object." implies that there is more ordering.

nobugz Suggests that setting everything to null and then doing garbage collection will suffice, but that seems to contradict the following quote from Mike Rosenblum's article:"You would think, then, that you can set all your variables = Nothing and then call GC.Collect() at the end, and this does work sometimes. However, Microsoft Office applications are sensitive to the order in which the objects are released and, unfortunately, setting your variables = Nothing and then calling GC.Collect() does not guarantee the release order of the objects."

Update 2:

Some extra info: In my own application, I do a lot of things with a chart. I am setting a lot of properties etc. As I understand, there are many places where I create new COM objects. I tried to make sure I never use double dots, and I tried to call Marshal.FinalReleaseComObject on all objects that I am finished with. I didn't use the wrapper approach because it would introduce a lot of nesting.
EXCEL.exe did not close after my app finished its work. But... it did close when I told my app to do the same work again. Of course a new EXCEL.exe opened which did not close. Now I have removed alllll the Marshal.FinalReleaseComObject calls and the app works exactly the same. The EXCEL.exe stays, until I tell my app to redo the work, but then a new EXCEL.exe starts and stays.

EDIT: Also when I tell my app to do other non-COM related work, after a while the EXCEL.exe disappears, but now no new EXCEL.exe appears.

Not sure what conclusions I can draw from this...

A: 

I think as long as application is last you can release them in any order (as long as they aren't null).

Then do a GC.Collect to finally kill the excel.exe process.

MattC
Are you sure about this? In the answer I linked above, Mike Rosenblum states: "You should also release your named references in reverse order of importance: range objects first, then worksheets, workbooks, and then finally your Excel Application object."
Matthijs Wessels
Strickly speaking no, I'm wrong. You could potentially still access an object while disposing of a child object i.e., access the worksheet after getting rid of a range.I guess I assumed you were killing everything and so you wouldn't be accessing any of the references.
MattC
Ah, so you're suggesting he says that you have to dispose in reversed order of importance because you might still want to use a parent object after disposing a child. I don't think that was meant. I think he means that you have to dispose of them in that order because otherwise something can go wrong with Excel and it won't quit.
Matthijs Wessels
good point. its not clear from the documentation why this is important (reverse order) but Mikes practical advice holds true, you can run into scenarios where Excel won't quit if you don't follow this advice.
Anonymous Type
+1  A: 

I've had a similar problem this is what my catch and finally look like for clean up. I hope it helps.

        .......

        oWB._SaveAs(strCurrentDir +
           strFile, XlFileFormat.xlWorkbookNormal, null, null, false, false,       XlSaveAsAccessMode.xlShared, false, false, null, null);
            sumsheet.Activate();
            oWB.Close(null, null, null);
            oXL.Workbooks.Close();
            oXL.Quit();
        }
        catch (Exception theException)
        {
            theException.ToString();
        }
        #region COM Object Cleanup
        finally
        {
            // Cleanup
            GC.Collect();
            GC.WaitForPendingFinalizers();

            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oRng);
            //System.Runtime.InteropServices.Marshal.FinalReleaseComObject(sumSheet);
            //System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oSheet);
            //oWB.Close(null, null, null);
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oWB);
            oXL.Quit();
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(oXL);
        }
        #endregion

EDIT

If you noticed I've commented out the sumSheet + oSheet(Which are my worksheets) because it wasn't needed. This code has solid for me with no problems. I've found by rearranging the order I've gotten errors.

xxmrlnxx
In the other order that generated the error, did you leave oXL as last? Because that would be a good enough counter example for MattC's theory.
Matthijs Wessels
do the collect, pump to finalizer queue twice as per Mike's suggestion... gc.collect Gc.waitforpendingfinalizers gc.collect Gc.waitforpendingfinalizers
Anonymous Type
On further reading of Mike's articles it seems a double pump of the finalization queue is only required for VSTO solutions. However so long as you don't intend to call on the GC.Collect waitforpendingfinalizers regularly the perf hit shouldn't matter.
Anonymous Type
+2  A: 

You should have no trouble finding possible live references in your code, they will be fields in your class(es). Or local variables in the cleanup method, that's unlikely. The list provided in the link are just objects that you most likely will store in a field. There could be others, they'll keep Excel just as alive as the Application object.

I don't think I'd recommend the jackhammer approach as advocated in the link, it just hides a potential life reference to an RCW that wraps a dead COM interface. A best you'll have a possibly permanent leak to the RCW object, at worst it crashes your program with an exception when it accidentally references the object. Bugz, to be sure, just not ones you'll easily discover. All you have to do is set your references to null, order doesn't matter, then collect.

Hans Passant
Mike Rosenblum states in this link:http://www.xtremevbtalk.com/showthread.php?t=160433 that:"You would think, then, that you can set all your variables = Nothing and then call GC.Collect() at the end, and this does work sometimes. However, Microsoft Office applications are sensitive to the order in which the objects are released and, unfortunately, setting your variables = Nothing and then calling GC.Collect() does not guarantee the release order of the objects."From this I derive that I do have to do some manual releasing. What is your opinion on this quote?
Matthijs Wessels
This is not how COM reference counting works.
Hans Passant
What do you mean? Do you disagree with this statement:"Microsoft Office applications are sensitive to the order in which the objects are released"?
Matthijs Wessels
What he means is that COM reference counting is not related to the statement "Office apps are sensitive to the order of termination when used from com interop". The main reason the order might be sensitive is that pretty much every object has an .application field reference and many lower level objects have a parent field that references the higher up object. In general just make sure you dereference everything and use Marshal.FinalReleaseCOMObject(var)
Anonymous Type