2008-12-08 28 views
8

Possible en double:
How to properly clean up Excel interop objects in C#C# Interop: processus Excel ne sortiez après l'ajout d'une nouvelle feuille de calcul au fichier existant

J'ai lu beaucoup des autres threads ici sur la gestion des références COM en en utilisant le .Net-Excel interop pour s'assurer que le processus Excel se termine correctement à la sortie, et jusqu'ici les techniques ont très bien fonctionné, mais j'ai récemment rencontré un problème lors de l'ajout de nouvelles feuilles de calcul à un fichier de classeur existant.

Le code ci-dessous laisse un processus Excel zombie.

Si j'ajoute une feuille de calcul à un fichier de classeur nouvellement créé, il se termine correctement. Si je cours le code excluant la ligne .Add(), il se termine bien. (Le fichier existant que je suis en train de lire est un fichier vide créé par le code commenté)

Des idées?

//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 la méthode GC ne fonctionne pas parce que vous maintenez toujours une référence quelque part. Êtes-vous sûr que c'est tout le code pertinent? –

+0

Oui, je reçois un processus de zombie laissé derrière le code ci-dessus exactement comme écrit – yoyoyoyosef

Répondre

4

Je n'ai pas le code à portée de main, mais j'ai rencontré un problème similaire. Si je me souviens bien, j'ai fini par récupérer l'identifiant de processus de l'instance Excel, et je l'ai tué (après une période d'attente appropriée, et quand l'autre méthode a échoué).

Je pense que je:

GetWindowThreadProcessId (via P/Invoke) sur la propriété hwnd objet Excel pour obtenir le numéro de processus, puis utilisé Process.GetProcessById pour obtenir un objet de processus. Une fois que j'aurais fait cela, j'appellerais Kill sur le processus.

EDIT: Je dois admettre que ce n'est pas la solution idéale, mais si vous ne trouvez pas l'interface non-validée qui ne sera pas publiée, alors cela la corrigera de la même façon. ;)

EDIT2: Vous ne devez pas appeler Kill sur l'objet de processus immédiatement ... Vous pouvez d'abord essayer d'appeler Close avant de recourir à Kill.

0

Pas très constructif Je sais mais j'ai testé le code exactement comme indiqué ci-dessus et mon processus Excel se termine comme prévu, mon C: \ addtest.xls est assis avec 8 nouvelles feuilles et aucun processus Excel n'est en cours d'exécution.
La version interop pourrait-elle être la cause que je me demande? J'ai testé avec 11 & 12.

+0

hmm ... fwiw Je cours v11 – yoyoyoyosef

+0

Pouvez-vous obtenir d'autres gars dans votre magasin répliquer? J'ai juste eu 2 autres gars arrêtent le travail :) et essaye avec les processus non orphelins. –

+0

Par "là" je voulais dire "leur", moi anglais pas gud –

13

J'ai fait une chose similaire. Je crée un fichier Excel ou ouvre un fichier existant. Je supprime toutes les feuilles et ajoute le mien. voici le code que j'utilise pour assurer que toutes les références sont fermées:

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

J'ai testé cela avec beaucoup d'options différentes et pas eu échouer sur moi encore.

0

J'utilise VB.NET 3.5 SP1 et le code suivant laisse ouverte 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

déplacer ce code GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForPendingFinalizers() au-dessus du ReleaseComObject appelle changement le ReleaseComObject appelle aux appels FinalReleaseCOMObject –

0

Andrew, voici le code que j'ai trouvé qui fonctionne.Je pensais que je posterai poste ici pour d'autres qui viennent à travers:

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

voici mon code complet pour tuer Excel vous avez créé avec la bibliothèque .Net Interop Office12: EnJOY -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

J'utilise votre code et de récupérer un processId de zéro. Des idées de ce qui pourrait causer cela? – thiag0

1

Cela fonctionne très bien pour moi, sans aucune exception.

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