如何在C#中关闭excel?

时间:2017-12-16 21:48:44

标签: c# excel

我正在尝试运行Excel宏并在运行后关闭excel 不幸的是它不起作用。我尝试了Stackoverflow的所有解决方案,无法获得可靠的解决方案。请帮忙。 你可以看到我试图关闭,相当,释放COM对象,但似乎没有任何工作。

public static bool RunMacro(string Path, string MacroName, bool Close, ProgressForm ProgressForm, params object[] Arguments)
{
    Microsoft.Office.Interop.Excel.Application aApplication = null;
    bool aCloseApplication = true;
    bool aResult = false;

    try
    {
        aApplication = (Microsoft.Office.Interop.Excel.Application)Marshal.GetActiveObject("Excel.Application");
        aCloseApplication = false;
    }
    catch (COMException aCOMException)
    {
        aApplication = new Microsoft.Office.Interop.Excel.Application();
        aApplication.Visible = false;
    }

    if (aApplication != null)
    {
        aApplication.ScreenUpdating = false;

        Microsoft.Office.Interop.Excel.Workbook aWorkbook = null;
        Microsoft.Office.Interop.Excel.Worksheet aWorksheet = null;
        bool aCloseWorkbook = true;

        try
        {
            if (IsEdited(aApplication))
            {
                throw new Exception("Excel is in cell edit mode. Please stop editing cell and run import again");
            }
            else
            {
                for (int i = 0; i < aApplication.Workbooks.Count; i++)
                    if (aApplication.Workbooks[i + 1].FullName == Path)
                    {
                        aWorkbook = aApplication.Workbooks[i + 1];
                        aCloseWorkbook = false;
                        break;
                    }

                if (aWorkbook == null)
                    aWorkbook = aApplication.Workbooks.Open(Path);

                // Run macro here
                aApplication.Run(string.Format("{0}!{1}", System.IO.Path.GetFileName(Path), MacroName), Arguments);

                aResult = true;
            }
        }
        finally
        {
            if (aWorksheet != null)
            {
                Marshal.ReleaseComObject(aWorksheet);
            }

            //does not work here!!! I want to close excel here 
            if (aWorkbook != null)
                aWorkbook.Close();
                aApplication.Quit();
                Marshal.ReleaseComObject(aWorkbook);
                Marshal.ReleaseComObject(aApplication);
        }
    }
    return aResult;
}

4 个答案:

答案 0 :(得分:3)

这是我在使用SSIS脚本任务刷新Excel文件时经常玩的东西。

我已经阅读了有关使用Marshal.ReleaseComObject的混合内容,但我也发现它并非必要。对我来说,最终解决方案如下:

using xl = Microsoft.Office.Interop.Excel;

...

public void Main()
{
    DoExcelWork();

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

private void DoExcelWork()
{
    xl.Application app = null;
    xl.Workbooks books = null;
    xl.Workbook book = null;

    try
    {
        app = new xl.Application() { DisplayAlerts = false };

        books = app.Workbooks;
        book = books.Open("filename goes here");

        book.RefreshAll();
        // this is where you would do your Excel work

        app.DisplayAlerts = false; // This is for reinforcement; the setting has been known to reset itself after a period of time has passed.
        book.SaveAs("save path goes here");
        app.DisplayAlerts = true;

        book.Close();
        app.Quit();
    }
    catch
    {
        if (book != null) book.Close(SaveChanges: false);
        if (app != null) app.Quit();
    }
}

我不确定您的应用程序是如何布局的,但在使用SSIS时,我发现有必要在声明Excel Interop对象的范围之外调用GC.Collect,以避免使用Excel实例在某些情况下保持打开状态,因此有两种方法。

您应该能够调整此代码以满足您的要求。

答案 1 :(得分:0)

三件事:

  • 手动或以编程方式关闭Excel。 Excel可能会弹出一个问题框,询问您是否要保存(即使Excel当前已隐藏)。实际上它必须关闭才能工作;如果您选择取消,它将不会被关闭。应该有一个重载,允许您忽略未保存的更改。

  • 将引用设置为null

  • 致电GC.Collect();

根据MSDN,一种阻止盒子阻止近距离尝试的方法是将DisplayAlerts设置为false。 (虽然你会失去变化。)

据我所知,Marshall你所做的事情是不必要的(根据this answer,它实际上很危险)。我特别做的是将interop对象包装在我自己的Excel对象中,该对象实现IDisposable。我的Dispose方法基本上看起来像这样(虽然我也做了一些其他的事情,比如总是Show Excel,如果它没有关闭,总是转回渲染和自动计算等。)

void Dispose() {
   if (mExcel != null) {
      mExcel = null;
      GC.Collect();
   }
}

并使用静态方法实现正确的使用模式:

public static void UseAndClose(Action<Excel> pAction) {
   using (var excel = new Excel()) {
      pAction(excel);
      excel.Close(false); // closes all workbooks and then calls `Quit`
   }
}

答案 2 :(得分:0)

您永远不应该在Marshal.ReleaseComObject内调用Marshal.FinalReleaseComObjectExcel Interop方法;这是一种不好的做法。

没有人(包括Microsoft)明确表示您必须手动释放COM引用,因为您的应用程序和互操作库应该自动处理此问题。即使您想继续手动发布COM个引用,一旦完成,您必须通过调用GC.Collect()GC.WaitForPendingFinalizers()确保在流程结束时清除它们(在旁注中,如果从调试器运行方法,则GC可能会失败,因为本地引用在方法结束之前一直处于kepy状态。)

这就是你要做的全部:

// let's make sure that the process doesn't hang because of alerts
aApplication.DisplayAlerts = false;

aWorkbook.Save();
aWorkbook.Close();

aApplication.Quit()
aApplication = null;

答案 3 :(得分:0)

如果你决定使用Marshal.ReleaseCOMObject技术(而不是GC.Collect),那么关键是确保你发布每一个RCW(COM对象),不只是一些

一个例子,基于您的原始代码:

public static bool RunMacro(string Path, string MacroName, bool Close, params object[] Arguments)
{
    Microsoft.Office.Interop.Excel.Application aApplication = null;
    bool aCloseApplication = true;
    bool aResult = false;

    try
    {
        aApplication = (Microsoft.Office.Interop.Excel.Application)Marshal.GetActiveObject("Excel.Application");
        aCloseApplication = false;
    }
    catch (COMException aCOMException)
    {
        aApplication = new Microsoft.Office.Interop.Excel.Application();
        aApplication.Visible = false;
    }

    if (aApplication != null)
    {
        aApplication.ScreenUpdating = false;

        Microsoft.Office.Interop.Excel.Workbook aWorkbook = null;
        Microsoft.Office.Interop.Excel.Worksheet aWorksheet = null;
        bool aCloseWorkbook = true;

        try
        {
            if (IsEdited(aApplication))
            {
                throw new Exception("Excel is in cell edit mode. Please stop editing cell and run import again");
            }
            else
            {
                var workbooks = aApplication.Workbooks;
                for (int i = 0; i < workbooks.Count; i++)
                {
                    var workbook = aApplication.Workbooks.Item[i + 1];
                    if (workbook.FullName == Path)
                    {
                        aWorkbook = workbook;
                        aCloseWorkbook = false;
                        break;
                    }
                    else
                    {
                        Marshal.ReleaseComObject(workbook);
                    }
                }

                if (aWorkbook == null)
                    aWorkbook = workbooks.Open(Path);

                Marshal.ReleaseComObject(workbooks);

                // Run macro here
                aApplication.Run(string.Format("{0}!{1}", System.IO.Path.GetFileName(Path), MacroName),
                    Arguments);

                aResult = true;
            }
        }
        finally
        {
            if (aWorksheet != null)
            {
                Marshal.ReleaseComObject(aWorksheet);
            }

            //does not work here!!! I want to close excel here 
            if (aWorkbook != null)
                aWorkbook.Close();
            aApplication.Quit();
            Marshal.ReleaseComObject(aWorkbook);
            Marshal.ReleaseComObject(aApplication);
        }
    }
    return aResult;
}

请注意,我已经介绍了一些额外的变量。 workbooksworkbook指向原始代码中未RCO的对象。

在决定使用此技术(使用ReleaseCOMObject)和其他技术(使用GC.Collect)方面,需要考虑以下因素:

  • ReleaseCOMObject需要更多纪律(确保您发布所有内容,请考虑double dot rule,意识到application.Workbooks["something]"实际上是application.Workbooks.Item["something"]的简写(因此ReleaseCOMObject等)等)
  • 如果在您的代码中很难跟踪所涉及对象的生命周期,那么
  • GC.Collect()会特别困难(因为您所需的生命周期基本上是函数范围 - 但它可以适用于某些场景)。正如two dots所述:
      

    如果您是使用适量COM对象的客户端应用程序   在您的托管代码中自由传递,您不应该使用   ReleaseComObject的。

  • GC.Collect();更简单(基本上只需致电GC.WaitForPendingFinalizers();Marshal.AreComObjectsAvailableForCleanup(),直到false返回GC.Collect)。
  • GC.Collect通常较慢(因为它需要GC 所有内容而不仅仅是Chris Brumme所涉及的内容。)
  • Marshal.AreComObjectsAvailableForCleanup(更具体地说,true)如果涉及多个线程可能会有问题(因为它可能会因其他RCW(来自其他线程)而返回GC.Collect,而不仅仅是你在这个范围内特别感兴趣的那些)。
  • GC.Collect可能会降低您的整体系统效果,因为它可能会为您的所有对象提供RCWs(通过将它们推送到以后的GC代,而不是以其他方式)。

请注意,如果您决定使用srand(),那么此处的其他答案非常适合提供有关如何执行此操作的一些指导。