it-swarm.com.de

Excel.exe kann nach dem Interop-Prozess nicht geschlossen werden

Ich habe ein Problem mit Excel Interop.

Die Excel.exe wird nicht geschlossen, auch wenn ich Instanzen löse.

Hier ist mein Code: 

using xl = Microsoft.Office.Interop.Excel;


xl.Application Excel = new xl.Application();
Excel.Visible = true;
Excel.ScreenUpdating = false;
if (wordFile.Contains(".csv") || wordFile.Contains(".xls"))
{
   //typeExcel become a string of the document name
   string typeExcel = wordFile.ToString();
   xl.Workbook workbook = Excel.Workbooks.Open(typeExcel,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing);
   object outputFileName = null;
   if (wordFile.Contains(".xls"))
   {
     outputFileName = wordFile.Replace(".xls", ".pdf");
   }
   else if (wordFile.Contains(".csv"))
   {
     outputFileName = wordFile.Replace(".csv", ".pdf");
   }

   workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outputFileName, 
                                 XlFixedFormatQuality.xlQualityStandard, oMissing,
                                 oMissing, oMissing, oMissing, oMissing, oMissing);

   object saveChanges = xl.XlSaveAction.xlDoNotSaveChanges;
   ((xl._Workbook)workbook).Close(saveChanges, oMissing, oMissing);

   Marshal.ReleaseComObject(workbook);
   workbook = null;
}

Ich habe gesehen, dass mit dem Marshal.RealeaseComObject es funktionieren sollte, aber nichts . Wie kann ich das beheben?

Vielen Dank.

25

Einfache Regel: Vermeiden Sie doppelte Aufrufaufrufe wie diese:

var workbook = Excel.Workbooks.Open(/*params*/)

... weil Sie auf diese Weise RCW objects nicht nur für workbook erstellen, sondern auch für Workbooks, und Sie sollten sie auch freigeben (was nicht möglich ist, wenn keine Referenz auf das Objekt gepflegt wird).

Der richtige Weg wird also sein:

var workbooks = Excel.Workbooks;
var workbook = workbooks.Open(/*params*/)

//business logic here

Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(Excel);
60

Hier ist ein Codeausschnitt, den ich geschrieben habe, weil ich das gleiche Problem wie Sie hatte. Grundsätzlich müssen Sie die Arbeitsmappe schließen, die Anwendung beenden und dann ALLE COM-Objekte freigeben (nicht nur das Excel-Anwendungsobjekt). Rufen Sie schließlich den Garbage Collector für ein gutes Maß an.

    /// <summary>
    /// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
    /// </summary>
    public void Dispose()
    {
        // Cleanup
        xWorkbook.Close(false);
        xApp.Quit();

        // Manual disposal because of COM
        while (Marshal.ReleaseComObject(xApp) != 0) { }
        while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
        while (Marshal.ReleaseComObject(xCharts) != 0) { }
        while (Marshal.ReleaseComObject(xMyChart) != 0) { }
        while (Marshal.ReleaseComObject(xGraph) != 0) { }
        while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
        while (Marshal.ReleaseComObject(xSeries) != 0) { }
        xApp = null;
        xWorkbook = null;
        xWorksheets = null;
        xWorksheet = null;
        xCharts = null;
        xMyChart = null;
        xGraph = null;
        xSeriesColl = null;
        xSeries = null;

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

Regeln - verwenden Sie niemals mehr als einen Punkt 

-- ein Punkt

var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);

- Zwei oder mehr Punkte

 (Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);

- Beispiel

 using Microsoft.Office.Interop.Excel;

 Application xls = null;
 Workbooks workBooks = null;
 Workbook workBook = null;
 Sheets sheets = null;
 Worksheet workSheet1 = null;
 Worksheet workSheet2 = null;

 workBooks = xls.Workbooks;
 workBook = workBooks.Open(workSpaceFile);
 sheets = workBook.Worksheets;
 workSheet1 = (Worksheet)sheets[1];


// removing from Memory
 if (xls != null)
 {    
   foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
   {
      ReleaseObject(sheet);
   }

   ReleaseObject(sheets);
   workBook.Close();
   ReleaseObject(workBook);
   ReleaseObject(workBooks);

   xls.Application.Quit(); // THIS IS WHAT IS CAUSES Excel TO CLOSE
   xls.Quit();
   ReleaseObject(xls);

   sheets = null;
   workBook = null;
   workBooks = null;
   xls = null;

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

In Ihrem Code haben Sie:

Excel.Workbooks.Open(...)

Excel.Workbooks erstellt ein COM-Objekt. Sie rufen dann die Open-Funktion von diesem COM-Objekt auf. Sie geben das COM-Objekt jedoch nicht frei, wenn Sie fertig sind.

Dies ist ein häufiges Problem beim Umgang mit COM-Objekten. Grundsätzlich sollten Sie niemals mehr als einen Punkt in Ihrem Ausdruck haben, da Sie die COM-Objekte bereinigen müssen, wenn Sie fertig sind.

Das Thema ist einfach zu groß, um es in einer Antwort vollständig zu erforschen, aber ich denke, Sie finden Jake Ginnivans Artikel zu diesem Thema äußerst hilfreich: VSTO und COM Interop

Wenn Sie die Aufrufe von ReleaseComObject satt haben, kann diese Frage hilfreich sein:
So reinigen Sie das Excel-Interop-Objekt in C #, Ausgabe 2012 richtig

7
JDB

Es ist schwierig, alle Referenzen loszuwerden, da Sie raten müssen, ob Aufrufe wie:

var workbook = Excel.Workbooks.Open("")

Erstellt eine Instanz von Workbooks, auf die Sie keinen Verweis haben.

Auch Referenzen wie:

targetRange.Columns.AutoFit()

Erstellt eine Instanz von .Columns(), ohne dass Sie es wissen und nicht ordnungsgemäß freigegeben werden.

Am Ende schrieb ich eine Klasse mit einer Liste von Objektreferenzen, die alle Objekte in umgekehrter Reihenfolge anordnen konnten.

Die Klasse verfügt über eine Liste von Objekten und Add()-Funktionen für alles, auf das Sie verweisen, wenn Sie Excel-Interop verwenden, das das Objekt selbst zurückgibt: 

    public List<Object> _interopObjectList = new List<Object>();

    public Excel.Application add(Excel.Application obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Range add(Excel.Range obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Workbook add(Excel.Workbook obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Worksheet add(Excel.Worksheet obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Worksheets add(Excel.Worksheets obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

    public Excel.Sheets add(Excel.Sheets obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }


    public Excel.Workbooks add(Excel.Workbooks obj)
    {
        _interopObjectList.Add(obj);
        return obj;
    }

Um die Registrierung von Objekten aufzuheben, habe ich folgenden Code verwendet:

    //Release all registered interop objects in reverse order
    public void unregister()
    {
        //Loop object list in reverse order and release Office object
        for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
        { ReleaseComObject(_interopObjectList[i]); }

        //Clear object list
        _interopObjectList.Clear();
    }


    /// <summary>
    /// Release a com interop object 
    /// </summary>
    /// <param name="obj"></param>
     public static void ReleaseComObject(object obj)
     {
         if (obj != null && InteropServices.Marshal.IsComObject(obj))
             try
             {
                 InteropServices.Marshal.FinalReleaseComObject(obj);
             }
             catch { }
             finally
             {
                 obj = null;
             }

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

Dann sollten Sie die Klasse erstellen und Referenzen wie folgt erfassen:

//Create helper class
xlsHandler xlObj = new xlsHandler();

..

//Sample - Capture reference to Excel application
Excel.Application _excelApp = xlObj.add(new Excel.Application());

..
//Sample - Call .Autofit() on a cell range and capture reference to .Columns() 
xlObj.add(_targetCell.Columns).AutoFit();

..

//Release all objects collected by helper class
xlObj.unregister();

Vielleicht kein Code von großer Schönheit, aber zu etwas Nützlichem inspirieren.

6
flodis

Wie in anderen Antworten angegeben, werden mit two dots ausgeblendete Verweise erstellt, die nicht mit Marshal.FinalReleaseComObject geschlossen werden können. Ich wollte nur meine Lösung mitteilen, wodurch es nicht mehr nötig ist, sich an Marshal.FinalReleaseComObject zu erinnern - es ist wirklich leicht zu übersehen, und es ist schwierig, den Täter zu finden.

Ich verwende eine generische IDisposable-Wrapper-Klasse, die für jedes COM-Objekt verwendet werden kann. Es funktioniert wie ein Zauber und hält alles schön und sauber. Ich kann sogar private Felder wiederverwenden (z. B. this.worksheet). Das Objekt wird auch automatisch freigegeben, wenn aufgrund der Art von IDisposable ein Fehler auftritt (die Dispose-Methode wird als finally ausgeführt).

using Microsoft.Office.Interop.Excel;

public class ExcelService
{
    private _Worksheet worksheet;

    private class ComObject<TType> : IDisposable
    {
        public TType Instance { get; set; }

        public ComObject(TType instance)
        {
            this.Instance = instance;
        }

        public void Dispose()
        {
            System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
        }
    }

    public void CreateExcelFile(string fullFilePath)
    {
        using (var comApplication = new ComObject<Application>(new Application()))
        {
            var excelInstance = comApplication.Instance;
            excelInstance.Visible = false;
            excelInstance.DisplayAlerts = false;

            try
            {
                using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
                using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
                using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
                {
                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "Action";
                        this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                    }

                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "Status";
                        this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                    }

                    using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
                    {
                        this.worksheet = comSheet.Instance;
                        this.worksheet.Name = "ItemPrices";
                        this.worksheet.Activate();

                        using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
                        using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
                        {
                            comRange.Instance.Select();
                            comWindow.Instance.FreezePanes = true;
                        }
                    }

                    if (this.fullFilePath != null)
                    {
                        var currentWorkbook = (workbook.Instance as _Workbook);
                        currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
                        currentWorkbook.Close(false);
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.Message);
                throw;
            }
            finally
            {
                // Close Excel instance
                excelInstance.Quit();
            }
        }
    }
}
2
Heliac

Wenn Sie verzweifelt sind . Verwenden Sie diesen Ansatz nicht, wenn Sie nicht wissen, was er tut:

foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("Excel"))
{
  proc.Kill();
}

Hinweis: Dies beendet jeden Prozess mit dem Namen "Excel".

Ich musste es tun, weil ich jedes einzelne COM-Objekt in meinem Code geschlossen hatte, in dem ich immer noch den störrischen Prozess Excel.exe hatte, der gerade dort hängt. Dies ist natürlich keinesfalls die beste Lösung.

1
Denis Molodtsov

Excel.exe kann nach dem Interop-Prozess nicht geschlossen werden

Mach das nicht zu kompliziert !! Erstellen Sie einfach eine einfache Methode und rufen Sie diese wie folgt auf:

// to kill the EXCELsheet file process from process Bar
private void KillSpecificExcelFileProcess() {
  foreach (Process clsProcess in Process.GetProcesses())
    if (clsProcess.ProcessName.Equals("Excel"))  //Process Excel?
      clsProcess.Kill();
    }
1
Jarus Rev

Alternativ können Sie den Excel-Prozess wie erklärt hier beenden.

Importieren Sie zunächst die Funktion SendMessage:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

Senden Sie dann die WM_CLOSE-Nachricht an das Hauptfenster:

SendMessage((IntPtr)Excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);
1

@Denis Molodtsov in einem Versuch hilfreich zu sein schlug vor, alle Prozesse mit dem Namen 'Excel' zu beenden. Das scheint nach Ärger zu fragen. Es gibt bereits viele Antworten, die Möglichkeiten beschreiben, wie der Prozess nach dem Aufruf von Excel.quit () angehalten werden kann, indem Nice mit COM-Interop gespielt wird. Dies ist am besten, wenn Sie es funktionieren lassen können.

@Kevin Vuilleumier hatte einen großartigen Vorschlag, WM_CLOSE an das Excel-Fenster zu senden. Ich habe vor, das zu testen.

Wenn Sie aus irgendeinem Grund den Excel-Prozess eines Excel-App-Objekts beenden müssen, können Sie ihn gezielt so ansprechen:

  using System.Diagnostics;
  using System.Runtime.InteropServices;

// . . .

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

// . . .

    uint excelAppPid;
    uint tid = GetWindowThreadProcessId(Excel.Hwnd, out excelAppPid);

    if (tid)
    {
      Process excelAppProc = Process.GetProcessById($excelPid)
      if (excelAppProc)
      {
        excelAppProc.Kill()
      }
    }

Ich habe keine Zeit, um in C # vollständig zu testen, aber ich habe in Powershell einen schnellen Test durchgeführt, bei dem ich ein Problem habe, weil Excel nicht beendet wurde und dieser Ansatz funktioniert.

Das ist ziemlich einfach. Die Hwnd-Eigenschaft des Excel-App-Objekts ist das ausgeblendete Fensterhandle des Excel-Prozesses. Übergeben Sie Excel.Hwnd an GetWindowThreadProcessId, um die Prozess-ID abzurufen. Verwenden Sie dies, um den Prozess zu öffnen, und rufen Sie schließlich Kill () auf.

Zumindest sind wir sicher, dass wir den richtigen Prozess beenden. Nun, ziemlich sicher. Wenn der Excel-Prozess bereits normal beendet wurde, kann seine Prozess-ID von einem neuen Prozess wiederverwendet werden. Um diese Möglichkeit einzuschränken, ist es wichtig, nicht zwischen dem Aufruf von Excel.quit () und dem Versuch des Killens zu warten.

0
jimhark

Ich hatte dasselbe Problem, wir können das Problem lösen, ohne zu töten, wir vergessen immer, die Schnittstellen zu schließen, die wir aus der Microsoft.Office.Interop.Excel-Klasse verwendet haben Behalten Sie die Sheets-Schnittstelle in Ihrem Code im Auge. Dies ist der Hauptschuldige. Wir schließen häufig die Anwendung, Arbeitsmappe, Arbeitsmappen, Bereich, Tabellenblatt. Wir vergessen jedoch, dass das Sheets-Objekt oder die verwendete Schnittstelle unwissentlich freigegeben wird.

               Microsoft.Office.Interop.Excel.Application app = null;
        Microsoft.Office.Interop.Excel.Workbooks books = null;
        Workbook book = null;
        Sheets sheets = null;
        Worksheet sheet = null;
        Range range = null;

        try
        {
            app = new Microsoft.Office.Interop.Excel.Application();
            books = app.Workbooks;
            book = books.Add();
            sheets = book.Sheets;
            sheet = sheets.Add();
            range = sheet.Range["A1"];
            range.Value = "Lorem Ipsum";
            book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
            book.Close();
            app.Quit();
        }
        finally
        {
            if (range != null) Marshal.ReleaseComObject(range);
            if (sheet != null) Marshal.ReleaseComObject(sheet);
            if (sheets != null) Marshal.ReleaseComObject(sheets);
            if (book != null) Marshal.ReleaseComObject(book);
            if (books != null) Marshal.ReleaseComObject(books);
            if (app != null) Marshal.ReleaseComObject(app);
        }
0
kki