Frage

Ich bin mit dem Excel-Interop in C # (ApplicationClass) und habe den folgenden Code in meinem finally platziert:

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();

Auch wenn diese Art von Arbeiten, der Excel.exe Prozess ist noch im Hintergrund, auch nachdem ich Excel schließen. Es wird nur freigegeben, wenn meine Anwendung manuell geschlossen wird.

Was mache ich falsch, oder ist es eine Alternative Interop-Objekte, um sicherzustellen, ordnungsgemäß entsorgt?

War es hilfreich?

Lösung

Excel nicht beenden, weil Ihre Anwendung noch Verweise auf COM-Objekte.

Ich denke, Sie mindestens ein Mitglied eines COM-Objekts aufrufen, ohne sie einer Variablen zugewiesen.

Für mich war es die excelApp.Worksheets Objekt, das ich ohne direkt verwendet es einer Variablen zugewiesen:

Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);

Ich wusste nicht, dass intern C # einen Wrapper für die erstellt Arbeitsblatt COM-Objekt, das durch meinen Code frei bekommen nicht (weil ich nicht bewusst, es war) und war die Ursache, warum Excel wurde nicht entladen.

fand ich die Lösung für mein Problem auf dieser Seite , die auch eine schöne Regel für die Verwendung von COM-Objekten in C # hat:

  

Sie niemals zwei Punkte mit COM-Objekten verwendet werden.


So mit diesem Wissen der richtige Weg, die oben zu tun ist:

Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);

FLEISCH UPDATE:

Ich möchte jeder Leser diese Antwort von Hans Passant sehr sorgfältig zu lesen, da sie die Falle ich und viele andere Entwickler stolperte in erklärt. Als ich diese Antwort Jahren schrieb es her, dass ich weiß nicht, über die Wirkung der Debugger an den Garbage Collector hat und zog die falschen Schlüsse. Ich halte meine Antwort unverändert im Interesse der Geschichte, sondern lesen Sie bitte diesen Link und nicht gehen, um die Art und Weise von den „zwei Punkte“: Legendes Garbage collection in .NET und bereinigen Excel Interop-Objekte mit IDisposable

Andere Tipps

können Sie geben tatsächlich Ihr Excel-Application-Objekt sauber, aber Sie haben dafür Sorge zu tragen.

Der Rat für absolut jedes COM-Objekt einen benannten Verweis auf halten Sie Zugriff auf und lassen Sie dann explizit über Marshal.FinalReleaseComObject() ist theoretisch richtig, aber leider sehr schwierig in der Praxis zu verwalten. Wenn man immer und überall verwendet „zwei Punkte“ schlüpft oder Iterierten Zellen über eine for each Schleife oder jede andere ähnliche Art von Befehl, dann werden Sie nicht referenzierten COM-Objekte und einen Hang riskieren. In diesem Fall gäbe es keine Möglichkeit, die Ursache im Code zu finden; Sie würden alle Ihre Codes mit dem Auge überprüfen und hoffentlich die Ursache finden, eine Aufgabe, die fast unmöglich, für ein großes Projekt sein könnte.

Die gute Nachricht ist, dass Sie nicht wirklich einen benannte Variable Bezug auf jedes COM-Objekt pflegen müssen Sie verwenden. Stattdessen rufen GC.Collect() und dann GC.WaitForPendingFinalizers() alle veröffentlichen die (in der Regel kleinere) Objekte, auf die Sie keinen Bezug halten, und lassen Sie dann explizit die Objekte, auf die Sie eine benannte Variable Referenz nicht halten.

Sie sollten auch Ihre Nennung von Namen in umgekehrter Reihenfolge ihrer Bedeutung Mitteilung:. Bereich Objekte zuerst, dann Arbeitsblatt, Arbeitsmappe und dann schließlich Ihr Excel-Application-Objekt

Zum Beispiel unter der Annahme, dass Sie ein Range-Objekt Variable mit dem Namen xlRng hatte, ein Arbeitsblatt Variable mit dem Namen xlSheet, eine Arbeitsmappe Variable mit dem Namen xlBook und eine Excel-Application-Variable namens xlApp, dann Bereinigungscode in etwa wie folgt aussehen:

// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();

Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);

xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);

xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);

In den meisten Codebeispiele Sie zur Reinigung von COM-Objekten .NET sehen werden, sind die GC.Collect() und GC.WaitForPendingFinalizers() Anrufe ZWEIMAL wie in:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

Dies sollte jedoch nicht erforderlich, es sei denn Sie Visual Studio Tools for Office (VSTO) verwenden, die Finalizer verwendet, die eine ganze Graph von Objekten kann in Finalisierungsschlange gefördert werden. Solche Objekte werden nicht bis zum weiter Garbage Collection freigegeben wird. Wenn Sie jedoch nicht VSTO verwenden, sollten Sie nur einmal GC.Collect() und GC.WaitForPendingFinalizers() nennen können.

ich weiß, dass expliziten Aufruf GC.Collect() ist ein no-no (und sicherlich tut es zweimal klingt sehr schmerzhaft), aber es gibt keinen Weg, um es, ehrlich zu sein. Durch den normalen Betrieb werden Sie versteckte Objekte erzeugen, zu denen Sie keinen Hinweis halten, dass Sie deshalb nicht durch andere Mittel außer Aufruf GC.Collect() freigeben kann.

Dies ist ein komplexes Thema, aber das wirklich alles ist, gibt es zu. Sobald Sie diese Vorlage für Ihre Bereinigungsverfahren etablieren können Sie Code normalerweise, ohne die Notwendigkeit für Verpackungen, usw.: -)

Ich habe ein Tutorial zu diesem Thema hier:

Automatisieren von Office-Programmen mit VB.Net / COM Interop

Es ist für VB.NET geschrieben, aber lassen Sie sich nicht durch, dass die Prinzipien sind genau die gleichen wie bei der Verwendung von C #.

aufschieben

Vorwort. Meine Antwort enthält zwei Lösungen, also seien Sie vorsichtig beim Lesen und nichts verpassen

Es gibt verschiedene Möglichkeiten und Ratschläge, wie Excel-Instanz entladen zu machen, wie zum Beispiel:

  • Loslassen JEDEN COM-Objekt explizit mit Marshal.FinalReleaseComObject () (Nicht zu vergessen, um implizit erstellt com-Objekte). Loslassen jeder erstellt COM-Objekt, können Sie verwenden die Regel von 2 Punkten hier erwähnt:
    Wie aufzuräumen ich richtig Excel Interop Objekte?

  • Der Aufruf GC.Collect () und GC.WaitForPendingFinalizers () zu machen CLR Freigabe nicht benutzte com-Objekte * (Eigentlich ist es funktioniert, finden Sie meine zweite Lösung für weitere Details)

  • Überprüfen, ob com-Server-Anwendung vielleicht zeigt ein Meldungsfeld wartet der Benutzer zu beantworten (obwohl ich nicht bin sicher, dass es von Excel verhindern kann Schließen, aber ich hörte es ein paar mal)

  • Senden WM_CLOSE-Nachricht an den Haupt Excel-Fenster

  • Ausführen der Funktion, die funktioniert mit Excel in einem separaten AppDomain. Einige Leute glauben, Excel-Instanz wird geschlossen, wenn AppDomain ist entladen wird.

  • Das Töten alle Instanzen übertreffen, die begann, nachdem unsere Excel-interoping Code instanziiert wurden.

ABER! Manchmal nur all diese Optionen nicht helfen können oder nicht geeignet sein!

Zum Beispiel gestern fand ich heraus, dass in einer meiner Funktionen (die mit Excel arbeitet) Excel weiterläuft, nachdem die Funktion beendet. Ich habe alles versucht! Ich habe gründlich die gesamte Funktion 10-mal und hinzugefügt Marshal.FinalReleaseComObject () für alles! Ich hatte GC.Collect auch () und GC.WaitForPendingFinalizers (). Ich habe für versteckte Message-Boxen. Ich habe versucht, WM_CLOSE-Nachricht an den Haupt-Excel-Fenster zu senden. Ich ausgeführt meine Funktion in einer separaten AppDomain und diese Domäne entladen. Es half alles nichts! Die Option mit allen Instanzen treffen Schließen ist unangemessen, weil, wenn der Benutzer manuell eine andere Excel-Instanz gestartet wird, während die Ausführung meiner Funktion, die auch mit Excel arbeitet, dann wird diese Instanz auch durch meine Funktion geschlossen werden. Ich wette, der Benutzer nicht glücklich sein! Also, ehrlich gesagt, ist dies eine lahme Option (keine Straftat Männer). So verbrachte ich ein paar Stunden, bevor ich eine gute (in meiner bescheidenen Meinung nach) Lösung gefunden: Kill-Excel-Prozess durch hWnd seines 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 mich stellte zwei Methoden sehen können, nach Try-Parse-Muster (Ich denke, es ist hier angebracht ist): eine Methode nicht die Ausnahme nicht aus, wenn der Prozess nicht (beispielsweise getötet werden könnte der Prozess nicht existiert mehr), und ein anderes Verfahren wirft die Ausnahme, wenn der Prozess nicht getötet wurde. Die einzige schwache Stelle in diesem Code sind Sicherheitsberechtigungen. Theoretisch haben kann der Benutzer keine Berechtigungen, den Prozess zu töten, aber in 99,99% aller Fälle Benutzer haben solche Berechtigungen. Getestet habe ich es auch mit einem Gastkonto -. Es funktioniert perfekt

So, Ihr Code, mit Excel arbeiten, kann wie folgt 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 wird beendet! :)

Ok, lassen Sie sich auf die zweite Lösung gehen, wie ich am Anfang der Post versprochen. Die zweite Lösung ist GC.Collect () aufzurufen und GC.WaitForPendingFinalizers (). Ja, sie tatsächlich arbeiten, aber Sie müssen hier vorsichtig sein!
Viele Leute sagen (und ich sagte), dass Aufruf GC.Collect () hilft nicht. Aber der Grund, warum es nicht helfen würde, wenn es immer noch Verweise auf COM-Objekte sind! Eines der beliebtesten Gründe für GC.Collect () nicht hilfreich sein wird, um das Projekt in Debug-Modus ausgeführt wird. Im Debug-Modus Objekten, die nicht mehr wirklich referenziert werden, wird nicht Müll bis zum Ende des Verfahrens gesammelt werden.
Also, wenn Sie GC.Collect versucht () und GC.WaitForPendingFinalizers () und es hat nicht geholfen, Versuchen Sie Folgendes zu tun:

1) Versuchen Sie Ihr Projekt im Release-Modus laufen zu lassen und prüfen, ob Excel korrekt geschlossen

2) Wickeln Sie das Verfahren in einem separaten Verfahren mit Excel arbeiten. Anstatt also so etwas wie folgt aus:

void GenerateWorkbook(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

Sie schreiben:

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)
    ...
  }
}

Nun wird Excel schließen =)

UPDATE : Added C # -Code und Link zu Windows-Jobs

Ich verbrachte einige Zeit, dieses Problem zu versuchen, herauszufinden, und zu der Zeit XtremeVBTalk war die aktivste und reaktionsschnell. Hier ist ein Link zu meinem ursprünglichen Beitrag, Schließen einer Excel Interop Prozess sauber, auch wenn Ihr Anwendung abstürzt . Im Folgenden finden Sie eine Zusammenfassung der Post, und die zu diesem Beitrag kopierten Code.

  • den Interop Prozess Closing mit Application.Quit() und Process.Kill() arbeitet zum größten Teil, aber nicht, wenn die Anwendungen katastrophal abstürzt. D. h Wenn die App abstürzt, wird nach wie vor der Excel-Prozess lose ausgeführt werden.
  • Die Lösung ist das Betriebssystem behandelt die Bereinigung Ihrer Prozesse durch Windows-Job Objekte mit Win32-Aufrufe. Wenn die Hauptanwendung stirbt, werden die damit verbundenen Prozesse (das heißt Excel) und beendet erhalten.

Ich fand das eine saubere Lösung zu sein, weil das Betriebssystem für die Säuberung eigentliche Arbeit tut. Alles, was Sie tun müssen, ist registrieren der Excel-Prozess.

Windows-Job-Code

Wraps die Win32-API-Aufrufe Interop Prozesse registrieren.

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

Hinweis über Constructor Code

  • Im Konstruktor wird die info.LimitFlags = 0x2000; genannt. 0x2000 ist der JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE ENUM-Wert, und dieser Wert wird durch MSDN wie:
  

Verursacht alle Prozesse mit dem Job verbunden zu beenden, wenn die   letzte Handle für den Job ist geschlossen.

Zusätzlicher Win32 API-Aufruf die Prozess-ID (PID)

bekommen
    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Mit dem Code

    Excel.Application app = new Excel.ApplicationClass();
    Job job = new Job();
    uint pid = 0;
    Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
    job.AddProcess(Process.GetProcessById((int)pid).Handle);

Das funktionierte für ein Projekt, das ich arbeite an:

excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;

Wir haben gelernt, dass es wichtig sei, auf alle Verweis auf ein Excel-COM-Objekt auf null, wenn Sie wurden mit ihm getan. Dazu gehörten Zellen, Schoten, und alles.

Alles, was in dem Excel-Namensraum freigegeben werden muss, ist. Zeitraum

Sie können nicht tun:

Worksheet ws = excel.WorkBooks[1].WorkSheets[1];

Sie haben zu tun,

Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];

, gefolgt von den Objekten der Freigabe.

First - Sie nie haben Marshal.ReleaseComObject(...) oder Marshal.FinalReleaseComObject(...) zu rufen, wenn Excel-Interop tun. Es ist ein verwirrendes anti-Muster, aber keine Informationen über diese, auch von Microsoft, dass Sie Referenzen manuell müssen loslassen COM gibt an, von .NET falsch ist. Tatsache ist, dass die .NET-Laufzeit und Garbage Collector korrekt verfolgen, und COM-Verweise aufzuräumen. Für den Code, dann können Sie die ganze entfernen `while (...) Schleife an der Spitze.

Zweitens, wenn Sie wollen sicherstellen, dass der COM-Verweis auf ein Out-of-Process-COM-Objekt bereinigt werden, wenn der Prozess endet (so dass der Excel-Prozess wird geschlossen), benötigen Sie, dass der Garbage Collector läuft zu gewährleisten. Sie tun dies mit Anrufen richtig GC.Collect() und GC.WaitForPendingFinalizers(). Aufruf dieser zweimal sicher und sorgt dafür, dass Zyklen definitiv zu aufgeräumt (obwohl ich nicht sicher bin, es gebraucht wird, und würde ein Beispiel zu schätzen wissen, dass dies zeigt).

Drittens, wenn unter dem Debugger ausgeführt wird, werden lokale Referenzen künstlich bis zum Ende des Verfahrens am Leben gehalten werden (so dass lokale Variable Inspektionsarbeiten). So sind GC.Collect() Anrufe für nicht wirksam Objekt wie rng.Cells aus dem gleichen Verfahren gereinigt werden. Sie sollten den Code teilen Sie das COM-Interop aus der GC-Bereinigung in separate Methoden zu tun. (Dies war eine entscheidende Entdeckung für mich, von einem Teil der Antwort hier gepostet von @nightcoder.)

Das allgemeine Muster wäre also:

Sub WrapperThatCleansUp()

    ' NOTE: Don't call Excel objects in here... 
    '       Debugger would keep alive until end, preventing GC cleanup

    ' Call a separate function that talks to Excel
    DoTheWork()

    ' Now let the GC clean up (twice, to clean up cycles too)
    GC.Collect()    
    GC.WaitForPendingFinalizers()
    GC.Collect()    
    GC.WaitForPendingFinalizers()

End Sub

Sub DoTheWork()
    Dim app As New Microsoft.Office.Interop.Excel.Application
    Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
    Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
    app.Visible = True
    For i As Integer = 1 To 10
        worksheet.Cells.Range("A" & i).Value = "Hello"
    Next
    book.Save()
    book.Close()
    app.Quit()

    ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub

Es gibt eine Menge falscher Informationen und Verwirrung über dieses Thema, darunter auch viele Beiträge über MSDN und auf Stack-Überlauf (und vor allem dieser Frage!).

Was mich schließlich überzeugt, einen genaueren Blick zu haben und die richtige Beratung war Blogeintrag herauszufinden

ein gefundenes nützliche allgemeine Vorlage, die Umsetzung der für COM-Objekte korrekte Entsorgung Muster helfen können, die brauchen Marshal.ReleaseComObject aufgerufen, wenn sie sich außerhalb des Geltungsbereichs gehen:

Verwendung:

using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
    try
    {
        using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
        {
           // do something with your workbook....
        }
    }
    finally
    {
         excelApplicationWrapper.ComObject.Quit();
    } 
}

Vorlage:

public class AutoReleaseComObject<T> : IDisposable
{
    private T m_comObject;
    private bool m_armed = true;
    private bool m_disposed = false;

    public AutoReleaseComObject(T comObject)
    {
        Debug.Assert(comObject != null);
        m_comObject = comObject;
    }

#if DEBUG
    ~AutoReleaseComObject()
    {
        // We should have been disposed using Dispose().
        Debug.WriteLine("Finalize being called, should have been disposed");

        if (this.ComObject != null)
        {
            Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
        }

        //Debug.Assert(false);
    }
#endif

    public T ComObject
    {
        get
        {
            Debug.Assert(!m_disposed);
            return m_comObject;
        }
    }

    private string ComObjectName
    {
        get
        {
            if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
            {
                return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
            }

            return null;
        }
    }

    public void Disarm()
    {
        Debug.Assert(!m_disposed);
        m_armed = false;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (m_armed)
            {
                int refcnt = 0;
                do
                {
                    refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
                } while (refcnt > 0);

                m_comObject = default(T);
            }

            m_disposed = true;
        }
    }
}

Hinweis:

http: //www.deez .info / sengelha / 2005/02/11 / nützlich-IDisposable-Klasse-3-autoreleasecomobject /

Ich kann nicht glauben, dass dieses Problem die Welt seit 5 Jahren verfolgt hat .... Wenn Sie eine Anwendung erstellt haben, müssen Sie es zuerst herunterzufahren, bevor Sie den Link zu entfernen.

objExcel = new Excel.Application();  
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); 

Beim Schließen

objBook.Close(true, Type.Missing, Type.Missing); 
objExcel.Application.Quit();
objExcel.Quit(); 

Wenn Sie eine Excel-Anwendung neu, öffnet sich ein Excel-Programm im Hintergrund. Sie müssen befehlen, dass Excel-Programm zu beenden, bevor Sie den Link freigeben, weil das Programm Excel ist nicht Teil Ihrer direkten Kontrolle. Daher wird es offen bleiben, wenn die Verbindung freigegeben wird!

Gute Programmierung jeden ~~

Gemeinsame Entwickler, keine Ihrer Lösungen für mich gearbeitet, so beschließe ich, einen neuen implementieren Trick .

Lassen Sie uns zunächst angeben „Was ist unser Ziel?“ => "Nicht zu sehen Excel-Objekt, nachdem unsere Aufgabe im Task-Manager"

Ok. Lassen Sie nicht fordern und beginnen, es zu zerstören, aber bedenken Sie nicht andere Instanz os Excel zu zerstören, die parallel ausgeführt werden.

So

, erhalten die Liste der aktuellen Prozessoren und holt PID von EXCEL Prozessen, dann, wenn Ihre Arbeit erledigt ist, haben wir einen neuen Gast in Prozessliste mit einem eindeutigen PID haben, finden und zerstört nur, dass ein.

 

Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL")
       excelPID.Add(p.Id);

.... // your job 

prs = Process.GetProcesses();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
       p.Kill();

Das löst mein Problem, hoffen, dass Ihr auch.

Dies scheint sicher, wie es zu kompliziert gewesen. Aus meiner Erfahrung gibt es nur drei wichtige Dinge zu Excel erhalten ordnungsgemäß zu schließen:

1: Stellen Sie sicher, dass es keine verbleibenden Verweise auf die Excel-Anwendung sind Sie erstellt (Sie sollte man sowieso nur haben, setzen Sie ihn auf null)

2: rufen GC.Collect()

3: Excel muss geschlossen werden, entweder manuell durch den Benutzer das Programm zu schließen, oder von Ihnen Quit auf dem Excel-Objekt aufrufen. (Beachten Sie, dass Quit funktioniert genauso, wenn der Benutzer versucht, das Programm zu schließen, und wird ein Bestätigungsdialog präsentieren, wenn es nicht gespeicherte Änderungen, auch wenn Excel nicht sichtbar ist. Der Benutzer könnte drücken abzubrechen, und dann wird Excel wurden nicht geschlossen ).

1 muss geschehen, bevor 2, aber 3 kann jederzeit passieren.

Eine Möglichkeit, dies zu implementieren, ist das Interop-Excel-Objekt mit dem eigenen Klasse zu wickeln, die Interop-Instanz im Konstruktor erstellen und implementieren IDisposable mit Entsorgen Sie sucht etwas wie

if (!mDisposed) {
   mExcel = null;
   GC.Collect();
   mDisposed = true;
}

Das wird aufzuräumen Excel aus Ihrem Programm der Seite der Dinge. Sobald Excel geschlossen (manuell vom Benutzer oder von Ihnen Quit Aufruf) wird der Prozess fortgehen. Wenn das Programm bereits geschlossen worden ist, dann wird der Prozess auf dem GC.Collect() Aufruf verschwinden.

(Ich bin nicht sicher, wie wichtig es ist, aber Sie können einen GC.WaitForPendingFinalizers() Anruf nach dem GC.Collect() Anruf wollen, aber es ist nicht unbedingt notwendig, den Excel-Prozesses loszuwerden.)

Das ist für mich ohne Frage seit Jahren gearbeitet. Beachten Sie aber, dass, während das funktioniert, Sie tatsächlich schließen müssen ordnungsgemäß für sie zu arbeiten. Sie werden immer noch excel.exe Prozesse erhalten akkumulieren, wenn Sie Ihr Programm unterbrechen, bevor Excel (in der Regel durch Schlagen „Stop“, während Ihr Programm gedebuggt wird) gereinigt wird.

Um zu Gründe hinzufügen, warum Excel nicht schließt, auch wenn Sie direkt refrences jedes Objekt über Lese, Schöpfung erstellen, ist die ‚für‘ Schleife.

For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
   objWorkBook.Close 'or whatever
   FinalReleaseComObject(objWorkBook)
   objWorkBook = Nothing
Next 

'The above does not work, and this is the workaround:

For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
   Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
   objTempWorkBook.Saved = True
   objTempWorkBook.Close(False, Type.Missing, Type.Missing)
   FinalReleaseComObject(objTempWorkBook)
   objTempWorkBook = Nothing
Next

Ich habe folgte traditionell die gefundene Beratung in VVS Antwort . Doch in dem Bemühen, diese Antwort zu halten up-to-date mit den neuesten Möglichkeiten, denke ich, alle meine zukünftigen Projekte werden die „NetOffice“ Bibliothek verwenden.

NetOffice ist ein vollständiger Ersatz für den Office-PIAs und ist völlig versionsunabhängig. Es ist eine Sammlung von Managed COM Wrapper, die die Bereinigung verarbeiten können, die oft solche Kopfschmerzen verursachen, wenn mit Microsoft Office in .NET arbeiten.

Einige der wichtigsten Merkmale sind:

  • Meistens versionsunabhängige (und versionsabhängige Funktionen sind dokumentiert)
  • Keine Abhängigkeiten
  • Keine PIA
  • Keine Anmeldung
  • Keine VSTO

Ich bin in keiner Weise mit dem Projekt verbundenen; Ich schätze nur wirklich den krassen Rückgang der Kopfschmerzen.

Die akzeptierte Antwort ist richtig, aber auch zur Kenntnis nehmen, dass nicht nur „zwei Punkt“ Referenzen vermieden werden müssen, sondern auch Objekte, die über den Index abgerufen werden. Sie müssen nicht auch warten müssen, bis Sie mit dem Programm zu bereinigen diese Objekte fertig sind, ist es am besten Funktionen erstellen, die sie so bald reinigen, wie Sie mit ihnen fertig sind, wenn möglich. Hier wird eine Funktion erstellt ich, dass einige Eigenschaften eines Style-Objekt namens xlStyleHeader zuordnet:

public Excel.Style xlStyleHeader = null;

private void CreateHeaderStyle()
{
    Excel.Styles xlStyles = null;
    Excel.Font xlFont = null;
    Excel.Interior xlInterior = null;
    Excel.Borders xlBorders = null;
    Excel.Border xlBorderBottom = null;

    try
    {
        xlStyles = xlWorkbook.Styles;
        xlStyleHeader = xlStyles.Add("Header", Type.Missing);

        // Text Format
        xlStyleHeader.NumberFormat = "@";

        // Bold
        xlFont = xlStyleHeader.Font;
        xlFont.Bold = true;

        // Light Gray Cell Color
        xlInterior = xlStyleHeader.Interior;
        xlInterior.Color = 12632256;

        // Medium Bottom border
        xlBorders = xlStyleHeader.Borders;
        xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom];
        xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        Release(xlBorderBottom);
        Release(xlBorders);
        Release(xlInterior);
        Release(xlFont);
        Release(xlStyles);
    }
}

private void Release(object obj)
{
    // Errors are ignored per Microsoft's suggestion for this type of function:
    // http://support.microsoft.com/default.aspx/kb/317109
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
    }
    catch { } 
}

Beachten Sie, dass ich xlBorders[Excel.XlBordersIndex.xlEdgeBottom] auf eine Variable zu setzen, um das zu bereinigen (Nicht wegen der zwei Punkte, die zu einer Aufzählung beziehen, die muss nicht freigegeben werden, sondern weil das Objekt ich bin Bezug ist eigentlich ein Border-Objekt, das freigegeben) werden muss.

So etwas ist nicht wirklich notwendig, in Standardanwendungen, die nach selbst aufzuräumen einen guten Job machen, aber in ASP.NET-Anwendungen, wenn Sie auch nur eine dieser verpassen, egal, wie oft der Garbage Collector aufrufen Excel wird nach wie vor auf dem Server ausgeführt werden.

Es erfordert viel Liebe zum Detail und viele Testausführungen, während der Task-Manager überwachen, wenn Sie diesen Code zu schreiben, aber die verzweifelt auf der Suche durch die Seiten des Codes so erspart Ihnen die Mühe machen, die eine Instanz Sie verpaßt zu finden. Dies ist besonders wichtig, wenn in Schleifen arbeiten, wo Sie jede Instanz eines Objekt lösen müssen, auch wenn sie die gleichen Variablennamen jedes Mal verwendet es Schleifen.

Nach dem Versuch,

  1. Veröffentlichung COM-Objekte in umgekehrter Reihenfolge
  2. Fügen Sie GC.Collect() und GC.WaitForPendingFinalizers() zweimal am Ende
  3. nicht mehr als zwei Punkte
  4. Schließen Arbeitsmappe und beenden Anwendung
  5. Ausführen im Release-Modus

die endgültige Lösung, die für mich funktioniert, ist ein Satz von

bewegen
GC.Collect();
GC.WaitForPendingFinalizers();

, dass wir an das Ende der Funktion zu einem Wrapper wie folgt:

private void FunctionWrapper(string sourcePath, string targetPath)
{
    try
    {
        FunctionThatCallsExcel(sourcePath, targetPath);
    }
    finally
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

Ich folgte genau ... Aber ich lief immer noch in Fragen 1 von 1000 mal. Wer weiß, warum. Zeit zu bringen, um den Hammer ...

Gleich nach der Excel Application-Klasse instanziiert ich einen Einfluß des Excel-Prozesses, der gerade erstellt wurde.

excel = new Microsoft.Office.Interop.Excel.Application();
var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First();

Dann, wenn ich clean-up alle über COM getan habe, stelle ich sicher, dass der Prozess nicht ausgeführt wird. Wenn es noch läuft, es töten!

if (!process.HasExited)
   process.Kill();

¨ ° º¤ø „¸ schießen Excel proc und kauen Kaugummi ¸„ø¤º ° ¨

public class MyExcelInteropClass
{
    Excel.Application xlApp;
    Excel.Workbook xlBook;

    public void dothingswithExcel() 
    {
        try { /* Do stuff manipulating cells sheets and workbooks ... */ }
        catch {}
        finally {KillExcelProcess(xlApp);}
    }

    static void KillExcelProcess(Excel.Application xlApp)
    {
        if (xlApp != null)
        {
            int excelProcessId = 0;
            GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
            Process p = Process.GetProcessById(excelProcessId);
            p.Kill();
            xlApp = null;
        }
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}

Sie müssen sich bewusst sein, dass Excel auf die Kultur ist sehr empfindlich Sie unter als auch ausgeführt werden.

Sie können feststellen, dass Sie vor dem Aufruf von Excel-Funktionen, die Kultur zu EN-US einstellen müssen. Dies gilt nicht für alle Funktionen -. Aber einige von ihnen

    CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); 
    System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
    string filePathLocal = _applicationObject.ActiveWorkbook.Path;
    System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;

Dies gilt auch dann, wenn Sie VSTO verwenden.

Weitere Informationen: http://support.microsoft.com /default.aspx?scid=kb;en-us;Q320369

„Niemals zwei Punkte mit COM-Objekten verwendet“ ist eine große Faustregel Leckage von COM-Verweise zu vermeiden, aber Excel PIA Leckage auf dem ersten Blick in vielerlei Hinsicht offensichtlich führen kann.

Eine dieser Möglichkeiten ist in jedem Fall durch eine der Excel-Objektmodell der COM-Objekte ausgesetzt abonnieren möchte.

Zum Beispiel Zeichnung der Workbook Veranstaltung Anwendungsklasse.

Einige Theorie auf COM Ereignisse

COM-Klassen eine Gruppe von Ereignissen durch Call-Back-Schnittstellen aus. Um Ereignisse zu abonnieren, kann der Client-Code einfach ein Objekt der Umsetzung der Call-Back-Schnittstelle registrieren und die COM-Klasse wird ihre Methoden in Reaktion auf bestimmte Ereignisse aufrufen. Da die Rückrufschnittstelle eine COM-Schnittstelle ist, ist es die Aufgabe der Durchführung der Aufgabe der Referenzzähler jeden COM-Objekts dekrementiert empfängt er (als Parameter) für einen der Event-Handler.

Wie Excel PIA COM Ereignisse aussetzen

Excel PIA macht COM Ereignisse von Excel Application-Klasse als herkömmliche .NET Ereignisse. Jedes Mal, wenn der Client-Code abonniert hat .NET-Ereignis (Betonung auf 'a'), PIA erstellt eine Instanz einer Klasse, den Call-Back-Schnittstelle implementiert und registriert sie mit Excel .

Damit eine Reihe von Call-Back-Objekten erhält mit Excel in Reaktion auf verschiedene Abonnement-Anfragen aus dem .NET-Code registriert. Ein Call-Back-Objekt pro Ereignis zu erhalten.

Eine Call-Back-Schnittstelle für Ereignisbehandlung bedeutet, dass PIA für jede .NET-Ereignisabonnement Anfrage an alle Schnittstellen-Ereignisse abonnieren hat. Es kann nicht wählen, und wählen. ein Ereignis Rückruf, Objekt prüft der Rückruf, wenn der zugehörige .NET Ereignishandler interessiert ich für das aktuelle Ereignis oder nicht, und dann entweder ruft den Handler oder ignoriert leise den Rückruf Beim Empfang.

Auswirkung auf die COM-Instanz Referenzzähler

All diese Rückrufe Objekte verringern nicht den Referenzzähler von einem der COM-Objekten, die sie (als Parameter) für eine des Call-Back-Verfahren (auch für diejenigen, die stillschweigend ignoriert werden) erhalten. Sie verlassen sich ausschließlich auf die CLR Garbage Collector die COM-Objekte freizugeben.

Da GC Lauf nicht-deterministisch ist, kann dies ab als erwünscht für eine längere Dauer von Excel-Prozess zum Halt führen und den Eindruck eines ‚Memory Leak‘ erstellen.

Lösung

Die einzige Lösung ab sofort ist die PIA-Ereignisanbieter für die COM-Klasse zu vermeiden und Ihre eigenen Ereignisanbieter schreiben, die COM-Objekte determinisfreigibt.

Für die Anwendungsklasse, kann dies durch die Umsetzung der AppEvents Schnittstelle erfolgen und dann die Umsetzung mit Excel Registrierung unter Verwendung von IConnectionPointContainer Schnittstelle . Die Application-Klasse (und was das betrifft alle COM-Objekte Belichtungs Ereignisse Callback-Mechanismus verwenden) implementiert die Schnittstelle IConnectionPointContainer.

Wie andere haben darauf hingewiesen, benötigen Sie einen ausdrücklichen Hinweis für alle Excel erstellen Objekt, das Sie verwenden, und rufen Sie Marshal.ReleaseComObject auf dieser Referenz, wie in dieser KB-Artikel . Sie müssen auch verwenden try / finally ReleaseComObject wird immer dann aufgerufen, auch wenn eine Ausnahme ausgelöst, um sicherzustellen. D. h Statt:

Worksheet sheet = excelApp.Worksheets(1)
... do something with sheet

Sie tun müssen, um so etwas wie:

Worksheets sheets = null;
Worksheet sheet = null
try
{ 
    sheets = excelApp.Worksheets;
    sheet = sheets(1);
    ...
}
finally
{
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (sheet != null) Marshal.ReleaseComObject(sheet);
}

Sie müssen auch Application.Quit aufrufen, bevor das Anwendungsobjekt die Freigabe, wenn Sie Excel schließen möchten.

Wie Sie sehen können, wird dies schnell extrem unhandlich, sobald Sie versuchen, etwas zu tun, auch nur einigermaßen komplex. Ich habe erfolgreich .NET-Anwendungen mit einem einfachen Wrapper-Klasse entwickelt, die ein paar einfache Manipulationen des Excel-Objektmodell Wraps (eine Arbeitsmappe öffnen, schreiben Sie eine Range, speichern / schließen Sie die Arbeitsmappe usw.). Die Wrapper-Klasse implementiert IDisposable, sorgfältig implementiert Marshal.ReleaseComObject auf jedem Objekt, das es verwendet, und aussetzt nicht pubicly keine Excel-Objekte mit dem Rest der App.

Aber dieser Ansatz skaliert nicht gut für komplexere Anforderungen.

Dies ist ein großer Mangel an .NET COM-Interop. Für komplexere Szenarien würde ich ernsthaft in Erwägung ziehen, einen ActiveX-DLL in VB6 oder andere nicht verwalteten Sprache zu schreiben, auf der Sie alle Interaktionen mit Out-proc COM-Objekten wie Office delegieren. Anschließend können Sie den ActiveX-DLL aus dem .NET-Anwendung verweisen, und die Dinge werden viel einfacher, als Sie nur diese eine Referenz lösen müssen.

Wenn alle oben das Zeug nicht funktioniert, versuchen Sie Excel einige Zeit, um seine Blätter zu schließen:

app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();

...
FinalReleaseComObject(app);

Stellen Sie sicher, dass Sie alle Objekte freigeben im Zusammenhang mit Excel!

verbrachte ich ein paar Stunden durch verschiedene Wege versucht. Alle großen Ideen sind, aber ich endlich mein Fehler: Wenn Sie nicht alle Objekte freigeben, keine der oben genannten Möglichkeiten können Sie helfen, wie in meinem Fall. Stellen Sie sicher, lassen Sie alle Objekte, einschließlich Bereich ein!

Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);

Die Optionen sind zusammen hier .

Die zwei Punkte Regel funktioniert nicht für mich. In meinem Fall habe ich eine Methode meine Ressourcen zu reinigen wie folgt:

private static void Clean()
{
    workBook.Close();
    Marshall.ReleaseComObject(workBook);
    excel.Quit();
    CG.Collect();
    CG.WaitForPendingFinalizers();
}
mit Word / Excel-Interop-Anwendungen

Sie sollten sehr vorsichtig sein. alle Lösungen nach dem Versuch, wir hatten immer noch eine Menge „Winword“ Prozess auf dem Server offen gelassen (mit mehr als 2000 Benutzer).

auf das Problem für Stunden Nach der Arbeit, erkannte ich, dass, wenn ich mehr als ein paar Dokumente öffnen mit Word.ApplicationClass.Document.Open() auf verschiedenen Threads gleichzeitig, IIS-Arbeitsprozess (w3wp.exe) abstürzen würde alle WinWord Prozesse offen lassen!

Also ich denke, es gibt keine absolute Lösung für dieses Problem, aber zu anderen Methoden Schalen wie Office Open XML Entwicklung.

Ein großer Artikel über die Freigabe von COM-Objekten ist 2.5 Releasing COM-Objekte (MSDN).

Die Methode, die ich befürworte würde, ist Ihre Excel.Interop Verweise auf null, wenn sie nicht-lokale Variablen sind, und rufen Sie dann GC.Collect() und GC.WaitForPendingFinalizers() zweimal. Lokal gültiges Interop Variablen automatisch gepflegt werden.

Dies beseitigt die Notwendigkeit, eine benannte Referenz zu halten für alle COM-Objekt.

Hier ist ein Beispiel aus dem Artikel entnommen:

public class Test {

    // These instance variables must be nulled or Excel will not quit
    private Excel.Application xl;
    private Excel.Workbook book;

    public void DoSomething()
    {
        xl = new Excel.Application();
        xl.Visible = true;
        book = xl.Workbooks.Add(Type.Missing);

        // These variables are locally scoped, so we need not worry about them.
        // Notice I don't care about using two dots.
        Excel.Range rng = book.Worksheets[1].UsedRange;
    }

    public void CleanUp()
    {
        book = null;
        xl.Quit();
        xl = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

Diese Worte sind gerade aus dem Artikel:

  

In fast allen Situationen, nulling die RCW Referenz und zwingt eine Garbage Collection richtig aufräumen wird. Wenn Sie auch GC.WaitForPendingFinalizers nennen, Garbage Collection wird als deterministisch wie Sie es machen können. Das heißt, werden Sie ziemlich sicher sein, genau dann, wenn das Objekt Aufwärts auf der Rückkehr aus dem zweiten Aufruf von WaitForPendingFinalizers gereinigt wurde. Als Alternative können Sie Marshal.ReleaseComObject verwenden. Beachten Sie jedoch, dass Sie sehr unwahrscheinlich, jemals diese Methode verwenden müssen.

Meine Lösung

[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

private void GenerateExcel()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Excel Process Id (ath the end, you kill him
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    excel.Quit();

    // Kill him !
    excelProcess.Kill();
}

Die akzeptierte Antwort nicht für mich arbeiten. Der folgende Code in der destructor hat seinen Zweck erfüllt.

if (xlApp != null)
{
    xlApp.Workbooks.Close();
    xlApp.Quit();
}

System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL");
foreach (System.Diagnostics.Process process in processArray)
{
    if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}

Zur Zeit arbeite ich an Büroautomation und haben über eine Lösung für dieses Problem gestolpert, die jedes Mal für mich funktioniert. Es ist einfach und beinhaltet keine Prozesse zu töten.

Es scheint, dass durch lediglich durch die aktuell aktiven Prozesse Looping und in irgendeiner Weise ‚Zugriff‘ einen offenen Excel-Prozess, wird jede Streu hängen Instanz von Excel entfernt werden. Der Code unten überprüft einfach für Prozesse, bei denen der Name ‚Excel‘ ist, dann schreibt die Eigenschaft Mainwindowtitle des Prozesses in einen String. Diese ‚Interaktion‘ mit dem Prozess scheint Windows-Aufholjagd und abbrechen die gefrorene Instanz von Excel zu machen.

Ich betreibe die folgenden Verfahren kurz vor dem Add-in denen ich quitt entwickle, wie es sie Entladen Ereignis ausgelöst. Es entfernt alle hängenden Instanzen von Excel jedes Mal. In aller Ehrlichkeit bin ich nicht ganz sicher, warum dies funktioniert, aber es funktioniert gut für mich und konnte, ohne sich Sorgen zu machen über Doppel Punkte, Marshal.ReleaseComObject, noch töten Prozesse am Ende jeder Excel-Anwendung platziert werden. Ich würde in allen möglichen Vorschlägen sehr interessiert sein, warum dies wirksam ist.

public static void SweepExcelProcesses()
{           
            if (Process.GetProcessesByName("EXCEL").Length != 0)
            {
                Process[] processes = Process.GetProcesses();
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToString() == "excel")
                    {                           
                        string title = process.MainWindowTitle;
                    }
                }
            }
}

Ich denke, dass einige das ist nur die Art und Weise, dass der Rahmen Office-Anwendungen behandelt, aber ich könnte falsch sein. An manchen Tagen reinigen einige Anwendungen, die Prozesse sofort auf, und an anderen Tagen scheint es, zu warten, bis die Anwendung geschlossen wird. Im Allgemeinen gab ich die Aufmerksamkeit auf die Details zu bezahlen und nur dafür sorgen, dass es keine zusätzliche Prozesse sind im Umlauf am Ende des Tages.

Auch und vielleicht bin ich über die Dinge zu vereinfachen, aber ich denke, man kann nur ...

objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();

Wie ich bereits sagte, ich nicht dazu neigen, die Aufmerksamkeit auf die Details zu zahlen, wenn der Excel-Prozess oder verschwindet erscheint, aber das funktioniert in der Regel für mich. Ich habe nicht halten auch Prozesse rund um für etwas Excel wie andere sind als die minimale Menge an Zeit, aber ich bin wahrscheinlich nur paranoid, dass auf.

Wie einige haben wohl schon geschrieben, ist es nicht nur wichtig, wie Sie schließen die Excel (Objekt); es ist auch wichtig, wie Sie open es und auch durch die Art des Projektes.

In einer WPF-Anwendung, im Grunde der gleiche Code funktioniert ohne oder mit nur sehr wenigen Problemen.

Ich habe ein Projekt, in dem die gleiche Excel-Datei mehrmals für verschiedenen Parameterwert verarbeitet wird - z.B. Parsen es basierend auf Werten innerhalb einer generischen Liste.

Ich habe alle Excel-bezogene Funktionen in der Basisklasse, und Parser in eine Unterklasse (unterschiedliche Parser verwenden gemeinsame Excel-Funktionen). Ich wollte nicht, dass Excel geöffnet und wieder in einer allgemeinen Liste für jedes Element geschlossen, also habe ich es nur einmal in der Basisklasse geöffnet und es in der Unterklasse schließen. Ich hatte Probleme, wenn Sie den Code in eine Desktop-Anwendung zu bewegen. Ich habe viele der oben genannten Lösungen ausprobiert. GC.Collect() wurde bereits vor ihrer Durchführung, doppelt so vorgeschlagen.

Dann habe ich beschlossen, dass ich den Code zum Öffnen Excel zu einer Unterklasse verschieben. Statt Öffnung nur einmal, jetzt schaffe ich ein neues Objekt (Basisklasse) und öffnen Sie Excel für jedes Einzelteil und schließen Sie es am Ende. Es besteht eine gewisse Leistungseinbuße, sondern auf der Grundlage mehrere Tests Excel Prozesse schließen, ohne Probleme (im Debug-Modus), so auch temporäre Dateien entfernt werden. Ich werde mit der Prüfung fortsetzen und noch mehr schreiben, wenn ich einige Updates erhalten.

Das Endergebnis ist: Sie müssen auch den initialize Code überprüfen, vor allem wenn Sie viele Klassen, etc

.
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top