2008-12-08 12 views
8

Duplicar posibles:
How to properly clean up Excel interop objects in C## interoperabilidad C: Proceso de Excel no salir después de añadir nueva hoja de cálculo al archivo existente

He leído muchos de los otros hilos aquí acerca de la gestión de referencias COM mientras utilizando la interoperabilidad .Net-Excel para asegurarnos de que el proceso de Excel sale correctamente al salir, y hasta ahora las técnicas han funcionado muy bien, pero recientemente encontré un problema al agregar nuevas hojas de trabajo a un archivo de libro de trabajo existente.

El siguiente código deja un proceso zombie de 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(); 
} 
+0

Si el método GC no funciona porque todavía está sosteniendo una referencia en alguna parte. ¿Estás seguro de que ese es todo el código relevante? –

+0

Sí, me sale un proceso zombie dejando correr el código anterior exactamente como está escrito – yoyoyoyosef

Respuesta

4

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 he utilizado:

GetWindowThreadProcessId (a través de P/Invoke) en la propiedad objeto hwnd Excel para obtener el identificador de proceso, y luego se usa Process.GetProcessById para obtener un objeto de proceso. Una vez que hice eso, llamaría al Kill en el proceso.

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

Edit2: Usted no tiene que llamar Kill en el procedimiento objeto de inmediato ... Usted podría intentar llamar primero Close antes de recurrir a Kill.

0

No 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á instalada con 8 hojas nuevas y no se está ejecutando ningún proceso de Excel.
¿Podría ser la causa de la versión de interoperabilidad? Probé con 11 & 12.

+0

hmm ... fwiw Estoy ejecutando v11 – yoyoyoyosef

+0

¿Puedes conseguir que otros tipos en tu tienda lo repliquen? Acabo de hacer que otros 2 chicos dejen de trabajar :) y prueben con los procesos sin huérfanos. –

+0

Al decir "allí" quise decir "su", yo inglés no gud –

13

He hecho algo similar. Creo un archivo de Excel o abro un existente. Borro todas las hojas y agrego las mías. Aquí está el código que utilizo 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 no tenía que fallar en mí todavía.

0

estoy usando VB.NET 3.5 SP1 y el siguiente código todavía deja abierta EXCEL.EXE:

 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) 
+0

mover el código GC.Collect()() GC.WaitForPendingFinalizers gc.collect() ( GC.WaitForPendingFinalizers) por encima de la llama ReleaseComObject cambio del ReleaseComObject llama a FinalReleaseCOMObject llamadas –

0

Andrew, aquí está el código que he encontrado que funciona.Pensé que salen post aquí para otros que vienen a través de:

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

aquí está mi código completo para matar al Excel que ha creado con la biblioteca de interoperabilidad 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. 
    } 
} 
+0

estoy usando su código y obtener un processId de cero. ¿Alguna idea de lo que podría causar que esto suceda? – thiag0

1

Esto funciona muy bien para mí, sin ninguna excepción.

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 
Cuestiones relacionadas