Ich verwende die Excel-Interop in C # ( ApplicationClass
) und habe den folgenden Code in meine finally-Klausel eingefügt :
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Obwohl diese Art von funktioniert, ist der Excel.exe
Prozess auch nach dem Schließen von Excel noch im Hintergrund. Es wird erst freigegeben, wenn meine Anwendung manuell geschlossen wurde.
Was mache ich falsch oder gibt es eine Alternative, um sicherzustellen, dass Interop-Objekte ordnungsgemäß entsorgt werden?
c#
excel
interop
com-interop
Hades
quelle
quelle
Antworten:
Excel wird nicht beendet, da Ihre Anwendung immer noch Verweise auf COM-Objekte enthält.
Ich vermute, Sie rufen mindestens ein Mitglied eines COM-Objekts auf, ohne es einer Variablen zuzuweisen.
Für mich war es das excelApp.Worksheets- Objekt, das ich direkt verwendet habe, ohne es einer Variablen zuzuweisen :
Ich wusste nicht, dass C # intern einen Wrapper für die Arbeitsblätter erstellt hat COM-Objekt erstellt hat, der von meinem Code nicht freigegeben wurde (weil ich es nicht wusste) und der Grund dafür war, dass Excel nicht entladen wurde.
Ich habe die Lösung für mein Problem auf dieser Seite gefunden , die auch eine nette Regel für die Verwendung von COM-Objekten in C # enthält:
Mit diesem Wissen ist der richtige Weg, dies zu tun:
POST MORTEM UPDATE:
Ich möchte, dass jeder Leser diese Antwort von Hans Passant sehr sorgfältig liest, da sie die Falle erklärt, in die ich und viele andere Entwickler geraten sind. Als ich diese Antwort vor Jahren schrieb, wusste ich nicht, welche Auswirkungen der Debugger auf den Garbage Collector hat, und zog die falschen Schlussfolgerungen. Ich behalte meine Antwort aus Gründen der Geschichte unverändert, aber bitte lesen Sie diesen Link und gehen Sie nicht den Weg der "zwei Punkte": Grundlegendes zur Speicherbereinigung in .NET und Bereinigen von Excel-Interop-Objekten mit IDisposable
quelle
Sie können Ihr Excel-Anwendungsobjekt tatsächlich sauber freigeben, müssen jedoch vorsichtig sein.
Der Rat, für absolut jedes COM-Objekt, auf das Sie zugreifen, eine benannte Referenz beizubehalten und diese dann explizit freizugeben,
Marshal.FinalReleaseComObject()
ist theoretisch korrekt, in der Praxis jedoch leider sehr schwierig zu verwalten. Wenn man jemals irgendwohin rutscht und "zwei Punkte" verwendet oder Zellen über a iteriertfor each
Schleife oder einen anderen ähnlichen Befehl , haben Sie nicht referenzierte COM-Objekte und riskieren einen Hang. In diesem Fall gibt es keine Möglichkeit, die Ursache im Code zu finden. Sie müssten Ihren gesamten Code mit den Augen überprüfen und hoffentlich die Ursache finden, eine Aufgabe, die für ein großes Projekt fast unmöglich sein könnte.Die gute Nachricht ist, dass Sie nicht für jedes von Ihnen verwendete COM-Objekt einen benannten Variablenverweis verwalten müssen. Rufen Sie stattdessen an
GC.Collect()
und geben Sie dannGC.WaitForPendingFinalizers()
alle (normalerweise geringfügigen) Objekte frei, auf die Sie keine Referenz haben, und geben Sie dann explizit die Objekte frei, auf die Sie eine benannte Variablenreferenz halten.Sie sollten Ihre benannten Referenzen auch in umgekehrter Reihenfolge der Wichtigkeit freigeben: zuerst Bereichsobjekte, dann Arbeitsblätter, Arbeitsmappen und schließlich Ihr Excel-Anwendungsobjekt.
Angenommen, Sie hätten beispielsweise eine Range-Objektvariable mit dem Namen
xlRng
, eine Arbeitsblattvariable mit dem NamenxlSheet
, eine Arbeitsmappenvariable mit dem NamenxlBook
und eine Excel-Anwendungsvariable mit dem Namen benanntxlApp
, könnte Ihr Bereinigungscode folgendermaßen aussehen:In den meisten Codebeispielen zum Bereinigen von COM-Objekten aus .NET werden die Aufrufe
GC.Collect()
undGC.WaitForPendingFinalizers()
zweimal ausgeführt, wie in:Dies sollte jedoch nicht erforderlich sein, es sei denn, Sie verwenden Visual Studio Tools für Office (VSTO), das Finalizer verwendet, die bewirken, dass ein ganzes Diagramm von Objekten in der Finalisierungswarteschlange heraufgestuft wird. Solche Objekte würden erst bei der nächsten Speicherbereinigung freigegeben . Wenn Sie jedoch VSTO nicht verwenden, sollten Sie in der Lage sein,
GC.Collect()
und anzurufenGC.WaitForPendingFinalizers()
nur einmal.Ich weiß, dass explizites Anrufen
GC.Collect()
ein Nein-Nein ist (und es klingt sicherlich zweimal sehr schmerzhaft), aber um ehrlich zu sein führt kein Weg daran vorbei. Durch normale Operationen generieren Sie versteckte Objekte, auf die Sie keinen Verweis haben, den Sie daher nur durch Aufrufen freigeben könnenGC.Collect()
.Dies ist ein komplexes Thema, aber das ist wirklich alles, was dazu gehört. Sobald Sie diese Vorlage für Ihren Bereinigungsvorgang erstellt haben, können Sie normal codieren, ohne dass Wrapper usw. erforderlich sind :-)
Ich habe hier ein Tutorial dazu:
Automatisieren von Office-Programmen mit VB.Net / COM Interop
Es ist für VB.NET geschrieben, aber lassen Sie sich davon nicht abschrecken. Die Prinzipien sind genau die gleichen wie bei der Verwendung von C #.
quelle
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:
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:
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:
du schreibst:
Jetzt wird Excel geschlossen =)
quelle
UPDATE : C # -Code hinzugefügt und Link zu Windows-Jobs
Ich habe einige Zeit damit verbracht, dieses Problem herauszufinden, und zu der Zeit war XtremeVBTalk am aktivsten und reaktionsschnellsten. Hier ist ein Link zu meinem ursprünglichen Beitrag: Schließen eines Excel Interop-Prozesses sauber, auch wenn Ihre Anwendung abstürzt . Unten finden Sie eine Zusammenfassung des Beitrags und den in diesen Beitrag kopierten Code.
Application.Quit()
undProcess.Kill()
funktioniert größtenteils, schlägt jedoch fehl, wenn die Anwendungen katastrophal abstürzen . Dh wenn die App abstürzt, läuft der Excel-Prozess immer noch locker.Ich fand, dass dies eine saubere Lösung ist, da das Betriebssystem echte Aufräumarbeiten durchführt. Sie müssen lediglich den Excel-Prozess registrieren .
Windows-Jobcode
Wickelt die Win32-API-Aufrufe ein, um Interop-Prozesse zu registrieren.
Hinweis zum Konstruktorcode
info.LimitFlags = 0x2000;
heißt das.0x2000
ist derJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
Aufzählungswert, und dieser Wert wird von MSDN wie folgt definiert :Zusätzlicher Win32-API-Aufruf zum Abrufen der Prozess-ID (PID)
Den Code verwenden
quelle
Dies funktionierte für ein Projekt, an dem ich arbeitete:
Wir haben gelernt, dass es wichtig ist, jeden Verweis auf ein Excel-COM-Objekt auf null zu setzen, wenn Sie damit fertig sind. Dies beinhaltete Zellen, Blätter und alles.
quelle
Alles, was sich im Excel-Namespace befindet, muss freigegeben werden. Zeitraum
Sie können nicht tun:
Du musst es tun
gefolgt von der Freigabe der Objekte.
quelle
Erstens - Sie müssen nie anrufen
Marshal.ReleaseComObject(...)
oderMarshal.FinalReleaseComObject(...)
Excel Interop ausführen. Es ist ein verwirrendes Anti-Pattern, aber alle Informationen dazu, einschließlich von Microsoft, die darauf hinweisen, dass Sie COM-Referenzen manuell aus .NET freigeben müssen, sind falsch. Tatsache ist, dass die .NET-Laufzeit und der Garbage Collector die COM-Referenzen korrekt verfolgen und bereinigen. Für Ihren Code bedeutet dies, dass Sie die gesamte `while (...) - Schleife oben entfernen können.Zweitens müssen Sie sicherstellen, dass der Garbage Collector ausgeführt wird, wenn Sie sicherstellen möchten, dass die COM-Verweise auf ein COM-Objekt außerhalb des Prozesses bereinigt werden, wenn Ihr Prozess endet (damit der Excel-Prozess geschlossen wird). Sie machen das richtig mit Anrufen an
GC.Collect()
undGC.WaitForPendingFinalizers()
. Ein zweimaliger Aufruf ist sicher und stellt sicher, dass die Zyklen definitiv auch bereinigt werden (obwohl ich nicht sicher bin, ob dies erforderlich ist, und würde ein Beispiel begrüßen, das dies zeigt).Drittens werden beim Ausführen unter dem Debugger lokale Referenzen bis zum Ende der Methode künstlich am Leben erhalten (damit die Überprüfung lokaler Variablen funktioniert). Daher sind
GC.Collect()
Aufrufe zum Reinigen von Objekten wierng.Cells
bei derselben Methode nicht wirksam . Sie sollten den Code, der das COM-Interop aus der GC-Bereinigung ausführt, in separate Methoden aufteilen. (Dies war eine wichtige Entdeckung für mich, aus einem Teil der Antwort, die hier von @nightcoder gepostet wurde.)Das allgemeine Muster wäre also:
Es gibt viele falsche Informationen und Verwirrung zu diesem Problem, einschließlich vieler Beiträge zu MSDN und zum Stapelüberlauf (und insbesondere zu dieser Frage!).
Was mich schließlich überzeugte, genauer hinzuschauen und den richtigen Rat zu finden, war der Blog-Beitrag Marshal.ReleaseComObject als gefährlich eingestuft, zusammen mit der Suche nach dem Problem mit Referenzen, die unter dem Debugger am Leben gehalten wurden und meine früheren Tests verwirrten.
quelle
Ich habe eine nützliche generische Vorlage gefunden, mit deren Hilfe das richtige Entsorgungsmuster für COM-Objekte implementiert werden kann, für die Marshal.ReleaseComObject aufgerufen werden muss, wenn sie den Gültigkeitsbereich verlassen:
Verwendungszweck:
Vorlage:
Referenz:
http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/
quelle
Ich kann nicht glauben, dass dieses Problem die Welt seit 5 Jahren heimgesucht hat ... Wenn Sie eine Anwendung erstellt haben, müssen Sie sie zuerst herunterfahren, bevor Sie den Link entfernen.
beim Schließen
Wenn Sie eine Excel-Anwendung neu erstellen, wird im Hintergrund ein Excel-Programm geöffnet. Sie müssen diesem Excel-Programm befehlen, das Programm zu beenden, bevor Sie den Link freigeben, da dieses Excel-Programm nicht Teil Ihrer direkten Kontrolle ist. Daher bleibt es offen, wenn der Link freigegeben wird!
Gute Programmierung an alle ~~
quelle
Gemeinsame Entwickler, keine Ihrer Lösungen hat für mich funktioniert, daher entscheide ich mich, einen neuen Trick zu implementieren .
Geben Sie zunächst an: "Was ist unser Ziel?" => "Excel-Objekt nach unserem Job im Task-Manager nicht sehen"
OK. Lassen Sie no herausfordern und anfangen, es zu zerstören, aber denken Sie daran, andere Instanzen von Excel, die parallel ausgeführt werden, nicht zu zerstören.
Holen Sie sich also die Liste der aktuellen Prozessoren und rufen Sie die PID von EXCEL-Prozessen ab. Sobald Ihre Arbeit erledigt ist, haben wir einen neuen Gast in der Prozessliste mit einer eindeutigen PID. Finden und zerstören Sie genau diese.
<Beachten Sie, dass jeder neue Excel-Prozess während Ihres Excel-Jobs als neu und zerstört erkannt wird.> <Eine bessere Lösung besteht darin, die PID eines neu erstellten Excel-Objekts zu erfassen und diese einfach zu zerstören.>
Dies löst mein Problem, hoffe auch deins.
quelle
Dies scheint sicher zu kompliziert gewesen zu sein. Nach meiner Erfahrung gibt es nur drei wichtige Dinge, um Excel ordnungsgemäß zu schließen:
1: Stellen Sie sicher, dass keine verbleibenden Verweise auf die von Ihnen erstellte Excel-Anwendung vorhanden sind (Sie sollten sowieso nur eine haben; setzen Sie diese auf
null
).2: anrufen
GC.Collect()
3: Excel muss geschlossen werden, entweder durch den Benutzer, der das Programm manuell schließt, oder durch Ihren Aufruf
Quit
des Excel-Objekts. (Beachten Sie, dass diesQuit
so funktioniert, als ob der Benutzer versucht hätte, das Programm zu schließen, und ein Bestätigungsdialogfeld angezeigt wird, wenn nicht gespeicherte Änderungen vorliegen, auch wenn Excel nicht sichtbar ist. Der Benutzer könnte auf Abbrechen klicken, und Excel wurde dann nicht geschlossen. )1 muss vor 2 passieren, aber 3 kann jederzeit passieren.
Eine Möglichkeit, dies zu implementieren, besteht darin, das Interop-Excel-Objekt mit Ihrer eigenen Klasse zu versehen, die Interop-Instanz im Konstruktor zu erstellen und IDisposable mit Dispose so zu implementieren
Das wird Excel von der Seite Ihres Programms bereinigen. Sobald Excel geschlossen ist (manuell vom Benutzer oder von Ihnen angerufen)
Quit
), wird der Prozess beendet. Wenn das Programm bereits geschlossen wurde, verschwindet der Vorgang beimGC.Collect()
Aufruf.(Ich bin nicht sicher, wie wichtig es ist, aber Sie möchten vielleicht einen
GC.WaitForPendingFinalizers()
Anruf nach demGC.Collect()
Anruf, aber es ist nicht unbedingt erforderlich, den Excel-Prozess loszuwerden.)Das hat bei mir seit Jahren ohne Probleme funktioniert. Beachten Sie jedoch, dass Sie, während dies funktioniert, tatsächlich ordnungsgemäß schließen müssen, damit es funktioniert. Wenn Sie Ihr Programm unterbrechen, bevor Excel bereinigt wird, werden immer noch excel.exe-Prozesse angesammelt (normalerweise durch Drücken von "Stop", während Ihr Programm debuggt wird).
quelle
Marshal
.Um die Gründe hinzuzufügen, warum Excel nicht geschlossen wird, selbst wenn Sie beim Lesen und Erstellen direkte Aktualisierungen für jedes Objekt erstellen, wird die 'For'-Schleife verwendet.
quelle
Ich habe traditionell den Rat befolgt, der in der Antwort von VVS enthalten ist . Um diese Antwort mit den neuesten Optionen auf dem neuesten Stand zu halten, denke ich, dass alle meine zukünftigen Projekte die "NetOffice" -Bibliothek verwenden werden.
NetOffice ist ein vollständiger Ersatz für die Office-PIAs und vollständig . Es handelt sich um eine Sammlung von verwalteten COM-Wrappern, die die Bereinigung verarbeiten können, die bei der Arbeit mit Microsoft Office in .NET häufig solche Kopfschmerzen verursacht.
Einige Hauptmerkmale sind:
Ich bin in keiner Weise mit dem Projekt verbunden; Ich schätze die starke Reduzierung der Kopfschmerzen wirklich.
quelle
Die hier akzeptierte Antwort ist korrekt, aber beachten Sie auch, dass nicht nur "Zwei-Punkt" -Referenzen vermieden werden müssen, sondern auch Objekte, die über den Index abgerufen werden. Sie müssen auch nicht warten, bis Sie mit dem Programm fertig sind, um diese Objekte zu bereinigen. Am besten erstellen Sie Funktionen, die sie bereinigen, sobald Sie mit ihnen fertig sind, wenn möglich. Hier ist eine Funktion, die ich erstellt habe und die einige Eigenschaften eines Style-Objekts mit dem Namen zuweist
xlStyleHeader
:Beachten Sie, dass ich einstellen musste
xlBorders[Excel.XlBordersIndex.xlEdgeBottom]
eine Variable , um dies zu bereinigen (nicht wegen der zwei Punkte, die sich auf eine Aufzählung beziehen, die nicht freigegeben werden muss, sondern weil das Objekt, auf das ich mich beziehe, tatsächlich ein Border-Objekt ist das muss freigegeben werden).Diese Art von Dingen ist in Standardanwendungen, die eine hervorragende Bereinigung nach sich selbst leisten, nicht unbedingt erforderlich. In ASP.NET-Anwendungen wird Excel jedoch auch nur eine dieser Anwendungen verpassen, unabhängig davon, wie oft Sie den Garbage Collector aufrufen läuft immer noch auf Ihrem Server.
Es erfordert viel Liebe zum Detail und viele Testausführungen, während der Task-Manager beim Schreiben dieses Codes überwacht wird. Dies erspart Ihnen jedoch den Aufwand, verzweifelt nach Codeseiten zu suchen, um die eine Instanz zu finden, die Sie verpasst haben. Dies ist besonders wichtig, wenn Sie in Schleifen arbeiten, in denen Sie JEDE INSTANZ eines Objekts freigeben müssen, obwohl es bei jeder Schleife denselben Variablennamen verwendet.
quelle
Nach dem Versuch
GC.Collect()
undGC.WaitForPendingFinalizers()
zweimal am EndeDie endgültige Lösung, die für mich funktioniert, besteht darin, einen Satz zu verschieben
dass wir am Ende der Funktion einen Wrapper wie folgt hinzugefügt haben:
quelle
Ich habe das genau befolgt ... Aber ich bin immer noch auf Probleme 1 von 1000 gestoßen. Wer weiß warum. Zeit, den Hammer herauszubringen ...
Unmittelbar nach der Instanziierung der Excel-Anwendungsklasse erhalte ich den gerade erstellten Excel-Prozess.
Sobald ich alle oben genannten COM-Bereinigungen durchgeführt habe, stelle ich sicher, dass der Prozess nicht ausgeführt wird. Wenn es noch läuft, töte es!
quelle
¨ ° º¤ø „¸ Schießen Sie Excel proc und kauen Sie Kaugummi ¸„ ø¤º ° ¨
quelle
Sie müssen sich bewusst sein, dass Excel auch sehr empfindlich auf die Kultur reagiert, unter der Sie arbeiten.
Möglicherweise müssen Sie die Kultur auf EN-US einstellen, bevor Sie Excel-Funktionen aufrufen können. Dies gilt nicht für alle Funktionen, sondern für einige.
Dies gilt auch dann, wenn Sie VSTO verwenden.
Für Details: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369
quelle
"Verwenden Sie niemals zwei Punkte mit COM-Objekten" ist eine gute Faustregel, um das Auslaufen von COM-Referenzen zu vermeiden. Excel PIA kann jedoch auf mehr als auf den ersten Blick erkennbare Weise zum Auslaufen führen.
Eine dieser Möglichkeiten besteht darin, ein Ereignis zu abonnieren, das von einem der COM-Objekte des Excel-Objektmodells verfügbar gemacht wird.
Abonnieren Sie beispielsweise das WorkbookOpen-Ereignis der Anwendungsklasse.
Einige Theorie zu COM-Ereignissen
COM-Klassen stellen eine Gruppe von Ereignissen über Rückrufschnittstellen bereit. Um Ereignisse zu abonnieren, kann der Clientcode einfach ein Objekt registrieren, das die Rückrufschnittstelle implementiert, und die COM-Klasse ruft ihre Methoden als Antwort auf bestimmte Ereignisse auf. Da die Rückrufschnittstelle eine COM-Schnittstelle ist, ist es die Pflicht des implementierenden Objekts, den Referenzzähler jedes empfangenen COM-Objekts (als Parameter) für einen der Ereignishandler zu verringern.
Wie Excel PIA COM-Ereignisse verfügbar macht
Excel PIA macht COM-Ereignisse der Excel-Anwendungsklasse als herkömmliche .NET-Ereignisse verfügbar. Immer wenn der Clientcode ein .NET-Ereignis abonniert (Schwerpunkt auf 'a'), erstellt PIA eine Instanz einer Klasse, die die Rückrufschnittstelle implementiert, und registriert sie bei Excel.
Daher wird eine Reihe von Rückrufobjekten als Antwort auf verschiedene Abonnementanforderungen aus dem .NET-Code bei Excel registriert. Ein Rückrufobjekt pro Ereignisabonnement.
Eine Rückrufschnittstelle für die Ereignisbehandlung bedeutet, dass PIA alle Schnittstellenereignisse für jede .NET-Ereignisabonnementanforderung abonnieren muss. Es kann nicht auswählen. Beim Empfang eines Ereignisrückrufs prüft das Rückrufobjekt, ob der zugeordnete .NET-Ereignishandler an dem aktuellen Ereignis interessiert ist oder nicht, und ruft dann entweder den Handler auf oder ignoriert den Rückruf stillschweigend.
Auswirkung auf die Referenzanzahl der COM-Instanzen
Alle diese Rückrufobjekte verringern nicht die Referenzanzahl der COM-Objekte, die sie (als Parameter) für eine der Rückrufmethoden erhalten (selbst für diejenigen, die stillschweigend ignoriert werden). Sie verlassen sich ausschließlich auf den CLR- Garbage Collector, um die COM-Objekte freizugeben.
Da der GC-Lauf nicht deterministisch ist, kann dies dazu führen, dass der Excel-Prozess länger als gewünscht unterbrochen wird und der Eindruck eines "Speicherverlusts" entsteht.
Lösung
Die einzige Lösung besteht derzeit darin, den PIA-Ereignisanbieter für die COM-Klasse zu vermeiden und einen eigenen Ereignisanbieter zu schreiben, der COM-Objekte deterministisch freigibt.
Für die Anwendungsklasse kann dies durch Implementieren der AppEvents-Schnittstelle und anschließendes Registrieren der Implementierung in Excel mithilfe der IConnectionPointContainer-Schnittstelle erfolgen . Die Anwendungsklasse (und im Übrigen alle COM-Objekte, die Ereignisse mithilfe des Rückrufmechanismus verfügbar machen) implementiert die IConnectionPointContainer-Schnittstelle.
quelle
Wie bereits erwähnt, müssen Sie für jedes von Ihnen verwendete Excel-Objekt eine explizite Referenz erstellen und Marshal.ReleaseComObject für diese Referenz aufrufen, wie in diesem KB-Artikel beschrieben . Sie müssen auch try / finally verwenden, um sicherzustellen, dass ReleaseComObject immer aufgerufen wird, auch wenn eine Ausnahme ausgelöst wird. Dh statt:
Sie müssen etwas tun wie:
Sie müssen auch Application.Quit aufrufen, bevor Sie das Application-Objekt freigeben, wenn Excel geschlossen werden soll.
Wie Sie sehen, wird dies schnell extrem unhandlich, sobald Sie versuchen, etwas zu tun, das auch nur mäßig komplex ist. Ich habe erfolgreich .NET-Anwendungen mit einer einfachen Wrapper-Klasse entwickelt, die einige einfache Manipulationen des Excel-Objektmodells umfasst (Öffnen einer Arbeitsmappe, Schreiben in einen Bereich, Speichern / Schließen der Arbeitsmappe usw.). Die Wrapper-Klasse implementiert IDisposable, implementiert Marshal.ReleaseComObject sorgfältig für jedes verwendete Objekt und macht keine Excel-Objekte öffentlich für den Rest der App verfügbar.
Dieser Ansatz lässt sich jedoch nicht besser für komplexere Anforderungen skalieren.
Dies ist ein großer Mangel an .NET COM Interop. Für komplexere Szenarien würde ich ernsthaft in Betracht ziehen, eine ActiveX-DLL in VB6 oder einer anderen nicht verwalteten Sprache zu schreiben, an die Sie die gesamte Interaktion mit Out-Proc-COM-Objekten wie Office delegieren können. Sie können dann in Ihrer .NET-Anwendung auf diese ActiveX-DLL verweisen, und die Dinge werden viel einfacher, da Sie nur diese eine Referenz freigeben müssen.
quelle
Wenn all die oben genannten Dinge nicht funktioniert haben, geben Sie Excel etwas Zeit, um die Blätter zu schließen:
quelle
Stellen Sie sicher, dass Sie alle Objekte freigeben, die sich auf Excel beziehen!
Ich habe ein paar Stunden damit verbracht, verschiedene Möglichkeiten auszuprobieren. Alle sind großartige Ideen, aber ich habe endlich meinen Fehler gefunden: Wenn Sie nicht alle Objekte freigeben, kann Ihnen in meinem Fall keine der oben genannten Möglichkeiten helfen . Stellen Sie sicher, dass Sie alle Objekte einschließlich des ersten Bereichs freigeben!
Die Optionen sind hier zusammen .
quelle
Ein großartiger Artikel zum Freigeben von COM-Objekten ist 2.5 Freigeben von COM-Objekten (MSDN).
Die Methode, die ich empfehlen würde, besteht darin, Ihre Excel.Interop-Referenzen auf Null zu setzen, wenn es sich um nicht lokale Variablen handelt, und dann
GC.Collect()
und aufzurufenGC.WaitForPendingFinalizers()
zweimal. Interop-Variablen mit lokalem Gültigkeitsbereich werden automatisch berücksichtigt.Dadurch entfällt die Notwendigkeit, für jeden eine benannte Referenz zu führen COM-Objekt .
Hier ist ein Beispiel aus dem Artikel:
Diese Wörter stammen direkt aus dem Artikel:
quelle
Die Zwei-Punkte-Regel hat bei mir nicht funktioniert. In meinem Fall habe ich eine Methode zum Bereinigen meiner Ressourcen wie folgt erstellt:
quelle
Meine Lösung
quelle
Sie sollten bei der Verwendung von Word / Excel-Interop-Anwendungen sehr vorsichtig sein. Nachdem wir alle Lösungen ausprobiert hatten, war noch viel "WinWord" -Prozess auf dem Server offen (mit mehr als 2000 Benutzern).
Nachdem ich stundenlang an dem Problem gearbeitet hatte, wurde mir klar, dass der
Word.ApplicationClass.Document.Open()
IIS-Arbeitsprozess (w3wp.exe) abstürzen würde , wenn ich mehr als ein paar Dokumente gleichzeitig mit verschiedenen Threads öffnen würde, wobei alle WinWord-Prozesse offen bleiben würden!Ich denke, es gibt keine absolute Lösung für dieses Problem, sondern den Wechsel zu anderen Methoden wie der Office Open XML- Entwicklung.
quelle
Die akzeptierte Antwort hat bei mir nicht funktioniert. Der folgende Code im Destruktor hat den Job erledigt.
quelle
Ich arbeite derzeit an der Office-Automatisierung und bin auf eine Lösung gestoßen, die jedes Mal für mich funktioniert. Es ist einfach und beinhaltet nicht das Beenden von Prozessen.
Es scheint, dass durch bloßes Durchlaufen der aktuell aktiven Prozesse und auf irgendeine Weise "Zugriff" auf einen offenen Excel-Prozess jede streunende hängende Instanz von Excel entfernt wird. Der folgende Code sucht einfach nach Prozessen, bei denen der Name 'Excel' lautet, und schreibt dann die MainWindowTitle-Eigenschaft des Prozesses in eine Zeichenfolge. Diese 'Interaktion' mit dem Prozess scheint Windows dazu zu bringen, die eingefrorene Instanz von Excel einzuholen und abzubrechen.
Ich führe die folgende Methode kurz vor dem Add-In aus, das ich gerade beende, da es das Entladeereignis auslöst. Es entfernt jedes Mal alle hängenden Instanzen von Excel. Ehrlich gesagt bin ich mir nicht ganz sicher, warum dies funktioniert, aber es funktioniert gut für mich und könnte am Ende jeder Excel-Anwendung platziert werden, ohne sich um doppelte Punkte, Marshal.ReleaseComObject oder das Beenden von Prozessen kümmern zu müssen. Ich wäre sehr an Vorschlägen interessiert, warum dies effektiv ist.
quelle
Ich denke, dass einige davon nur die Art und Weise sind, wie das Framework mit Office-Anwendungen umgeht, aber ich könnte mich irren. An manchen Tagen bereinigen einige Anwendungen die Prozesse sofort, und an anderen Tagen scheint es zu warten, bis die Anwendung geschlossen wird. Im Allgemeinen höre ich auf, auf die Details zu achten, und stelle nur sicher, dass am Ende des Tages keine zusätzlichen Prozesse im Umlauf sind.
Auch und vielleicht bin ich damit fertig, Dinge zu vereinfachen, aber ich denke, Sie können einfach ...
Wie ich bereits sagte, neige ich nicht dazu, auf die Details zu achten, wann der Excel-Prozess erscheint oder verschwindet, aber das funktioniert normalerweise bei mir. Ich mag es auch nicht, Excel-Prozesse für etwas anderes als die minimale Zeit zu halten, aber ich bin wahrscheinlich nur paranoid.
quelle
Wie einige haben wohl schon geschrieben, ist es nicht nur wichtig , wie Sie schließen das Excel (Objekt); Es ist auch wichtig, wie Sie öffnen und auch nach Art des Projekts.
In einer WPF-Anwendung funktioniert im Grunde derselbe Code ohne oder mit sehr wenigen Problemen.
Ich habe ein Projekt, in dem dieselbe Excel-Datei mehrmals für unterschiedliche Parameterwerte verarbeitet wird - z. B. anhand von Werten in einer generischen Liste.
Ich habe alle Excel-bezogenen Funktionen in die Basisklasse und den Parser in eine Unterklasse eingefügt (verschiedene Parser verwenden gemeinsame Excel-Funktionen). Ich wollte nicht, dass Excel für jedes Element in einer generischen Liste geöffnet und wieder geschlossen wird, deshalb habe ich es nur einmal in der Basisklasse geöffnet und in der Unterklasse geschlossen. Ich hatte Probleme beim Verschieben des Codes in eine Desktop-Anwendung. Ich habe viele der oben genannten Lösungen ausprobiert.
GC.Collect()
wurde bereits zuvor implementiert, zweimal wie vorgeschlagen.Dann habe ich beschlossen, den Code zum Öffnen von Excel in eine Unterklasse zu verschieben. Anstatt nur einmal zu öffnen, erstelle ich jetzt ein neues Objekt (Basisklasse) und öffne Excel für jedes Element und schließe es am Ende. Es gibt einige Leistungseinbußen, aber basierend auf mehreren Tests werden Excel-Prozesse problemlos geschlossen (im Debug-Modus), sodass auch temporäre Dateien entfernt werden. Ich werde mit dem Testen fortfahren und weitere schreiben, wenn ich einige Updates bekomme.
Das Fazit lautet: Sie müssen auch den Initialisierungscode überprüfen, insbesondere wenn Sie viele Klassen usw. haben.
quelle