C#interop:在将新工作表添加到现有文件后,excel进程不会退出

时间:2008-12-08 18:50:47

标签: c# excel com interop pia

  

可能重复:
  How to properly clean up Excel interop objects in 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();
}

7 个答案:

答案 0 :(得分:13)

我做了类似的事情。我创建一个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();

我已经用很多不同的选项对它进行了测试,并没有让我失败。

答案 1 :(得分:4)

我没有代码可以提供,但我遇到了类似的问题。 如果我没记错的话,我最终检索了excel实例的进程ID,并将其终止(在适当的等待期后,以及其他方法失败时)。

我想我用过:

GetWindowThreadProcessId(通过P / Invoke)在excel对象hwnd属性上获取进程ID,然后使用Process.GetProcessById获取进程对象。 完成后,我会在流程上调用Kill

编辑:我不得不承认,这不是理想的解决方案,但是如果你找不到尚未发布的恶意界面,那么这将以真正的蛋壳/大锤方式修复它。 ;)

EDIT2:您不必立即在流程对象上调用Kill ...您可以先尝试拨打Close,然后再诉诸Kill

答案 2 :(得分:4)

这是我的完整代码,用于杀死使用Office12 .Net互操作库创建的Excel: 请享用, -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.
    }
}

答案 3 :(得分:1)

这对我来说非常好,没有任何例外。

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

答案 4 :(得分:0)

我知道不是很有建设性,但我完全按照上面的说明测试了代码,我的Excel进程按预期退出,我的C:\ addtest.xls有8个新工作表,没有Excel进程正在运行。
可能我想知道互操作版本是什么原因?我测试了11&amp; 12。

答案 5 :(得分:0)

我正在使用VB.NET 3.5 SP1,以下代码STILL使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)

答案 6 :(得分:0)

安德鲁,这是我发现有效的代码。我以为我会在这里发帖给其他遇到的人:

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