Here's what I've done:
Keep your Excel instance a singleton:
// Our singleton excel instance
private static Excel.Application instance;
/// <summary>
/// Returns an instance of an Excel Application.
/// This instance is a singleton.
/// </summary>
public static Excel.Application GetInstance
{
get
{
if (instance == null)
{
try
{
instance = new Excel.Application();
// Give the app a GUID so we can kill it later
instance.Caption = System.Guid.NewGuid().ToString().ToUpper(CultureInfo.CurrentCulture);
instance.Visible = false;
}
catch (COMException ce)
{
ShowMessage(ce.Message, MessageBoxIcon.Error);
}
}
return instance;
}
}
When you're done, you can destroy it:
public static void DestroyInstance()
{
if (instance != null)
{
// Close the workbooks
object missing = Type.Missing;
foreach (Excel.Workbook book in GetInstance.Workbooks)
{
book.Close(false, book.FullName, missing);
}
String appVersion = instance.Version;
String appCaption = instance.Caption;
IntPtr appHandle = IntPtr.Zero;
if (Convert.ToDouble(appVersion, CultureInfo.CurrentCulture) >= 10)
{
appHandle = new IntPtr(instance.Parent.Hwnd);
}
// Quit and release the object
instance.Workbooks.Close();
instance.Quit();
Release(instance);
// Force a cleanup. MSDN documentation shows them making
// two sets of collect and wait calls.
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
EnsureProcessKilled(appHandle, appCaption);
}
}
The EnsureProcessKilled method looks like:
/// <summary>
/// This method contains a number of ways to try and kill
/// the Excel process created when an instance is instantiated.
/// </summary>
/// <param name="windowHandle">Handle of the application window</param>
/// <param name="caption">A custom GUI stored as the app's caption</param>
public static void EnsureProcessKilled(IntPtr windowHandle, String caption)
{
NativeMethods.SetLastError(0);
if (IntPtr.Equals(windowHandle, IntPtr.Zero))
{
windowHandle = NativeMethods.FindWindow(null, caption);
}
if (IntPtr.Equals(windowHandle, IntPtr.Zero))
{
// Can't find the window; assumed it's closed
return;
}
int resourceId;
int processId = 0;
resourceId = NativeMethods.GetWindowThreadProcessId(windowHandle, ref processId);
if (processId == 0)
{
// Can't get process id
if (NativeMethods.EndTask(windowHandle) != 0)
{
return; // Success
}
}
// Couldn't end it nicely, let's kill it
System.Diagnostics.Process process;
process = System.Diagnostics.Process.GetProcessById(processId);
process.CloseMainWindow();
process.Refresh();
if (process.HasExited)
{
return;
}
// If we get here, it's being really stubborn. Say goodbye, EXCEL.EXE!
process.Kill();
}
And you're going to need the NativeMethods class:
public static class NativeMethods
{
[DllImport("user32.dll")]
internal static extern int EndTask(IntPtr windowHandle);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr FindWindow(String className, String windowName);
[DllImport("user32.dll")]
internal static extern int GetWindowThreadProcessId(IntPtr windowHandle, ref int processId);
[DllImport("kernel32.dll")]
internal static extern IntPtr SetLastError(int errorCode);
}
And one other little helper function:
public static void Release(Object releasable)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(releasable);
releasable = null;
}