简体   繁体   English

清理Excel Interop

[英]Cleaning up Excel Interop

So I have a Winforms application that needs to read from an Excel sheet to populate some fields. 所以我有一个Winforms应用程序,需要从Excel工作表中读取以填充一些字段。 To make the UI responsive, I decided to create a thread to read from the Excel sheet so the main thread does not have to wait. 为了使UI响应,我决定创建一个从Excel工作表中读取的线程,这样主线程就不必等待了。 If the read finishes, and then the application exits, EXCEL.EXE behaves nicely and exits. 如果读取完成,然后应用程序退出,EXCEL.EXE表现良好并退出。 But if I close the main application while the read is still ongoing, then EXCEL.EXE task stays alive. 但是如果我在读取仍在进行时关闭主应用程序,则EXCEL.EXE任务保持活动状态。

I'm guessing that is is because ExcelReader does not have the time to call the destructor before it closes? 我猜这是因为ExcelReader没有时间在它关闭之前调用destructor

One possible solution would be to have the main form call ExcelReader.Cleanup in it's FormClosing event. 一个可能的解决办法是有主窗体调用ExcelReader.Cleanup在它FormClosing事件。 But that seems to be a horrible violation of encapsulation. 但这似乎是对封装的可怕违反。

What other possible solutions are there to this? 还有什么其他可能的解决方案? Here's my code for the ExcelReader: 这是我的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);
    }
}

Excel can be a pain to close under certain circumstances during an Interop session. 在Interop会话期间,Excel在某些情况下可能会很难关闭。 Sometimes you may need to use Windows API calls to force the instance of Excel you're using to close. 有时您可能需要使用Windows API调用来强制关闭您正在使用的Excel实例。

Here is a class I've used on several projects to manage Excel in Interop. 这是我在几个项目中使用的类,用于在Interop中管理Excel。 The key points here are to use a unique caption, in this case a GUID, to identify the specific instance of Excel you're working with and to use this information to force the instance to close if the Quit method doesn't do the trick. 这里的关键点是使用一个唯一的标题,在这种情况下是一个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