Excel互操作COM不会关闭

时间:2012-11-21 16:06:05

标签: c# excel com

我有一些代码可以打开电子表格,读取一些值,然后关闭工作表。我需要为多个文件执行此操作。我遇到的问题是Excel应用程序实例没有退出,因此当我运行几个文件的进程时,我最终运行了几个excel.exe进程。我有什么想法让Excel关闭?

    static void ParseFile(string file)
    {
        try
        {
            System.Console.WriteLine("parsing:" + file);
            Excel.Application excel = new Excel.Application();
            Excel.Workbook wb = excel.Workbooks.Open(file);
            Excel.Worksheet ws = wb.Worksheets[1];
            for (int i = 2; i < 27; i++)
            {
                log(ws.Cells[i, 1].Text);
            }
            wb.Close(false);                
            excel.Quit();
            excel = null;
            ws = null;
            wb = null;
        }
        catch (Exception ex)
        {
            log(ex.Message);
        }
    }

------更新12/11/12 --------仍然将excel实例打开-------

static void ParseFile(string file)
    {
        try
        {
            log("parsing:" + file);
            Excel.Application excel = new Excel.Application();
            Excel.Workbook wb = excel.Workbooks.Open(file);
            Excel.Worksheet ws = wb.Worksheets[1];
    //do some stuff here
            wb.Close(false);
            excel.Quit();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Marshal.FinalReleaseComObject(ws);
            Marshal.FinalReleaseComObject(wb);
            Marshal.FinalReleaseComObject(excel);                
            excel = null;
            ws = null;
            wb = null;
            System.GC.Collect();
        }
        catch (Exception ex)
        {
            log(ex.Message);
        }            
    }

4 个答案:

答案 0 :(得分:3)

  1. 使用Marshal.FinalReleaseComObject()释放资源

  2. 如果您一个接一个地读取多个文件,那么在性能方面,最好只打开/关闭Excel 应用程序一次并将打开/关闭应用程序部分移动到单独的函数

  3. 重构代码:

    static Excel.Application OpenExcel(){
        Excel.Application excel = null;
        try{
            excel = new Excel.Application();
            return excel;
        }
        catch(Exception ex){
            log(ex.Message);
            return null;
        }
    }
    
    static void ParseFile(string file)
    {
        try
        {
            System.Console.WriteLine("parsing:" + file);            
            Excel.Workbook wb = excel.Workbooks.Open(file);
            Excel.Worksheet ws = wb.Worksheets[1];
            for (int i = 2; i < 27; i++)
            {
                log(ws.Cells[i, 1].Text);
            }
            wb.Close(false);    
        }
        catch (Exception ex)
        {
            log(ex.Message);
        }
        finally{
            Marshal.FinalReleaseComObject(ws);
            Marshal.FinalReleaseComObject(wb);
            ws = null;
            wb = null;
        }
    }
    
    static void CloseExcel(Excel.Application excel){
        try{
            excel.Quit();
        }
        catch(Exception ex){
            log(ex.Message);
        }
        finally{
            Marshal.FinalReleaseComObject(excel);
            excel = null;
        }
    }
    

    用法:

    Excel.Application excel = OpenExcel();
    if(excel != null){
        // Parse files in a loop
        ParseFile("fileName");
    
        // Close excel after parsing all files
        CloseExcel(excel);
    }
    

答案 1 :(得分:2)

您可以在实现IDisposable的实际COM对象周围使用包装器对象,以便它可以与C#'s using statement一起使用。

这样做的好处是它可以提升可读代码,并且它适用于任何COM对象。以下是运行时调度的示例:

using System;
using System.Reflection;
using System.Runtime.InteropServices;

public class ComRef<T> : IDisposable where T : class
{
    private T reference;

    public T Reference
    {
        get
        {
            return reference;
        }
    }

    public ComRef(T o)
    {
        reference = o;
    }

    public void Dispose()
    {
        if (reference != null)
        {
            Marshal.ReleaseComObject(reference);
            reference = null;
        }
    }
}

public class Test
{
    static void Main()
    {
        Type excelAppType = Type.GetTypeFromProgID("Excel.Application");
        using (var comRef = new ComRef<object>(Activator.CreateInstance(excelAppType)))
        {
            var excel = comRef.Reference;
            // ...
            excel.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, excel, null);
        }
    }
}

但是,如果您已经导入了Excel的类型库或任何其他类型的库,那么您可能需要更友好的东西:

public class CoClassComRef<T> : ComRef<T> where T : class, new()
{
    public CoClassComRef() : base(new T())
    {
    }
}

public class Test
{
    static void Main()
    {
        using (var comRef = new CoClassComRef<Excel.Application>())
        {
            var excel = comRef.Reference;
            // ...
            excel.Quit();
        }
    }
}

您应该确保不会将comRef.Reference捕获到比using语句更长的某个字段或变量。

请注意,我没有过多考虑线程安全性和正确的Dispose implementation。如果您只使用ComRef using语句,则线程安全并不重要。正确的Dispose实现会与终结器进行协调,但这里没有必要,因为using基本上是try-finally。如果您在ComRef语句中使用using并且未调用Dispose,则ComRef将被简单地进行垃圾收集,并使用它来获取基础COM对象,这将是如果只有ComRef引用它,则会被释放。

最后,我没有使用Marshal.FinalReleaseComObject,因为当您绝对确定要释放底层COM对象时(至少来自托管的所有引用)环境)无论它有多少次(重新)进入管理世界。但是,如果你感到幸运,你可能只是创建一个新的构造函数,它也会收到一个布尔说明是否应该调用FinalReleaseComObject而不是ReleaseComObject。网络搜索任何这些方法的第一个结果将指向文章和博客文章,详细说明为什么它们通常是邪恶的,比另一个更多。

答案 2 :(得分:2)

结束杀死进程,这是唯一有效的方法。

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    /// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
    /// <param name="hWnd">Handle to the main window of the process.</param>
    /// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
    public static bool TryKillProcessByMainWindowHwnd(int hWnd)
    {
        uint processID;
        GetWindowThreadProcessId((IntPtr)hWnd, out processID);
        if (processID == 0) return false;
        try
        {
            Process.GetProcessById((int)processID).Kill();
        }
        catch (ArgumentException)
        {
            return false;
        }
        catch (Exception ex)
        {
            return false;
        }            
        return true;
    }

    static void ParseFile(string file)
    {
        try
        {
            log("parsing:" + file);
            Excel.Application excel = new Excel.Application();
            Excel.Workbook wb = excel.Workbooks.Open(file);
            Excel.Worksheet ws = wb.Worksheets[1];
            //do some stuff here
            wb.Close(false);
            int hWnd = excel.Application.Hwnd;
            excel.Quit();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Marshal.FinalReleaseComObject(ws);
            Marshal.FinalReleaseComObject(wb);
            Marshal.FinalReleaseComObject(excel);                
            excel = null;
            ws = null;
            wb = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            TryKillProcessByMainWindowHwnd(hWnd);
        }
        catch (Exception ex)
        {
            log(ex.Message);
        }            
    }

答案 3 :(得分:1)

我想就这个问题分享一些经验。

按照此网页中所述的“规则”,我重新构造了Excel Interop代码,以确保我的财产期限永远不超过一个: http://jake.ginnivan.net/vsto-com-interop/

例如:

  • 我删除了如下字符串...

ExcelApplication.ExcelWorkbook.Workbookworksheet.WorksheetRange.Range.Count(有点夸张)。

-我用以下内容替换了这些字符串...

var CurrentRange = currentMDL.CurrentMDL_xlRange;
var CurrentRangeColumns = CurrentRange.Columns;
var CurrentRangeWorksheet = currentMDL.CurrentMDL_Worksheet;
var CurrentRangeWorksheetCells = CurrentRangeWorksheet.Cells;

从这里,我可以更轻松地利用我想要的东西。

例如:

for(int i = 1; i <= CurrentRangeColumns.Count; i++)
{
//Doing stuff
}

在完成所有操作之后,我确保以打开文档的相同方法关闭Excel文档。我打算重新访问此程序,以查看是否能够远程关闭Excel文档。

最后,我确保跟进Excel处理方法中使用的所有COM对象的某些发行版。

例如:

Marshal.FinalReleaseComObject(CurrentRange);
Marshal.FinalReleaseComObject(CurrentRangeCells);
Marshal.FinalReleaseComObject(CurrentRangeRows);

这里的操作顺序很重要。我确保先关闭工作簿,然后关闭Excel应用程序,然后最后释放我的COM对象。我曾与一位合作的工程师谈过ComObject版本的用法。他说我不需要使用这些调用,因为垃圾回收最终将清理我的混乱情况。通过这里的学习,我无法进行垃圾回收来关闭我的Excel实例并选择自己释放它们。

-克里斯