清理Excel Interop

时间:2013-05-28 20:05:33

标签: c# winforms office-interop

所以我有一个Winforms应用程序需要从Excel工作表中读取以填充某些字段。为了使UI响应,我决定创建一个从Excel工作表中读取的线程,这样主线程就不必等待了。如果读取完成,然后应用程序退出,EXCEL.EXE表现良好并退出。但是,如果在读取仍在进行时关闭主应用程序,则EXCEL.EXE任务保持活动状态。

我猜是因为ExcelReader没有时间在关闭前调用destructor

一种可能的解决方案是在其ExcelReader.Cleanup事件中调用主要表单FormClosing。但这似乎是对封装的可怕违反。

还有哪些其他可能的解决方案?这是我的ExcelReader代码:

using Excel = Microsoft.Office.Interop.Excel;

class ExcelReader
{
    private int sheetNum { get ; set; }
    public int rowCount { get; private set; }
    public int colCount { get; private set; }
    public List<string> sheetValues { get; private set; }
    public List<string> sheetNames { get; private set; }

    Excel.Application xlApp;

    Excel.Workbooks workBooks;
    Excel.Workbook xlWorkbook;

    Excel.Worksheet xlWorkSheet;
    Excel.Range xlRange;

    Excel.Range row;
    Excel.Range col;


    public ExcelReader(string path){
        //initialize values
        this.sheetNum = 1;
        sheetNames = new List<string>();
        sheetValues = new List<string>();

        //read from excel blackmagic here
        xlApp = new Excel.Application();

        workBooks = xlApp.Workbooks;
        xlWorkbook = workBooks.Open(path);

        xlWorkSheet = xlWorkbook.Sheets[sheetNum];
        xlRange = xlWorkSheet.UsedRange;

        row = xlRange.Rows;
        col = xlRange.Columns;

        int rowCount = row.Count;
        int colCount = col.Count;            

        this.getSheetNames(xlWorkbook);
        this.getValues(xlRange, rowCount, colCount);

        CleanUp();
    }

    ~ExcelReader()
    {
        CleanUp();
    }

    private void getSheetNames(Excel.Workbook xlWorkbook)
    {
        var workSheets = xlWorkbook.Sheets;
        int numberOfSheets = workSheets.Count;
        for (int i = 1; i < numberOfSheets+1; i++)
        {
            sheetNames.Add(xlWorkbook.Sheets[i].Name);
        }
        Marshal.FinalReleaseComObject(workSheets);
    }

    private void getValues(Excel.Range xlRange, int rowCount, int colCount)
    {
        for (int i = 1; i < rowCount; i++)
        {
            for (int j = 1; j < colCount; j++)
            {
                var cells = xlRange.Cells[i, j];
                var value = cells.Value2;
                sheetValues.Add(value);
                Marshal.FinalReleaseComObject(cells);
            }
        }
    }

    private void CleanUp()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();

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

        col.Clear();
        row.Clear();
        Marshal.FinalReleaseComObject(col);
        Marshal.FinalReleaseComObject(row);

        xlRange.Clear();
        Marshal.FinalReleaseComObject(xlRange);

        //close book without saving
        xlWorkbook.Close(false);
        workBooks.Close();
        Marshal.FinalReleaseComObject(xlWorkbook);
        Marshal.FinalReleaseComObject(workBooks);

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

1 个答案:

答案 0 :(得分:2)

在Interop会话期间,在某些情况下关闭Excel可能会很痛苦。有时您可能需要使用Windows API调用来强制关闭您正在使用的Excel实例。

这是我用于在Interop中管理Excel的几个项目的类。这里的关键点是使用一个唯一的标题,在这种情况下是一个GUID,用于标识您正在使用的Excel的特定实例,并使用此信息强制实例关闭,如果Quit方法不起作用

using System;
using System.Runtime.InteropServices;  
namespace ExcelSupport
{
    public class ExcelController : IDisposable
    {
        private Microsoft.Office.Interop.Excel.Application _ExcelApplication;
        private bool disposedValue = false;   // To detect redundant calls
        private string _caption; //used to uniquely identify hidden Excel instance
        //
        // Windows API used to help close Excel instance
        //
        [DllImport("user32.dll")] 
        private static extern int EndTask(IntPtr hWnd);
        [DllImport("user32.dll")]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        [DllImport("kernel32.dll")] 
        private static extern IntPtr SetLastError(int dwErrCode);

    public ExcelController()
    {
        OpenExcel(null);
    }

    public ExcelController(string workbookName)
    {
        OpenExcel(workbookName);
    }

    private void OpenExcel(string workbookName)
    {
        _ExcelApplication = new Microsoft.Office.Interop.Excel.Application();
        _caption = System.Guid.NewGuid().ToString().ToUpper();
        _ExcelApplication.Caption = _caption;
        _ExcelApplication.Visible = false;
        _ExcelApplication.DisplayAlerts = false;
        if(workbookName != null)
            _ExcelApplication.Workbooks.Open(workbookName);
    }

    public Microsoft.Office.Interop.Excel.Application Application
    {
        get { return _ExcelApplication; }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected void Dispose(bool disposing)
    {
        if (!this.disposedValue)
        {
            try
            {
                if (_ExcelApplication != null)
                {
                    foreach (Microsoft.Office.Interop.Excel.Workbook WorkBookName in _ExcelApplication.Workbooks)
                    {
                        WorkBookName.Close(false);
                    }
                    IntPtr iHandle = IntPtr.Zero;
                    iHandle = new IntPtr(_ExcelApplication.Parent.Hwnd);
                    _ExcelApplication.DisplayAlerts = false;
                    _ExcelApplication.Quit();
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(_ExcelApplication);
                    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(_ExcelApplication);
                    SetLastError(0);
                    if (IntPtr.Equals(iHandle, IntPtr.Zero))
                        iHandle = FindWindow(null, _caption);
                    if (!IntPtr.Equals(iHandle, IntPtr.Zero))
                    {
                        int iRes;
                        uint iProcId;
                        iRes = (int)GetWindowThreadProcessId(iHandle, out iProcId);
                        if (iProcId == 0)
                        {
                            if (EndTask(iHandle) == 0)
                                throw new ApplicationException("Excel Instance Could Not Be Closed");
                        }
                        else
                        {
                            System.Diagnostics.Process proc = System.Diagnostics.Process.GetProcessById((int)iProcId);
                            proc.CloseMainWindow();
                            proc.Refresh();
                            if (!proc.HasExited)
                                proc.Kill();

                        }
                    }
                }
            }
            finally
            {
                _ExcelApplication = null;
                GC.WaitForPendingFinalizers();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            }
        }
        this.disposedValue = true;
    }
}