解決在IIS中調用Microsoft Office Excel組件后進程無法正常退出的問題


有一個項目用到Excel組件產生報表,本以為這個通用功能是個很簡單的case,沒想到結果卻花了不少時間

本人開發環境: Win7 64bit + IIS7.5 + VS2012

 

最開始碰到的問題是NetworkService無法訪問com組件,需要對帳戶進行授權,這個相信很多人都碰到過,也很好解決

1.運行:mmc comexp.msc /32,找到我的電腦 -> DCom配置中的Microsoft Excel Application
2.在Microsoft Excel Application上點擊右鍵,選擇"屬性"
3.點擊"標識"標簽,選擇"交互式用戶"
4.點擊"安全"標簽,在"啟動和激活權限"上點擊"自定義",然后點擊對應的"編輯"按鈕,在彈出的"安全性"對話框中填加一個"NETWORK SERVICE"用戶(注意要選擇本計算機名),並給它賦予"本地啟動"和"本地激活"權限.
5.依然是"安全"標簽,在"訪問權限"上點擊"自定義",然后點擊"編輯",在彈出的"安全性"對話框中也填加一個"NETWORK SERVICE"用戶,然后賦予"本地訪問"權限.

 

之后程序能正常運行了,但這個項目需要並發處理,可能有多個ApplicationClass實例,這時問題就來了

調用_Application.Quit()之后,Excel.exe進程仍然存在,搜索之后,解決方案來了:

[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int proecessId);

public static void KillExcel(Excel.Application excel)
{
    var t = new IntPtr(excel.Hwnd);
    int proecessId = 0;
    GetWindowThreadProcessId(t, out proecessId);
    Process.GetProcessById(proecessId).Kill();
}

 

用VS調試正常工作,在Console應用里也OK,但如果用IIS啟動就不行了,斷點調用發現GetWindowThreadProcessId取得的processId一直為0。

搜索了一下網站,大概意思是運行在不同的Session級別,Excel是以"交互式用戶"啟動,即本地Administrator,所以取不到對應的進程Id,

於是又在Microsoft Excel Application的屬性->標識中將啟動用戶這項選中,發現這回Excel進程是以NetworkService啟動,測試代碼如下:

Application excel = new ApplicationClass { Visible = false, DisplayAlerts = false };
excel.Quit();
KillExcel(excel);

也能正常退出,好像問題已經解決了,OK,來編寫業務代碼了

Workbook workbook = excel.Workbooks.Add(true);

一調試,馬上報異常,繼續Google和看微軟官方網站,發現Office自動化必須以交互式用戶方式來啟動,而NetworkService是虛擬的,所以這條路顯然走不通。

即使你想到給NetworkService提升權限,也不清楚正常運行Excel倒底要那些權限,搜索后也無果,大部分推薦使用第三方組件,或者建立一個專用的帳戶,或者建立一個Service項目來處理Http請求。

這些方式各有各的不足,將建立專用域帳戶做一備選方案后,繼續尋求解決辦法,最先找到的代碼是:

Application excel = new ApplicationClass { Visible = false, DisplayAlerts = false };
Workbook workbook = excel.Workbooks.Add(true);
Worksheet worksheet = (Worksheet)workbook.ActiveSheet;
// do sth
excel.Quit();
Marshal.ReleaseComObject(worksheet);
workbook.Close(false, null, null);
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(excel);

這段代碼相信遇到這個問題的人都試過吧,結果還是不行,在IIS下面啟動的Excel進程不一定按你的要求及時退出

 

后來一位高手同事給了個鏈接 http://support.microsoft.com/kb/317109

測試代碼:

Application excel = new ApplicationClass { Visible = false, DisplayAlerts = false };
Workbooks workbooks = excel.Workbooks;
Workbook workbook = workbooks.Add(true);
Worksheet worksheet = (Worksheet)workbook.ActiveSheet;
// do sth
excel.Quit();
Marshal.ReleaseComObject(worksheet);
workbook.Close(false, null, null);
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);

這段代碼測試正常,看到希望了,繼續Coding,然后測試。

服務器運行一段時間后,仍然存在大量的Excel進程沒有退出,有心的人估計從兩段稍有差別的代碼看到問題的所在了,問題就在於:

調用com+對象后,必須要及時釋放資源,那怕你只是請求了某個屬性,只要這個屬性是com+資源,也得顯示釋放資源

代碼如下:

public class ExcelApp : IDisposable
{
    private Application _excel;
    private Workbooks _workbooks;
    private Workbook _workbook;
    private Worksheet _worksheet;

    public ExcelApp()
    {
        _excel = new ApplicationClass { Visible = false, DisplayAlerts = false };
        _workbooks = _excel.Workbooks;
    }

    public int ColCount { get; set; }

    public int RowCount { get; set; }

    public string WorksheetName
    {
        get
        {
            return _worksheet != null ? _worksheet.Name : null;
        }
        set
        {
            if (_worksheet != null)
            {
                _worksheet.Name = value;
            }
        }
    }

    #region Get Excel Range
    public Range GetCell(int rowIndex, int cellIndex)
    {
        Range cells = null;
        Range range = null;

        try
        {
            cells = _excel.Cells;
            range = (Range)cells[1 + rowIndex, 1 + cellIndex];
        }
        finally
        {
            Marshal.ReleaseComObject(cells);
        }

        return range;
    }

    public Range GetColumn(int cellIndex)
    {
        Range range = null;
        Range cells = null;
        object rangeX = null;
        object rangeY = null;

        try
        {
            cells = _excel.Cells;
            rangeX = cells[1, 1 + cellIndex];
            rangeY = cells[RowCount, 1 + cellIndex];
            range = _worksheet.get_Range(rangeX, rangeY);
        }
        finally
        {
            Marshal.ReleaseComObject(rangeX);
            Marshal.ReleaseComObject(rangeY);
            Marshal.ReleaseComObject(cells);
        }

        return range;
    }

    public Range GetRange(int xRowIndex, int xCellIndex, int yRowIndex, int yCellIndex)
    {
        Range range = null;
        Range cells = null;
        object rangeX = null;
        object rangeY = null;

        try
        {
            cells = _excel.Cells;
            rangeX = cells[1 + xRowIndex, 1 + xCellIndex];
            rangeY = cells[yRowIndex + 1, yCellIndex + 1];
            range = _worksheet.get_Range(rangeX, rangeY);
        }
        finally
        {
            Marshal.ReleaseComObject(rangeX);
            Marshal.ReleaseComObject(rangeY);
            Marshal.ReleaseComObject(cells);
        }

        return range;
    }
    #endregion

    public void Save(string fullFilePath)
    {
        if (string.IsNullOrEmpty(fullFilePath))
        {
            throw new ArgumentNullException("fullFilePath");
        }

        string directory = Path.GetDirectoryName(fullFilePath);

        if (string.IsNullOrEmpty(directory))
        {
            throw new ArgumentException("fullFilePath is not a valid file path.");
        }

        if (!Directory.Exists(directory))
        {
            Directory.CreateDirectory(directory);
        }

        _workbook.SaveCopyAs(fullFilePath);
    }

    public void Open(string fullFilePath)
    {
        _workbook = _workbooks._Open(fullFilePath,
                                        Missing.Value, Missing.Value,
                                        Missing.Value, Missing.Value,
                                        Missing.Value, Missing.Value,
                                        Missing.Value, Missing.Value,
                                        Missing.Value, Missing.Value,
                                        Missing.Value, Missing.Value);

        _worksheet = (Worksheet)_workbook.ActiveSheet;

        ColCount = 0;
        RowCount = 0;
    }

    public void AddWorkbook()
    {
        _workbook = _workbooks.Add(true);
        _worksheet = (Worksheet)_workbook.ActiveSheet;

        ColCount = 0;
        RowCount = 0;
    }

    public void Reset()
    {
        Close();
        AddWorkbook();
    }

    private void Close()
    {
        if (_worksheet != null)
        {
            Marshal.ReleaseComObject(_worksheet);
        }
        if (_workbook != null)
        {
            _workbook.Close(false, null, null);
            Marshal.ReleaseComObject(_workbook);
        }
        _worksheet = null;
        _workbook = null;
    }

    #region IDisposable Members

    public void Dispose()
    {
        try
        {
            Close();

            if (_workbooks != null)
            {
                Marshal.ReleaseComObject(_workbooks);
            }
            if (_excel != null)
            {
                _excel.Quit();
                Marshal.ReleaseComObject(_excel);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("dispose ExcelApp object failed", ex);
        }

        _workbooks = null;
        _excel = null;
    }

    #endregion
}


public class Disposable
{
    public static Disposable<T> Create<T>(T o) where T : class
    {
        return new Disposable<T>(o);
    }
}

public class Disposable<T> : IDisposable where T : class
{
    public T Value;

    internal Disposable(T o)
    {
        Value = o;
    }

    public void Dispose()
    {
        if (Value != null)
        {
            Marshal.ReleaseComObject(Value);
        }
    }
}
View Code

調用示例:

View Code
using (var excel = new ExcelApp())
{
    excel.AddWorkbook();

    using (var range = Disposable.Create(excel.GetCell(0, 0)))
    {
        using (var font = Disposable.Create(range.Value.Font))
        {
            font.Value.Color = 255;
        }

        range.Value.Value = 200;
    }
}

至此在IIS里調用Excel不能退出的問題總算圓滿解決了,貼出來和大家分享一下,免得其他人也在這上面浪費時間

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM