C#互操作:将新工作表添加到现有文件后,excel进程未退出[重复]

Posted

技术标签:

【中文标题】C#互操作:将新工作表添加到现有文件后,excel进程未退出[重复]【英文标题】:C# interop: excel process not exiting after adding new worksheet to existing file [duplicate] 【发布时间】:2008-12-08 18:50:46 【问题描述】:

可能重复:How to properly clean up Excel interop objects in C#

我在这里阅读了许多其他关于在使用 .Net-Excel 互操作时管理 COM 引用的线程,以确保 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();

【问题讨论】:

如果 GC 方法不起作用,因为您仍然在某处持有引用。你确定这就是所有相关的代码吗? 是的,我得到了一个僵尸进程,它完全按照上面的代码运行 【参考方案1】:

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

我已经用许多不同的选项对此进行了测试,但我还没有失败。

【讨论】:

【参考方案2】:

我手头没有代码,但我确实遇到了类似的问题。 如果我没记错的话,我最终检索了 excel 实例的进程 ID,并杀死了它(在适当的等待期之后,并且当其他方法失败时)。

我想我用过:

GetWindowThreadProcessId(通过P/Invoke)在excel对象的hwnd属性上获取进程id,然后使用Process.GetProcessById获取进程对象。 完成此操作后,我会在该过程中致电Kill

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

EDIT2:您不必立即在进程对象上调用Kill...您可以先尝试调用Close,然后再使用Kill

【讨论】:

【参考方案3】:

这是我的完整代码,用于杀死您使用 Office12 .Net 互操作库创建的 Excel: 享受, -艾伦。

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.
    

【讨论】:

我正在使用您的代码并取回零的 processId。关于什么可能导致这种情况发生的任何想法?【参考方案4】:

这对我来说很好用,没有任何例外。

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

【讨论】:

【参考方案5】:

我知道不是很有建设性,但我完全按照上面所示测试了代码,我的 Excel 进程按预期退出,我的 C:\addtest.xls 有 8 个新工作表,没有运行 Excel 进程。可以互操作版本是我想知道的原因吗?我用 11 和 12 进行了测试。

【讨论】:

你能让你店里的其他人复制它吗?我刚刚让其他 2 个人停下来工作 :) 并尝试使用没有孤立的进程。 “那里”是指“他们的”,我是英语而不是 gud 是的,这里在另一台机器上复制。两者都使用 PIA dll 版本 11.0.0.0,一个安装了 Excel 2003 SP2 (11.6560.6568),另一个安装了 Excel 2003 SP3 (11.8231.8221)。 这里只是一个想法。确保您终止所有正在运行的 Excel 进程,然后在 Excel.Application excelapp = new Excel.Application() 之后添加一个 tryfinally 块,并将 excelApp 清理程序粘贴到最后,看看它是否仍在发生。我知道……但试一试。【参考方案6】:

我正在使用 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)

【讨论】:

移动此代码 GC.Collect() GC.WaitForPendingFinalizers() GC.Collect() GC.WaitForPendingFinalizers() 以上 ReleaseCOMObject 调用将 ReleaseCOMObject 调用更改为 FinalReleaseCOMObject 调用【参考方案7】:

安德鲁,这是我发现的有效代码。我想我把它贴在这里给遇到的其他人:

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;
    


【讨论】:

以上是关于C#互操作:将新工作表添加到现有文件后,excel进程未退出[重复]的主要内容,如果未能解决你的问题,请参考以下文章

将新工作表添加到XLSM宏Excel文件时出错

Excel 互操作从右到左文档

访问 VBA 如何将新工作表添加到 Excel?

将新筛选器添加到 Tableau 上的现有仪表板

将新数据从 JSON 添加到现有表

可以使用 XSLT 样式表填充现有 Excel 工作表吗?