Vorwort: Meine Antwort enthält zwei Lösungen. Seien Sie also beim Lesen vorsichtig und verpassen Sie nichts.
Es gibt verschiedene Möglichkeiten und Ratschläge zum Entladen von Excel-Instanzen, z.
JEDES com-Objekt explizit mit Marshal.FinalReleaseComObject () freigeben (nicht zu vergessen implizit erstellte com-Objekte). Um jedes erstellte COM-Objekt freizugeben, können Sie die hier genannte Regel von 2 Punkten verwenden:
Wie bereinige ich Excel-Interop-Objekte ordnungsgemäß?
Aufruf von GC.Collect () und GC.WaitForPendingFinalizers (), damit CLR nicht verwendete com-Objekte freigibt * (Tatsächlich funktioniert es, siehe meine zweite Lösung für Details)
Wenn Sie überprüfen, ob die com-server-Anwendung möglicherweise ein Meldungsfeld anzeigt, das auf die Antwort des Benutzers wartet (obwohl ich nicht sicher bin, ob das Schließen von Excel verhindert werden kann, habe ich jedoch einige Male davon gehört).
Senden der WM_CLOSE-Nachricht an das Hauptfenster von Excel
Ausführen der Funktion, die mit Excel funktioniert, in einer separaten AppDomain. Einige Leute glauben, dass die Excel-Instanz geschlossen wird, wenn AppDomain entladen wird.
Töten aller Excel-Instanzen, die nach dem Start unseres Excel-Interoping-Codes instanziiert wurden.
ABER! Manchmal helfen all diese Optionen einfach nicht oder können nicht angemessen sein!
Zum Beispiel habe ich gestern herausgefunden, dass Excel in einer meiner Funktionen (die mit Excel funktioniert) nach dem Ende der Funktion weiter ausgeführt wird. Ich habe alles versucht! Ich habe die gesamte Funktion 10 Mal gründlich überprüft und Marshal.FinalReleaseComObject () für alles hinzugefügt! Ich hatte auch GC.Collect () und GC.WaitForPendingFinalizers (). Ich habe nach versteckten Meldungsfeldern gesucht. Ich habe versucht, eine WM_CLOSE-Nachricht an das Excel-Hauptfenster zu senden. Ich habe meine Funktion in einer separaten AppDomain ausgeführt und diese Domain entladen. Nichts hat geholfen! Die Option zum Schließen aller Excel-Instanzen ist unangemessen. Wenn der Benutzer während der Ausführung meiner Funktion, die auch mit Excel funktioniert, eine andere Excel-Instanz manuell startet, wird diese Instanz auch von meiner Funktion geschlossen. Ich wette, der Benutzer wird nicht glücklich sein! Ehrlich gesagt ist dies eine lahme Option (keine Beleidigung, Leute).Lösung : Beenden Sie den Excel-Prozess mit hWnd des Hauptfensters (es ist die erste Lösung).
Hier ist der einfache Code:
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
/// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
public static bool TryKillProcessByMainWindowHwnd(int hWnd)
{
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
if(processID == 0) return false;
try
{
Process.GetProcessById((int)processID).Kill();
}
catch (ArgumentException)
{
return false;
}
catch (Win32Exception)
{
return false;
}
catch (NotSupportedException)
{
return false;
}
catch (InvalidOperationException)
{
return false;
}
return true;
}
/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running).
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
if (processID == 0)
throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
Process.GetProcessById((int)processID).Kill();
}
Wie Sie sehen, habe ich zwei Methoden gemäß dem Try-Parse-Muster bereitgestellt (ich denke, dass dies hier angemessen ist): Eine Methode löst keine Ausnahme aus, wenn der Prozess nicht beendet werden konnte (zum Beispiel existiert der Prozess nicht mehr). und eine andere Methode löst die Ausnahme aus, wenn der Prozess nicht beendet wurde. Die einzige Schwachstelle in diesem Code sind Sicherheitsberechtigungen. Theoretisch hat der Benutzer möglicherweise keine Berechtigungen zum Beenden des Prozesses, aber in 99,99% aller Fälle verfügt der Benutzer über solche Berechtigungen. Ich habe es auch mit einem Gastkonto getestet - es funktioniert perfekt.
Ihr Code, der mit Excel arbeitet, kann also folgendermaßen aussehen:
int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);
Voila! Excel ist beendet! :) :)
Ok, gehen wir zurück zur zweiten Lösung, wie ich zu Beginn des Beitrags versprochen habe.
Die zweite Lösung besteht darin, GC.Collect () und GC.WaitForPendingFinalizers () aufzurufen. Ja, sie funktionieren tatsächlich, aber Sie müssen hier vorsichtig sein!
Viele Leute sagen (und ich sagte), dass das Aufrufen von GC.Collect () nicht hilft. Aber der Grund, warum es nicht helfen würde, ist, wenn es immer noch Verweise auf COM-Objekte gibt! Einer der beliebtesten Gründe dafür, dass GC.Collect () nicht hilfreich ist, ist das Ausführen des Projekts im Debug-Modus. Im Debug-Modus werden Objekte, auf die nicht mehr wirklich verwiesen wird, erst am Ende der Methode mit Müll gesammelt.
Wenn Sie also GC.Collect () und GC.WaitForPendingFinalizers () ausprobiert haben und es nicht geholfen hat, versuchen Sie Folgendes:
1) Versuchen Sie, Ihr Projekt im Release-Modus auszuführen, und überprüfen Sie, ob Excel korrekt geschlossen wurde
2) Wickeln Sie die Arbeitsweise mit Excel in eine separate Methode ein. Also, anstatt so etwas:
void GenerateWorkbook(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
du schreibst:
void GenerateWorkbook(...)
{
try
{
GenerateWorkbookInternal(...);
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private void GenerateWorkbookInternal(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
}
}
Jetzt wird Excel geschlossen =)