Interoperabilidad de C #: el proceso de Excel no sale después de agregar una nueva hoja de trabajo al archivo existente [duplicado]

StackOverflow https://stackoverflow.com/questions/350454

  •  20-08-2019
  •  | 
  •  

Pregunta

  

Posible duplicado:
   Cómo limpiar correctamente los objetos de interoperabilidad de Excel en C #

He leído muchos de los otros hilos aquí sobre la administración de referencias COM mientras uso la interoperabilidad .Net-Excel para asegurarme de que el proceso de Excel salga correctamente al salir, y hasta ahora las técnicas han estado funcionando muy bien, pero recientemente surgió un problema al agregar nuevas hojas de trabajo a un archivo de libro existente.

El siguiente código deja un proceso zombie Excel.

Si agrego una hoja de trabajo a un archivo de libro recién creado, sale bien. Si ejecuto el código excluyendo la línea .Add(), sale bien. (El archivo existente del que estoy leyendo es un archivo vacío creado por el código comentado)

¿Alguna idea?

//using Excel = Microsoft.Office.Interop.Excel;
//using System.Runtime.InteropServices;
public static void AddTest()
{
  string filename = @"C:\addtest.xls";
  object m = Type.Missing;
  Excel.Application excelapp = new Excel.Application();
  if (excelapp == null) throw new Exception("Can't start Excel");
  Excel.Workbooks wbs = excelapp.Workbooks;

  //if I create a new file and then add a worksheet,
  //it will exit normally (i.e. if you uncomment the next two lines
  //and comment out the .Open() line below):
  //Excel.Workbook wb = wbs.Add(Excel.XlWBATemplate.xlWBATWorksheet);
  //wb.SaveAs(filename, m, m, m, m, m, 
  //          Excel.XlSaveAsAccessMode.xlExclusive,
  //          m, m, m, m, m);

  //but if I open an existing file and add a worksheet,
  //it won't exit (leaves zombie excel processes)
  Excel.Workbook wb = wbs.Open(filename,
                               m, m, m, m, m, m,
                               Excel.XlPlatform.xlWindows,
                               m, m, m, m, m, m, m);

  Excel.Sheets sheets = wb.Worksheets;

  //This is the offending line:
  Excel.Worksheet wsnew = sheets.Add(m, m, m, m) as Excel.Worksheet; 

  //N.B. it doesn't help if I try specifying the parameters in Add() above

  wb.Save();
  wb.Close(m, m, m);

  //overkill to do GC so many times, but shows that doesn't fix it
  GC();
  //cleanup COM references
  //changing these all to FinalReleaseComObject doesn't help either
  while (Marshal.ReleaseComObject(wsnew) > 0) { } 
  wsnew = null;
  while (Marshal.ReleaseComObject(sheets) > 0) { }
  sheets = null;
  while (Marshal.ReleaseComObject(wb) > 0) { }
  wb = null;
  while (Marshal.ReleaseComObject(wbs) > 0) { }
  wbs = null;
  GC();
  excelapp.Quit();
  while (Marshal.ReleaseComObject(excelapp) > 0) { }
  excelapp = null;
  GC();
}

public static void GC()
{
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();
}
¿Fue útil?

Solución

No tengo el código a mano, pero me encontré con un problema similar. Si recuerdo correctamente, terminé recuperando la identificación del proceso de la instancia de Excel y eliminándola (después de un período de espera adecuado y cuando el otro método falló).

Creo que usé:

GetWindowThreadProcessId (a través de P / Invoke) en la propiedad hwnd del objeto de Excel para obtener la identificación del proceso, y luego usó Process.GetProcessById para obtener un objeto de proceso. Una vez que lo hubiera hecho, llamaría a Kill en el proceso.

EDITAR: Tengo que admitir que esta no es la solución ideal, pero si no puede encontrar la interfaz maliciosa que no se está lanzando, entonces esto lo arreglará en forma de cáscara de huevo / mazo real. ;)

EDIT2: no tiene que llamar a Close en el objeto de proceso inmediatamente ... Primero puede intentar llamar a <=> antes de recurrir a <=>.

Otros consejos

He hecho algo similar. Creo un archivo de Excel o abro uno existente. Borro todas las hojas y agrego las mías. Aquí está el código que uso para asegurar que todas las referencias estén cerradas:

            workbook.Close(true, null, null);
            excelApp.Quit();

            if (newSheet != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(newSheet);
            }
            if (rangeSelection != null)
            {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(rangeSelection);
            }
            if (sheets != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(sheets);
            }
            if (workbook != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook);
            }
            if (excelApp != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
            }

            newSheet = null;
            rangeSelection = null;
            sheets = null;
            workbook = null;
            excelApp = null;

            GC.Collect();

He probado esto con muchas opciones diferentes y todavía no me ha fallado.

aquí está mi código completo para eliminar el Excel que creó con la biblioteca de interoperabilidad de Office12 .Net: Disfrutar, -Alan.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Office.Interop.Excel;

class Program
{

    /// <summary> 
    /// Win32 API import for getting the process Id. 
    /// The out param is the param we are after. I have no idea what the return value is. 
    /// </summary> 
    [DllImport("user32.dll")]
    private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out IntPtr ProcessId); 

    static void Main(string[] args)
    {
        var app = new Application();
        IntPtr hwnd = new IntPtr(app.Hwnd);
        IntPtr processId;
        IntPtr foo = GetWindowThreadProcessId(hwnd, out processId);
        Process proc = Process.GetProcessById(processId.ToInt32());
        proc.Kill(); // set breakpoint here and watch the Windows Task Manager kill this exact EXCEL.EXE
        app.Quit(); // should give you a "Sorry, I can't find this Excel session since you killed it" Exception.
    }
}

Esto funciona muy bien para mí, sin excepciones.

Public Class ExcelHlpr

    Declare Function EndTask Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal ShutDown As Boolean, ByVal Force As Boolean) As Integer

    Dim cXlApp As Microsoft.Office.Interop.Excel.Application

    Public Function GetExcel() As Microsoft.Office.Interop.Excel.Application
        cXlApp = New Microsoft.Office.Interop.Excel.Application
        Return cXlApp
    End Function

    Public Function EndExcel() As Integer
        Dim xlHwnd As New IntPtr(cXlApp.Hwnd)
        Return EndTask(xlHwnd, False, True)
    End Function

End Class

No es muy constructivo, lo sé, pero probé el código exactamente como se muestra arriba y mi proceso de Excel sale como se esperaba, mi C: \ addtest.xls está sentado con 8 nuevas hojas y no se está ejecutando ningún proceso de Excel.
¿Podría el versión de interoperabilidad sea la causa me pregunto? Probé con 11 & Amp; 12.

Estoy usando VB.NET 3.5 SP1 y el siguiente código TODAVÍA deja EXCEL.EXE abierto:

        xlWorkbook.Close(SaveChanges:=False)
        xlApplication.Quit()

        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorksheet)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheets)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkbook)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApplication)

        xlRange = Nothing
        xlWorksheet = Nothing
        xlSheets = Nothing
        xlWorkbook = Nothing
        xlApplication = Nothing

        GC.GetTotalMemory(False)
        GC.Collect()
        GC.WaitForPendingFinalizers()

        GC.Collect()
        GC.WaitForPendingFinalizers()
        GC.Collect()
        GC.GetTotalMemory(True)

Andrew, aquí está el código que encontré que funciona. Pensé en publicarlo aquí para otros que vengan:

namespace WindowHandler
{
using System;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;

/// <summary>
/// Window class for handling window stuff.
/// This is really a hack and taken from Code Project and mutilated to this small thing.
/// </summary>
public class Window
{
    /// <summary>
    /// Win32 API import for getting the process Id.
    /// The out param is the param we are after. I have no idea what the return value is.
    /// </summary>
    [DllImport("user32.dll")]
    private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out IntPtr ProcessId);

    /// <summary>
    /// Gets a Window's process Id.
    /// </summary>
    /// <param name="hWnd">Handle Id.</param>
    /// <returns>ID of the process.</returns>
    public static IntPtr GetWindowThreadProcessId(IntPtr hWnd)
    {
        IntPtr processId;
        IntPtr returnResult = GetWindowThreadProcessId(hWnd, out processId);

        return processId;
    }
}
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top