簡體   English   中英

清理Excel Interop

[英]Cleaning up Excel 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);
    }
}

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM