proceso guardar close cerrar cambios archivo c# winforms excel-interop

c# - guardar - No se puede cerrar Excel.exe después del proceso Interop



close excel vba (9)

Tengo un problema con Excel Interop.

Excel.exe no se cierra incluso si cuando lo hago se libera.

Aquí está mi código:

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; }

Vi que con Marshal.RealeaseComObject debería funcionar, pero nada. ¿Cómo puedo arreglar esto?

Gracias.


Alternativamente, puede matar el proceso de Excel como se explica here .

Primero, importa la función SendMessage :

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

Luego, envíe el mensaje WM_CLOSE a la ventana principal:

SendMessage((IntPtr)excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);


Aquí hay un fragmento de código que escribí porque tenía el mismo problema que tú. Básicamente, debe cerrar el libro de trabajo, salir de la aplicación y luego liberar TODOS sus objetos COM (no solo el objeto Aplicación de Excel). Finalmente, llame al recolector de basura por si acaso.

/// <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(); }


Como se indica en otras respuestas, usar dos puntos creará referencias ocultas que Marshal.FinalReleaseComObject no puede cerrar. Solo quería compartir mi solución, lo que elimina la necesidad de recordar a Marshal.FinalReleaseComObject : es muy fácil pasar por alto y resulta doloroso localizar al culpable.

Uso una clase genérica IDisposable envoltorio que se puede utilizar en cualquier objeto COM. Funciona como un encanto, y mantiene todo limpio y agradable. Incluso puedo reutilizar campos privados (por ejemplo, this.worksheet ). También libera automáticamente el objeto cuando algo arroja un error, debido a la naturaleza de IDisposable (el método Dispose se ejecuta finally ).

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(); } } } }


En caso de que estés desesperado . No use este enfoque a menos que comprenda lo que hace :

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

Tuve que hacerlo porque, aunque he cerrado todos los objetos COM en mi código, todavía tenía el obstinado proceso Excel.exe colgando allí. Esta no es la mejor solución, por supuesto.


En tu código tienes:

excel.Workbooks.Open(...)

excel.Workbooks está creando un objeto COM. A continuación, llama a la función Open desde ese objeto COM. Sin embargo, no está liberando el objeto COM cuando haya terminado.

Este es un problema común al tratar con objetos COM. Básicamente, nunca deberías tener más de un punto en tu expresión porque necesitarás limpiar los objetos COM cuando hayas terminado.

El tema es simplemente demasiado grande para explorarlo completamente en una respuesta, pero creo que encontrará el artículo de Jake Ginnivan sobre el tema extremadamente útil: VSTO y COM Interop

Si te cansas de todas esas llamadas a ReleaseComObject, puedes encontrar esta pregunta útil:
Cómo limpiar correctamente el objeto de interoperabilidad de Excel en C #, edición de 2012


Es complicado deshacerse de todas las referencias ya que hay que adivinar si las llamadas como:

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

Crea una instancia de Workbooks que no tiene una referencia.

Incluso referencias como:

targetRange.Columns.AutoFit()

.Columns() una instancia de .Columns() sin que usted lo sepa y no lo haya lanzado correctamente.

Terminé escribiendo una clase que contenía una lista de referencias de objetos que podían disponer de todos los objetos en orden inverso.

La clase tiene una lista de objetos y funciones de Add() para todo lo que haga referencia a medida que utiliza la interoperabilidad de Excel que devuelve el objeto en sí:

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; }

Luego, para anular el registro de objetos, utilicé el siguiente código:

//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(); }

Entonces el principio es crear la clase y capturar referencias como esta:

//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();

No es quizás un código de gran belleza, pero puede inspirar a algo útil.


Regla simple: evite el uso de expresiones de doble llamada, como esta:

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

... porque de esta manera creas objetos RCW no solo para el workbook de workbook , sino para los Workbooks , y también debes liberarlo (lo cual no es posible si no se mantiene una referencia al objeto).

Entonces, la forma correcta será:

var workbooks = excel.Workbooks; var workbook = workbooks.Open(/*params*/) //business logic here Marshal.ReleaseComObject(workbook); Marshal.ReleaseComObject(workbooks); Marshal.ReleaseComObject(excel);


Reglas - nunca use más de un punto

-- un punto

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

- Dos o más puntos

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

- Ejemplo

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(); }


Tuve el mismo problema, podemos resolver el problema sin ningún tipo de asesinato, siempre nos olvidamos de cerrar las interfaces que hemos utilizado desde la clase Microsoft.Office.Interop.Excel así que aquí está el fragmento de código y seguir la estructura y la forma en que se han borrado los objetos, también eche un vistazo a la interfaz Sheets en su código, este es el principal culpable, a menudo cerramos la aplicación, Workbook, workbooks, range, sheet, pero olvidamos o, sin saberlo, no lanzamos el objeto Sheets o la interfaz utilizada, así que aquí está el código:

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