在C#中安全地处理Excel互操作对象?

14

我正在开发一个基于WinForms、使用C#和Visual Studio 2008的应用程序。该应用程序与Excel文件交互,我使用Microsoft.Office.Interop.Excel;实现此功能。

我想知道如何确保对象在出现错误时也能被释放?

以下是我的代码:

private void button1_Click(object sender, EventArgs e)
{
    string myBigFile="";
    OpenFileDialog openFileDialog1 = new OpenFileDialog();
    DialogResult result = openFileDialog1.ShowDialog(); // Show the dialog.
    if (result == DialogResult.OK) // Test result.
        myBigFile=openFileDialog1.FileName;

    Excel.Application xlApp;
    Excel.Workbook xlWorkBook;
    Excel.Worksheet xlWorkSheet;
    Excel.Range range;

    string str;
    int rCnt = 0;
    int cCnt = 0;

    xlApp = new Excel.ApplicationClass();
    xlWorkBook = xlApp.Workbooks.Open(myBigFile, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", true, false, 0, true, 1, 0);
    xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);

    range = xlWorkSheet.UsedRange;

    /*
    for (rCnt = 1; rCnt <= range.Rows.Count; rCnt++)
    {
        for (cCnt = 1; cCnt <= range.Columns.Count; cCnt++)
        {
            str = (string)(range.Cells[rCnt, cCnt] as Excel.Range).Value2;
            MessageBox.Show(str);
        }
    }
     */
    xlWorkSheet..EntireRow.Delete(Excel.XLDirection.xlUp)

    xlWorkBook.SaveAs(xlWorkBook.Path + @"\XMLCopy.xls",         Excel.XlFileFormat.xlXMLSpreadsheet, Type.Missing, Type.Missing,
   false, false, Excel.XlSaveAsAccessMode.xlNoChange,
   Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

    xlWorkBook.Close(true, null, null);
    xlApp.Quit();

    releaseObject(xlWorkSheet);
    releaseObject(xlWorkBook);
    releaseObject(xlApp);
}

private void releaseObject(object obj)
{
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
        obj = null;
    }
    catch (Exception ex)
    {
        obj = null;
        MessageBox.Show("Unable to release the Object " + ex.ToString());
    }
    finally
    {
        GC.Collect();
    }
}

如何确保在工作簿打开后出现错误时,我能够确保处理对象:

Excel.Application xlApp;
Excel.Workbook xlWorkBook;
Excel.Worksheet xlWorkSheet;
Excel.Range range;

换言之,无论我需要什么,以下行应该运行。
xlWorkBook.Close(true, null, null);
xlApp.Quit();

releaseObject(xlWorkSheet);
releaseObject(xlWorkBook);
releaseObject(xlApp);

请注意,我也尝试过这个方法,但结果仍然有同样的问题。
xlWorkBook.Close(false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);


                xlApp.Quit();

                Marshal.ReleaseComObject(xlWorkSheet);
                Marshal.ReleaseComObject(xlWorkBook);
                Marshal.ReleaseComObject(xlApp);

                xlWorkSheet = null;
                xlWorkBook = null;
                xlApp = null;

                GC.GetTotalMemory(false);
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                GC.GetTotalMemory(true);  

同时,我也这样做了:

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

                Marshal.FinalReleaseComObject(xlWorkSheet);

                xlWorkBook.Close(Type.Missing, Type.Missing, Type.Missing);
                Marshal.FinalReleaseComObject(xlWorkBook); 

                xlApp.Quit();
                Marshal.FinalReleaseComObject(xlApp); 

目前,我认为从Visual Studio 2008关闭Excel不可能。这可能是一个错误或其他原因,但我已经尝试了前20个网站,结果都一样:由于某种原因,Excel会打开两个实例,当我进行垃圾回收等操作时(或不进行),它只会关闭一个实例。

当我尝试打开文件时,它会显示错误或损坏。

当我进入任务管理器并结束Excel进程后,文件可以正常打开。

是否有办法在Visual Studio 2008中关闭Excel?如果有,请提供指导或解决方案。


4
请勿调用GC.Collection()... http://blogs.msdn.com/b/ricom/archive/2004/11/29/271829.aspx - Erik Philips
1
这篇文章非常好地回答了你的问题。 - Jay Riggs
@JayRiggs 但是Erik明确表示不要使用gc.collect。 - Alex Gordon
@ErikPhilips,Jay引用了某人的话,建议使用gc.collection两次! - Alex Gordon
@JayRiggs 我不同意:FinalReleaseComObject 几乎总是不好的选择。另一方面,严格控制生命周期可以很好地与ReleaseComObject配合使用。如果无法确保这一点,那么请务必只使用GC(但不要使用GC.Collect“hack”,这意味着生命周期很重要,应该明确地进行控制)。有关详细信息和我的理由,请参见https://dev59.com/xWHVa4cB1Zd3GeqPnor7#9709506。 - user166390
显示剩余10条评论
2个回答

23

首先,我将介绍一个修改过的releaseObject函数,然后提供一种使用它的模式。

using Marshal = System.Runtime.InteropServices.Marshal;
private void releaseObject(ref object obj) // note ref!
{
    // Do not catch an exception from this.
    // You may want to remove these guards depending on
    // what you think the semantics should be.
    if (obj != null && Marshal.IsComObject(obj)) {
        Marshal.ReleaseComObject(obj);
    }
    // Since passed "by ref" this assingment will be useful
    // (It was not useful in the original, and neither was the
    //  GC.Collect.)
    obj = null;
}

现在,使用的模式如下:
private void button1_Click(object sender, EventArgs e)
{
    // Declare. Assign a value to avoid a compiler error.
    Excel.Application xlApp = null;
    Excel.Workbook xlWorkBook = null;
    Excel.Worksheet xlWorkSheet = null;

    try {
        // Initialize
        xlApp = new Excel.ApplicationClass();
        xlWorkBook = xlApp.Workbooks.Open(myBigFile, 0, true, 5, "", "", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t", true, false, 0, true, 1, 0);
        // If the cast fails this like could "leak" a COM RCW
        // Since this "should never happen" I wouldn't worry about it.
        xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
        ...
    } finally {
        // Release all COM RCWs.
        // The "releaseObject" will just "do nothing" if null is passed,
        // so no need to check to find out which need to be released.
        // The "finally" is run in all cases, even if there was an exception
        // in the "try". 
        // Note: passing "by ref" so afterwords "xlWorkSheet" will
        // evaluate to null. See "releaseObject".
        releaseObject(ref xlWorkSheet);
        releaseObject(ref xlWorkBook);
        // The Quit is done in the finally because we always
        // want to quit. It is no different than releasing RCWs.
        if (xlApp != null) {
            xlApp.Quit();
        }
        releaseObject(ref xlApp);    
    }
}

这种简单的方法可以在大多数情况下进行扩展/嵌套。我使用一个实现了IDisposable接口的自定义包装类,使得这个任务更容易。

1
非常感谢您的帮助!我在所有releaseObject调用中都遇到了这个错误:错误1:属性或索引器不能作为out或ref参数传递。 - Alex Gordon
是的,在C#中,ref只能与变量一起使用。在我的项目中,我有两个“releaseObject”方法。一个使用ref(我用它来处理所有变量),另一个不使用“ref”(我很少用于某些属性)。如果不使用“ref”,则只需知道您无法更改调用者中传递的值。例如,需要使用realeaseObject(ref variable)releaseObjectNonRef(Property); Property = null; - user166390
我通过删除 REF 已经将其编译成功。 - Alex Gordon
哦,我明白了。不,它从未关闭。 - Alex Gordon
@I__ 然后尝试将其缩减到能够正确关闭的点......例如只是 xlApp,只是xlWorkBook,只是 xlWorkSheet......希望这样问题可以被隔离。 - user166390
显示剩余12条评论

4

请确认代码中存在的两个问题:

  • 程序关闭后,Excel仍作为一个运行进程保留在系统中;
  • 打开由程序创建的Excel文件时,Excel会显示文件已损坏或其他错误信息。

我将你在编辑问题中的button1点击处理程序和pst的releaseObject方法复制到了一个干净的VS2008、C#3.5 Winform应用程序中,并做了一些细微的修改,以消除我列出的这两个问题。

要解决Excel无法卸载的问题,请在创建的range对象上调用releaseObject方法。在调用releaseObject(xlWorkSheet);之前完成此操作即可。COM互操作编程的乐趣就在于要记住所有这些引用。

要解决损坏的Excel文件问题,请更新WorkBook.SaveAs方法调用,将第二个参数(Excel.XlFileFormat.xlXMLSpreadsheet)替换为Type.MissingSaveAs方法将默认正确地处理此问题。

我相信你在问题中发布的代码是简化的,以帮助排除你遇到的问题。你应该使用pst演示的try...finally块。


非常感谢您!我现在正在尝试它。但是请问为什么我们要使用xlXMLspreadsheet?我不想它成为XML格式。 - Alex Gordon
|_ 不太确定我理解了 - 我从你的代码中得到了XML格式规范,来自于 xlWorkBook.SaveAs 方法调用。我建议不要使用 Type.Missing 代替它。 - Jay Riggs
抱歉,我的意思是我想以完全相同的格式保存它。 - Alex Gordon
我不确定你该如何做。有一个Excel Workbook.FileFormat 属性 可以检索其值,并且在 SaveAs 新文件时可能会用到。 - Jay Riggs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接