You have to be super buttoned up when using Excel interop. I won't repost everything that's already available on SO, but here is the intro comment to my ExcelInterfacer class:
/*
General rules on working with interop COM objects boil down to "Never use 2 dots" (always hold a named
reference to all COM objects so that you can explicitely release them via Marshal.FinalReleaseComObject()):
https://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects/1307180#1307180
Also, don't force the GC to cleanup in the same method where objects are used (or debugging breaks):
https://stackoverflow.com/questions/17130382/understanding-garbage-collection-in-net/17131389#17131389
(We initiate our GC calls when disposing.)
*/
How do I properly clean up Excel interop objects?
Understanding garbage collection in .NET
In my program, I only open Excel when it's necessary, and I always wrap the ExcelInterfacer class in a using block. For that, you have to implement IDisposable, but that's easy. Doing this, my Excel processes always close very soon after they're not used anymore, but you're still slave to garbage collection - unless you force that immediately too, I guess.
I've never tried doing anything with the Excel document in the very next code statement after the using() block. You probably want to check whether the hidden ~$ version of the Excel file you want to mail still exists or has been closed. That would be the clear indication that the file is accessible.
My suggestion:
- Read those posts above and understand them. It's easy to detach the Excel RCW from the C# object that gets created, and then your FinalReleaseComObject() calls won't do much.
- Create a class for all Excel operations, and make sure that it
implements IDisposable correctly.
- Wrap all of your Excel interactions via that class in a using() block.
- Check if the ~$ file is gone as an indication if the Excel file is okay to access.
- Finally: get used to checking your running processes regularly. If your program crashes (or you quit the debug session) while Excel interop is active, you orphan that Excel process. Get used to killing them manually, so that no Excel is running when you start your next debug session.
Go from there :) For reference, the IDisposable code from my Excel interface class:
// Properly clean up Excel on quitting. See:
// https://stackoverflow.com/questions/2260990/com-object-that-has-been-separated-from-its-underlying-rcw-cannot-be-used
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (m_Disposed)
return;
// I know this looks like total overkill, but per http://www.xtremevbtalk.com/tutors-corner/160433-automating-office-programs-vb-net-com-interop.html
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
if (disposing)
{
// Free managed objects
CloseWorkbook();
if (m_Application != null)
{
m_Application.Quit();
Marshal.FinalReleaseComObject(m_Application);
}
m_Application = null;
}
m_Disposed = true;
}
~ExcelInterfacer()
{
Dispose(false);
}