34

我在使用 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它应该可以工作,但没有。我怎样才能解决这个问题?

谢谢你。

4

13 回答 13

75

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

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

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

所以,正确的方法是:

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

//business logic here

Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);
于 2013-06-28T14:50:42.597 回答
25

这是我写的一段代码,因为我和你有同样的问题。基本上,您需要关闭工作簿,退出应用程序,然后释放所有 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();
    }
于 2013-06-28T14:50:03.553 回答
10

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

——一个点

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();
}
于 2014-01-09T15:44:20.770 回答
9

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

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

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

于 2014-11-27T18:02:00.227 回答
7

在您的代码中,您有:

excel.Workbooks.Open(...)

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

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

该主题太大而无法在答案中完全探索,但我认为您会发现 Jake Ginnivan 关于该主题的文章非常有帮助:VSTO 和 COM 互操作

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

于 2013-06-28T14:49:57.120 回答
5

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

我使用可用于任何 COM 对象的通用 IDisposable 包装类。它就像一个魅力,它保持一切美好和干净。我什至可以重用私有字段(例如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();
            }
        }
    }
}
于 2017-01-13T12:39:49.243 回答
2

互操作进程后无法关闭 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();
    }
于 2019-04-24T07:30:53.733 回答
1

万一你绝望了除非您了解它的作用,否则请勿使用此方法

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

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

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

于 2015-06-04T13:01:34.617 回答
1

或者,您可以按照此处的说明终止 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);
于 2016-04-12T11:01:30.020 回答
0

我有同样的问题,我们可以在没有任何杀戮的情况下解决这个问题,我们总是忘记关闭我们在 Microsoft.Office.Interop.Excel 类中使用过的接口所以这里是代码片段并遵循结构和方式已经清除了对象,也密切关注代码中的 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);
        }
于 2017-01-20T06:39:30.783 回答
0

@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() 和尝试杀死之间等待。

于 2018-10-10T21:12:14.663 回答
0

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

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

尽管有关于双点神话的消息,但在我自己的测试中,如果我没有变量 for 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);
}
于 2021-04-24T02:33:39.253 回答
0

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

//导出excel代码在这里

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

于 2022-01-22T07:40:24.503 回答