C # interop: le processus Excel ne se ferme pas après l'ajout d'une nouvelle feuille de calcul au fichier existant [dupliquer]

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

  •  20-08-2019
  •  | 
  •  

Question

  

Double possible:
   Comment nettoyer correctement les objets d'interopérabilité Excel en C #

J'ai lu beaucoup d'autres discussions ici sur la gestion des références COM lors de l'utilisation de l'interopérabilité .Net-Excel pour m'assurer que le processus Excel se termine correctement à la sortie. Jusqu'à présent, les techniques fonctionnaient très bien. Un problème est survenu 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 ferme correctement. Si j'exécute le code en excluant la ligne .Add(), il se termine correctement. (Le fichier existant que je lis est un fichier vide créé par le code mis en commentaire)

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();
}
Était-ce utile?

La solution

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

Je pense avoir utilisé:

GetWindowThreadProcessId (via P / Invoke) sur la propriété hwnd de l'objet excel pour obtenir l'identifiant du processus, puis utilisé Process.GetProcessById pour obtenir un objet processus. Une fois cela fait, je ferais appel à Kill le processus.

EDIT: Je dois admettre que ce n’est pas la solution idéale, mais si vous ne trouvez pas l’interface non fiable qui n’a pas été publiée, cela corrigera le problème de manière très réaliste. ;)

EDIT2: Il n'est pas nécessaire d'appeler Close immédiatement sur l'objet de processus ... Vous pouvez d'abord essayer d'appeler <=> avant de recourir à <=>.

Autres conseils

J'ai fait la même chose. Je crée un fichier Excel ou ouvre un fichier existant. Je supprime toutes les feuilles et ajoute les miennes. voici le code que j'utilise pour m'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 de nombreuses options et je ne l'avais pas encore échoué.

voici mon code complet pour supprimer Excel créé avec la bibliothèque Office12 .Net interop: Prendre plaisir, -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.
    }
}

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

Pas très constructif, je le 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 contient 8 nouvelles feuilles et aucun processus Excel n’est en cours.
version interop peut être la cause que je me demande? J'ai testé avec 11 & Amp; 12.

J'utilise VB.NET 3.5 SP1 et le code suivant laisse encore EXCEL.EXE ouvert:

        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, voici le code que j'ai trouvé qui fonctionne. Je pensais poster ici pour les autres qui rencontrent:

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;
    }
}
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top