【问题标题】:Cannot close Excel.exe after Interop process互操作进程后无法关闭 Excel.exe
【发布时间】:2013-06-28 14:43:52
【问题描述】:

我遇到了 Excel 互操作问题。

即使我释放实例,Excel.exe 也不会关闭。

这是我的代码:

using xl = Microsoft.Office.Interop.Excel;


xl.Application excel = new xl.Application();
excel.Visible = true;
excel.ScreenUpdating = false;
if (wordFile.Contains(".csv") || wordFile.Contains(".xls"))
{
   //typeExcel become a string of the document name
   string typeExcel = wordFile.ToString();
   xl.Workbook workbook = excel.Workbooks.Open(typeExcel,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing,  oMissing,  oMissing,
                                                oMissing,  oMissing);
   object outputFileName = null;
   if (wordFile.Contains(".xls"))
   {
     outputFileName = wordFile.Replace(".xls", ".pdf");
   }
   else if (wordFile.Contains(".csv"))
   {
     outputFileName = wordFile.Replace(".csv", ".pdf");
   }

   workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outputFileName, 
                                 XlFixedFormatQuality.xlQualityStandard, oMissing,
                                 oMissing, oMissing, oMissing, oMissing, oMissing);

   object saveChanges = xl.XlSaveAction.xlDoNotSaveChanges;
   ((xl._Workbook)workbook).Close(saveChanges, oMissing, oMissing);

   Marshal.ReleaseComObject(workbook);
   workbook = null;
}

我看到了,Marshal.RealeaseComObject 应该可以工作,但没有。 我该如何解决这个问题?

谢谢。

【问题讨论】:

标签: c# winforms excel-interop


【解决方案1】:

简单规则:避免使用双点调用表达式,例如:

var workbook = excel.Workbooks.Open(/*params*/)

...因为通过这种方式,您不仅可以为workbook 创建RCW 对象,还可以为Workbooks 创建对象,并且您也应该释放它(如果不维护对对象的引用,这是不可能的) .

所以,正确的方法是:

var workbooks = excel.Workbooks;
var workbook = workbooks.Open(/*params*/)

//business logic here

Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);

【讨论】:

  • 因此,我们(您)指出,为了提高 OfficeInterop 的效率,我们必须逐步完成所有工作,而不是“直接”。很高兴知道。这样工作。谢谢。
  • 我应该在发布时执行 .close 和 .quit 吗?只是想知道我是否可以避免它(自知)。
  • @Jean:始终建议您退出并关闭您创建的所有对象。它们可能会在您下次运行应用程序时产生问题。
  • 几个月后我终于遇到了这个答案,感谢上帝。我认为这种情况应该已经在 Microsoft Documentation for Marshal Class 中作为示例提到过。我怎么会知道这一点,为什么我必须至少搜索 2 个月。
  • Simple rule: avoid using double-dot-calling expressions, such as this - 这是shown to be false
【解决方案2】:

这是我写的一个sn-p代码,因为我和你有同样的问题。基本上,您需要关闭工作簿,退出应用程序,然后释放所有 COM 对象(不仅仅是 Excel 应用程序对象)。最后,调用垃圾收集器。

    /// <summary>
    /// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
    /// </summary>
    public void Dispose()
    {
        // Cleanup
        xWorkbook.Close(false);
        xApp.Quit();

        // Manual disposal because of COM
        while (Marshal.ReleaseComObject(xApp) != 0) { }
        while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
        while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
        while (Marshal.ReleaseComObject(xCharts) != 0) { }
        while (Marshal.ReleaseComObject(xMyChart) != 0) { }
        while (Marshal.ReleaseComObject(xGraph) != 0) { }
        while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
        while (Marshal.ReleaseComObject(xSeries) != 0) { }
        xApp = null;
        xWorkbook = null;
        xWorksheets = null;
        xWorksheet = null;
        xCharts = null;
        xMyChart = null;
        xGraph = null;
        xSeriesColl = null;
        xSeries = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

【讨论】:

  • 不错。还值得注意的是,我还必须在循环中使用的 Excel Range 对象上调用 ReleaseComObject。
  • 你对while的想法很棒!
【解决方案3】:

规则 - 永远不要使用一个点

-- 一个点

var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);

-- 两个或多个点

 (Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);

-- 例子

 using Microsoft.Office.Interop.Excel;

 Application xls = null;
 Workbooks workBooks = null;
 Workbook workBook = null;
 Sheets sheets = null;
 Worksheet workSheet1 = null;
 Worksheet workSheet2 = null;

 workBooks = xls.Workbooks;
 workBook = workBooks.Open(workSpaceFile);
 sheets = workBook.Worksheets;
 workSheet1 = (Worksheet)sheets[1];


// removing from Memory
 if (xls != null)
 {    
   foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
   {
      ReleaseObject(sheet);
   }

   ReleaseObject(sheets);
   workBook.Close();
   ReleaseObject(workBook);
   ReleaseObject(workBooks);

   xls.Application.Quit(); // THIS IS WHAT IS CAUSES EXCEL TO CLOSE
   xls.Quit();
   ReleaseObject(xls);

   sheets = null;
   workBook = null;
   workBooks = null;
   xls = null;

   GC.Collect();
   GC.WaitForPendingFinalizers();
   GC.Collect();
   GC.WaitForPendingFinalizers();
}

【讨论】:

    【解决方案4】:

    摆脱所有引用很棘手,因为您必须猜测调用是否像:

    var workbook = excel.Workbooks.Open("")
    

    创建一个您没有引用的Workbooks 实例。

    甚至像这样的引用:

    targetRange.Columns.AutoFit()
    

    将在您不知情的情况下创建.Columns() 的实例并且未正确发布。

    我最终编写了一个类,其中包含一个对象引用列表,可以以相反的顺序处理所有对象。

    该类有一个对象列表和Add() 函数,用于在您使用返回对象本身的 Excel 互操作时引用的任何内容:

        public List<Object> _interopObjectList = new List<Object>();
    
        public Excel.Application add(Excel.Application obj)
        {
            _interopObjectList.Add(obj);
            return obj;
        }
    
        public Excel.Range add(Excel.Range obj)
        {
            _interopObjectList.Add(obj);
            return obj;
        }
    
        public Excel.Workbook add(Excel.Workbook obj)
        {
            _interopObjectList.Add(obj);
            return obj;
        }
    
        public Excel.Worksheet add(Excel.Worksheet obj)
        {
            _interopObjectList.Add(obj);
            return obj;
        }
    
        public Excel.Worksheets add(Excel.Worksheets obj)
        {
            _interopObjectList.Add(obj);
            return obj;
        }
    
        public Excel.Sheets add(Excel.Sheets obj)
        {
            _interopObjectList.Add(obj);
            return obj;
        }
    
    
        public Excel.Workbooks add(Excel.Workbooks obj)
        {
            _interopObjectList.Add(obj);
            return obj;
        }
    

    然后我使用以下代码取消注册对象:

        //Release all registered interop objects in reverse order
        public void unregister()
        {
            //Loop object list in reverse order and release Office object
            for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
            { ReleaseComObject(_interopObjectList[i]); }
    
            //Clear object list
            _interopObjectList.Clear();
        }
    
    
        /// <summary>
        /// Release a com interop object 
        /// </summary>
        /// <param name="obj"></param>
         public static void ReleaseComObject(object obj)
         {
             if (obj != null && InteropServices.Marshal.IsComObject(obj))
                 try
                 {
                     InteropServices.Marshal.FinalReleaseComObject(obj);
                 }
                 catch { }
                 finally
                 {
                     obj = null;
                 }
    
             GC.Collect();
             GC.WaitForPendingFinalizers();
             GC.Collect();
             GC.WaitForPendingFinalizers();
         }
    

    那么原则就是这样创建类并捕获引用:

    //Create helper class
    xlsHandler xlObj = new xlsHandler();
    
    ..
    
    //Sample - Capture reference to excel application
    Excel.Application _excelApp = xlObj.add(new Excel.Application());
    
    ..
    //Sample - Call .Autofit() on a cell range and capture reference to .Columns() 
    xlObj.add(_targetCell.Columns).AutoFit();
    
    ..
    
    //Release all objects collected by helper class
    xlObj.unregister();
    

    也许不是美丽的代码,但可能会激发一些有用的东西。

    【讨论】:

      【解决方案5】:

      在您的代码中,您有:

      excel.Workbooks.Open(...)
      

      excel.Workbooks 正在创建一个 COM 对象。然后,您将从该 COM 对象调用 Open 函数。但是,当您完成后,您不会释放 COM 对象。

      这是处理 COM 对象时的常见问题。基本上,您的表达式中不应包含多个点,因为您需要在完成后清理 COM 对象。

      这个话题太大了,无法在答案中完全探索,但我认为您会发现 Jake Ginnivan 关于该主题的文章非常有帮助:VSTO and COM Interop

      如果您厌倦了所有这些 ReleaseComObject 调用,您可能会发现这个问题很有帮助:
      How to properly clean up Excel interop object in C#, 2012 edition

      【讨论】:

        【解决方案6】:

        如其他答案所述,使用 两个点 将创建无法被 Marshal.FinalReleaseComObject 关闭的隐藏引用。我只是想分享我的解决方案,这样就无需记住Marshal.FinalReleaseComObject - 这真的很容易错过,而且很难找到罪魁祸首。

        我使用了一个通用 IDisposable 包装类,它可以用于任何 COM 对象。它就像一个魅力,它保持一切美好和干净。我什至可以重用私有字段(例如this.worksheet)。由于 IDisposable 的性质(Dispose 方法作为 finally 运行),它还会在引发错误时自动释放对象。

        using Microsoft.Office.Interop.Excel;
        
        public class ExcelService
        {
            private _Worksheet worksheet;
        
            private class ComObject<TType> : IDisposable
            {
                public TType Instance { get; set; }
        
                public ComObject(TType instance)
                {
                    this.Instance = instance;
                }
        
                public void Dispose()
                {
                    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
                }
            }
        
            public void CreateExcelFile(string fullFilePath)
            {
                using (var comApplication = new ComObject<Application>(new Application()))
                {
                    var excelInstance = comApplication.Instance;
                    excelInstance.Visible = false;
                    excelInstance.DisplayAlerts = false;
        
                    try
                    {
                        using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
                        using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
                        using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
                        {
                            using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
                            {
                                this.worksheet = comSheet.Instance;
                                this.worksheet.Name = "Action";
                                this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                            }
        
                            using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
                            {
                                this.worksheet = comSheet.Instance;
                                this.worksheet.Name = "Status";
                                this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
                            }
        
                            using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
                            {
                                this.worksheet = comSheet.Instance;
                                this.worksheet.Name = "ItemPrices";
                                this.worksheet.Activate();
        
                                using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
                                using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
                                {
                                    comRange.Instance.Select();
                                    comWindow.Instance.FreezePanes = true;
                                }
                            }
        
                            if (this.fullFilePath != null)
                            {
                                var currentWorkbook = (workbook.Instance as _Workbook);
                                currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
                                currentWorkbook.Close(false);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Trace.WriteLine(ex.Message);
                        throw;
                    }
                    finally
                    {
                        // Close Excel instance
                        excelInstance.Quit();
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案7】:

          Interop 进程后无法关闭 Excel.exe

          不要把这个弄得太复杂!! 只需创建一个简单的方法并将该方法称为 如下:

          // to kill the EXCELsheet file process from process Bar
          private void KillSpecificExcelFileProcess() {
            foreach (Process clsProcess in Process.GetProcesses())
              if (clsProcess.ProcessName.Equals("EXCEL"))  //Process Excel?
                clsProcess.Kill();
              }
          

          【讨论】:

          • 但稍后当您打开 Excel 时,您会收到侧边栏警告,提示您可能因应用程序意外关闭而丢失数据。
          【解决方案8】:

          或者,您可以按照here 的说明终止 Excel 进程。

          首先,导入SendMessage函数:

          [DllImport("user32.dll", CharSet = CharSet.Auto)]
          private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
          

          然后,将 WM_CLOSE 消息发送到主窗口:

          SendMessage((IntPtr)excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);
          

          【讨论】:

            【解决方案9】:

            万一你绝望除非你了解它的作用,否则不要使用这种方法

            foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
            {
              proc.Kill();
            }
            

            注意:这会杀死所有名为“EXCEL”的进程。

            我必须这样做,因为即使我已经关闭了代码中的每个 COM 对象,我仍然有顽固的 Excel.exe 进程挂在那里。当然,这绝不是最好的解决方案。

            【讨论】:

            • 这是我要避免的,因为这会关闭甚至用户手动打开的常规 Excel。
            • 因为这会杀死任何碰巧正在运行的随机 Excel 实例,如果非常有限,它会很有用。我将发布一个更不危险、更有针对性的方法来杀死 Excel 应用程序对象使用的 Excel 进程。
            • 我们很可能想要杀死所有 Excel 进程。感谢您再添加一个免责声明,即我们正在杀死每个名为 Excel 的进程。显然,代码 foreach (processes in Processes){process.Kill()} 杀死每个进程并不明显。悲伤的时光......
            • @DenisMolodtsov,我听到了。是的,代码的作用对你我来说都很清楚,但是 Stack Overflow 上有广泛的用户,包括初学者和一些甚至不具备这样质量的人。如果您觉得没有我的编辑您的答案会更好,请随时将其删除。
            【解决方案10】:

            我有同样的问题,我们可以解决问题而无需任何杀戮,我们总是忘记关闭我们在 Microsoft.Office.Interop.Excel 类中使用的接口所以这里是代码 sn-p 并遵循结构和方式已清除对象,还要注意代码中的 Sheets 界面这是我们经常关闭应用程序、工作簿、工作簿、范围、工作表的罪魁祸首,但我们忘记或在不知不觉中没有释放 Sheets 对象或使用的界面所以这里是代码:

                           Microsoft.Office.Interop.Excel.Application app = null;
                    Microsoft.Office.Interop.Excel.Workbooks books = null;
                    Workbook book = null;
                    Sheets sheets = null;
                    Worksheet sheet = null;
                    Range range = null;
            
                    try
                    {
                        app = new Microsoft.Office.Interop.Excel.Application();
                        books = app.Workbooks;
                        book = books.Add();
                        sheets = book.Sheets;
                        sheet = sheets.Add();
                        range = sheet.Range["A1"];
                        range.Value = "Lorem Ipsum";
                        book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
                        book.Close();
                        app.Quit();
                    }
                    finally
                    {
                        if (range != null) Marshal.ReleaseComObject(range);
                        if (sheet != null) Marshal.ReleaseComObject(sheet);
                        if (sheets != null) Marshal.ReleaseComObject(sheets);
                        if (book != null) Marshal.ReleaseComObject(book);
                        if (books != null) Marshal.ReleaseComObject(books);
                        if (app != null) Marshal.ReleaseComObject(app);
                    }
            

            【讨论】:

              【解决方案11】:

              @Denis Molodtsov 为了提供帮助,建议终止所有名为“EXCEL”的进程。这似乎是在自找麻烦。已经有很多答案描述了在调用 excel.quit() 后通过与 COM 互操作玩得好来停止进程的方法。如果你能让它发挥作用,这是最好的。

              @Kevin Vuilleumier 提出了一个很棒的建议,将 WM_CLOSE 发送到 Excel 窗口。我打算测试一下。

              如果出于某种原因您需要终止 Excel 应用程序对象的 Excel 进程,您可以使用以下方式专门针对它:

                using System.Diagnostics;
                using System.Runtime.InteropServices;
              
              // . . .
              
                  [DllImport("user32.dll", SetLastError=true)]
                  public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
              
              // . . .
              
                  uint excelAppPid;
                  uint tid = GetWindowThreadProcessId(excel.Hwnd, out excelAppPid);
              
                  if (tid)
                  {
                    Process excelAppProc = Process.GetProcessById($excelPid)
                    if (excelAppProc)
                    {
                      excelAppProc.Kill()
                    }
                  }
              

              我没有时间在 C# 中进行全面测试,但我在 Powershell 中进行了快速测试,但遇到 Excel 无法终止的问题,这种方法有效。

              这很简单。 Excel App 对象的 Hwnd 属性是 Excel 进程的隐藏窗口句柄。将 excel.Hwnd 传递给 GetWindowThreadProcessId 以获取进程 ID。使用它打开进程,最后调用 Kill()。

              至少我们确定我们正在杀死正确的进程。嗯,很确定。如果 Excel 进程已经正常终止,则它的进程 ID 可以被新进程重用。为了限制这种可能性,重要的是不要在调用 excel.quit() 和尝试杀死之间等待。

              【讨论】:

                【解决方案12】:

                在我自己做了几次测试,检查了不同的答案之后,这是使进程在几秒钟后消失的最短代码:

                var excelApp = new Microsoft.Office.Interop.Excel.Application();
                var workbooks = excelApp.Workbooks;
                try
                {
                    var wb = workbooks.Open(filePath);
                
                    // Use worksheet, etc.
                    Worksheet sheet = wb.Worksheets.get_Item(1);
                }
                finally
                {
                    excelApp.Quit();
                    Marshal.ReleaseComObject(workbooks);
                    Marshal.ReleaseComObject(excelApp);
                }
                

                尽管有关于双点神话的消息,但在我自己的测试中,如果我没有 Workbooks 的变量,该过程将永远存在。似乎确实调用excelApp.Workbooks 会在内存中创建一些对象,从而阻止垃圾收集器处理 excel.exe。这意味着这将进程留在内存中

                try
                {
                    // Do not
                    var wb = excelApp.Workbooks.Open("");
                }
                finally
                {
                    excelApp.Quit();
                    // Do not
                    Marshal.ReleaseComObject(excelApp.Workbooks);
                    Marshal.ReleaseComObject(excelApp);
                }
                

                【讨论】:

                  【解决方案13】:

                  İ如果您在一个按钮中处理它,你们可以获得您创建的最新进程并且您可以杀死它。我使用它。祝您有美好的日子。

                  //导出excel代码在这里

                  System.Diagnostics.Process [] proc = System.Diagnostics.Process.GetProcessesByName("excel"); proc[proc.Length-1].Kill();

                  【讨论】:

                  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
                  猜你喜欢
                  • 1970-01-01
                  • 2013-02-21
                  • 2014-12-02
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-03-21
                  相关资源
                  最近更新 更多