Взаимодействие с C #: процесс Excel не завершается после добавления нового рабочего листа в существующий файл

Возможный дубликат:
Как правильно очистить объекты взаимодействия Excel в C #

Я читал здесь много других тем об управлении ссылками на COM при использовании взаимодействия .Net-Excel, чтобы убедиться, что процесс Excel завершается правильно после выхода, и до сих пор методы работали очень хорошо, но недавно я наткнулся на проблема при добавлении новых листов в существующий файл книги.

Приведенный ниже код оставляет процесс Excel зомби.

Если я добавлю лист к вновь созданному файлу книги, он завершится нормально. Если я запустил код, исключая строку .Add(), он завершится нормально. (Существующий файл, из которого я читаю, представляет собой пустой файл, созданный закомментированным кодом)

Любые идеи?

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

person yoyoyoyosef    schedule 08.12.2008    source источник
comment
Если метод GC не работает, потому что вы все еще где-то держите ссылку. Вы уверены, что это весь соответствующий код?   -  person Vinko Vrsalovic    schedule 11.12.2009
comment
Да, у меня остался зомби-процесс, выполняющий приведенный выше код в точности так, как написано   -  person yoyoyoyosef    schedule 11.12.2009


Ответы (7)


У меня нет кода под рукой, но я столкнулся с аналогичной проблемой. Если я правильно помню, я получил идентификатор процесса экземпляра excel и убил его (после подходящего периода ожидания и когда другой метод не удался).

Я думаю, что использовал:

GetWindowThreadProcessId (через P / Invoke) в свойстве hwnd объекта excel, чтобы получить идентификатор процесса, а затем использовать Process.GetProcessById для получения объекта процесса. Как только я это сделаю, я позвоню Kill по процессу.

РЕДАКТИРОВАТЬ: Я должен признать, что это не идеальное решение, но если вы не можете найти мошеннический интерфейс, который не выпускается, это исправит его в истинном стиле яичной скорлупы / кувалды. ;)

РЕДАКТИРОВАТЬ2: Вам не нужно сразу вызывать Kill для объекта процесса ... Вы можете сначала попробовать вызвать Close, прежде чем прибегать к Kill.

person Andrew Rollings    schedule 08.12.2008

Я сделал то же самое. Я создаю файл Excel или открываю существующий. Удаляю все листы и добавляю свои. вот код, который я использую для закрытия всех ссылок:

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

Я тестировал это с множеством различных вариантов, и пока что у меня ничего не вышло.

person Jon    schedule 10.12.2008

вот мой полный код, чтобы убить Excel, который вы создали с помощью библиотеки взаимодействия Office12 .Net: Наслаждайтесь, -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.
    }
}
person AlanR    schedule 16.02.2010
comment
Я использую ваш код и получаю нулевой идентификатор процесса. Есть идеи, что могло вызвать это? - person thiag0; 12.04.2011

У меня это прекрасно работает без каких-либо исключений.

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
person io.gun    schedule 09.08.2011

Не очень конструктивно, я знаю, но я протестировал код точно так, как показано выше, и мой процесс Excel завершился, как ожидалось, мой C: \ addtest.xls сидит с 8 новыми листами, и процесс Excel не запущен.
Может ли версия взаимодействия быть причиной мне интересно? Я тестировал с 11 и 12.

person Simon Wilson    schedule 08.12.2008
comment
Можете ли вы заставить других парней в вашем магазине повторить это? Я просто попросил 2 других парня прекратить работу :) и попробовать без сиротских процессов. - person Simon Wilson; 08.12.2008
comment
Под там я имел в виду их, я английский не гуд - person Simon Wilson; 08.12.2008
comment
да, воспроизведено здесь на другой машине. Оба используют библиотеку PIA dll версии 11.0.0.0, одна с установленным пакетом обновления 2 (11.6560.6568) для Excel 2003, а другая - с пакетом обновления 3 (11.8231.8221) для Excel 2003. - person yoyoyoyosef; 09.12.2008
comment
Просто мысль здесь. Убедитесь, что вы завершили все запущенные процессы Excel, затем добавьте блок try {} finally {} после Excel.Application excelapp = new Excel.Application () и, наконец, вставьте в него очистку excelApp и посмотрите, происходит ли это по-прежнему. Я знаю ... но попробуй. - person Simon Wilson; 09.12.2008

Я использую VB.NET 3.5 SP1, и следующий код ВСЕ ЕЩЕ оставляет открытым 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)
person Community    schedule 19.02.2009
comment
переместите этот код GC.Collect () GC.WaitForPendingFinalizers () GC.Collect () GC.WaitForPendingFinalizers () над вызовами ReleaseCOMObject измените вызовы ReleaseCOMObject на вызовы FinalReleaseCOMObject - person Anonymous Type; 15.02.2010

Эндрю, вот код, который я нашел, работает. Я думал, что отправлю его здесь для других, кто сталкивается:

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;
    }
}
}
person Community    schedule 16.03.2009