簡體   English   中英

Excel互操作COM不會關閉

[英]Excel interop COM doesn't close

我有一些代碼可以打開電子表格,讀取一些值,然后關閉工作表。 我需要為多個文件執行此操作。 我遇到的問題是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);
        }            
    }
  1. 使用Marshal.FinalReleaseComObject()釋放資源

  2. 如果您一個接一個地讀取多個文件,那么性能方面最好只打開/關閉Excel 應用程序一次並將打開/關閉應用程序部分移動到單獨的功能

重構代碼:

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

您可以在實現IDisposable的實際COM對象周圍使用包裝器對象,以便它可以與C#的using語句一起using

這樣做的好處是它可以提升可讀代碼,並且它適用於任何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語句comRef.Reference的某個字段或變量。

請注意,我沒有考慮過線程安全性和正確的Dispose實現 如果只使用帶有using語句的ComRef ,則線程安全並不重要。 一個適當的Dispose實現可以與終結器一起使用,但是這里沒有必要,因為using基本上是try-finally 如果你沒有在using語句中使用ComRef並且沒有調用Dispose ,那么ComRef將被簡單地進行垃圾收集,並且使用底層COM對象,如果只有這個ComRef引用它,它將被釋放。

最后,我沒有使用Marshal.FinalReleaseComObject ,因為當您完全確定要釋放底層COM對象(至少是來自托管環境的所有引用)時,無論它有多少次(重新),都會使用它。進入管理世界。 但是,如果你覺得幸運,你可能只是創建一個新的構造函數,它也接收一個布爾表示是否應該調用FinalReleaseComObject而不是ReleaseComObject 在網絡上搜索任何這些方法的第一個結果將指向文章和博客文章,詳細說明為什么它們通常是邪惡的,比另一個更多。

結束殺死進程,這是唯一有效的方法。

    [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);
        }            
    }

我想分享一些關於同樣問題的經驗。

我重新構建了我的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實例並選擇自己發布它們。

-克里斯

暫無
暫無

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

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