C # interoperabilidade: processo do Excel não sair depois de adicionar nova planilha ao arquivo existente [duplicar]

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

  •  20-08-2019
  •  | 
  •  

Pergunta

Duplicate possíveis:
como limpar corretamente os objetos do Excel interoperabilidade em C #

Eu li muitos dos outros tópicos aqui sobre o gerenciamento de referências COM ao usar a interoperabilidade Net-Excel para garantir que as saídas de processo do Excel corretamente ao sair, e até agora as técnicas têm vindo a trabalhar muito bem, mas eu recentemente deparei com um problema ao adicionar novas planilhas em um arquivo de pasta de trabalho existente.

O código abaixo folhas de um processo Excel zumbi.

Se eu adicionar uma planilha para um arquivo de pasta de trabalho recém-criado, ele sai bem. Se eu executar o código excluindo a linha .Add(), sai bem. (O arquivo existente que estou lendo a partir é um arquivo vazio criado pelo código comentado)

Todas as idéias?

//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();
}
Foi útil?

Solução

Eu não tenho o código para a mão, mas eu correr em um problema semelhante. Se bem me lembro, acabei recuperar o ID do processo da instância do excel, e matá-lo (depois de um período de espera adequado, e quando o outro método falhou).

Eu acho que eu usei:

GetWindowThreadProcessId (via P / Invoke) no excel propriedade do objeto hwnd para obter o ID do processo, e então usado Process.GetProcessById para obter um objeto de processo. Uma vez que eu tinha feito isso, eu chamaria Kill sobre o processo.

EDIT: Eu tenho que admitir, esta não é a solução ideal, mas se você não consegue encontrar a interface desonestos que não está sendo liberado, então este irá corrigi-lo na forma verdadeira casca de ovo / marreta. ;)

EDIT2:. Você não tem que chamar Kill no objeto processo imediatamente ... Você poderia primeiro tentar chamar Close antes de recorrer a Kill

Outras dicas

Eu fiz uma coisa semelhante. Eu criar um arquivo Excel ou abrir uma já existente. Eu apagar todas as folhas e adicionar minha própria. aqui está o código que eu uso para garantir que todas as referências estão fechados:

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

Eu testei isso com muitas opções diferentes e não tinha que falhar em mim ainda.

aqui está o meu código completo para matar o Excel criado com a biblioteca do Office12 interoperabilidade .NET: Aproveitar, -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.
    }
}

Esta é funciona muito bem para mim, sem qualquer exceção.

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

Não muito construtiva eu ??sei, mas eu testei o código exatamente como mostrado acima e meus sai do processo do Excel Como esperado, o meu C:. \ Addtest.xls está sentado com 8 novas folhas e nenhum processo de Excel está sendo executado
Poderia o interoperabilidade versão ser a causa I maravilha? Eu testei com 11 e 12.

Eu estou usando VB.NET 3.5 SP1 e as seguintes folhas ainda código EXCEL.exe aberta:

        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, aqui está o código que eu encontrei que funciona. Eu pensei que eu postar postá-lo aqui para os outros que vêm através:

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top