解决在IIS中调用Microsoft Office Excel组件后进程无法正常退出的问题

来源:http://www.cnblogs.com/ahui/archive/2013/03/05/2944441.html

有一个项目用到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+资源,也得显示释放资源

代码如下:

  1 public class ExcelApp : IDisposable
  2 {
  3     private Application _excel;
  4     private Workbooks _workbooks;
  5     private Workbook _workbook;
  6     private Worksheet _worksheet;
  7 
  8     public ExcelApp()
  9     {
 10         _excel = new ApplicationClass { Visible = false, DisplayAlerts = false };
 11         _workbooks = _excel.Workbooks;
 12     }
 13 
 14     public int ColCount { get; set; }
 15 
 16     public int RowCount { get; set; }
 17 
 18     public string WorksheetName
 19     {
 20         get
 21         {
 22             return _worksheet != null ? _worksheet.Name : null;
 23         }
 24         set
 25         {
 26             if (_worksheet != null)
 27             {
 28                 _worksheet.Name = value;
 29             }
 30         }
 31     }
 32 
 33     #region Get Excel Range
 34     public Range GetCell(int rowIndex, int cellIndex)
 35     {
 36         Range cells = null;
 37         Range range = null;
 38 
 39         try
 40         {
 41             cells = _excel.Cells;
 42             range = (Range)cells[1 + rowIndex, 1 + cellIndex];
 43         }
 44         finally
 45         {
 46             Marshal.ReleaseComObject(cells);
 47         }
 48 
 49         return range;
 50     }
 51 
 52     public Range GetColumn(int cellIndex)
 53     {
 54         Range range = null;
 55         Range cells = null;
 56         object rangeX = null;
 57         object rangeY = null;
 58 
 59         try
 60         {
 61             cells = _excel.Cells;
 62             rangeX = cells[1, 1 + cellIndex];
 63             rangeY = cells[RowCount, 1 + cellIndex];
 64             range = _worksheet.get_Range(rangeX, rangeY);
 65         }
 66         finally
 67         {
 68             Marshal.ReleaseComObject(rangeX);
 69             Marshal.ReleaseComObject(rangeY);
 70             Marshal.ReleaseComObject(cells);
 71         }
 72 
 73         return range;
 74     }
 75 
 76     public Range GetRange(int xRowIndex, int xCellIndex, int yRowIndex, int yCellIndex)
 77     {
 78         Range range = null;
 79         Range cells = null;
 80         object rangeX = null;
 81         object rangeY = null;
 82 
 83         try
 84         {
 85             cells = _excel.Cells;
 86             rangeX = cells[1 + xRowIndex, 1 + xCellIndex];
 87             rangeY = cells[yRowIndex + 1, yCellIndex + 1];
 88             range = _worksheet.get_Range(rangeX, rangeY);
 89         }
 90         finally
 91         {
 92             Marshal.ReleaseComObject(rangeX);
 93             Marshal.ReleaseComObject(rangeY);
 94             Marshal.ReleaseComObject(cells);
 95         }
 96 
 97         return range;
 98     }
 99     #endregion
100 
101     public void Save(string fullFilePath)
102     {
103         if (string.IsNullOrEmpty(fullFilePath))
104         {
105             throw new ArgumentNullException("fullFilePath");
106         }
107 
108         string directory = Path.GetDirectoryName(fullFilePath);
109 
110         if (string.IsNullOrEmpty(directory))
111         {
112             throw new ArgumentException("fullFilePath is not a valid file path.");
113         }
114 
115         if (!Directory.Exists(directory))
116         {
117             Directory.CreateDirectory(directory);
118         }
119 
120         _workbook.SaveCopyAs(fullFilePath);
121     }
122 
123     public void Open(string fullFilePath)
124     {
125         _workbook = _workbooks._Open(fullFilePath,
126                                         Missing.Value, Missing.Value,
127                                         Missing.Value, Missing.Value,
128                                         Missing.Value, Missing.Value,
129                                         Missing.Value, Missing.Value,
130                                         Missing.Value, Missing.Value,
131                                         Missing.Value, Missing.Value);
132 
133         _worksheet = (Worksheet)_workbook.ActiveSheet;
134 
135         ColCount = 0;
136         RowCount = 0;
137     }
138 
139     public void AddWorkbook()
140     {
141         _workbook = _workbooks.Add(true);
142         _worksheet = (Worksheet)_workbook.ActiveSheet;
143 
144         ColCount = 0;
145         RowCount = 0;
146     }
147 
148     public void Reset()
149     {
150         Close();
151         AddWorkbook();
152     }
153 
154     private void Close()
155     {
156         if (_worksheet != null)
157         {
158             Marshal.ReleaseComObject(_worksheet);
159         }
160         if (_workbook != null)
161         {
162             _workbook.Close(false, null, null);
163             Marshal.ReleaseComObject(_workbook);
164         }
165         _worksheet = null;
166         _workbook = null;
167     }
168 
169     #region IDisposable Members
170 
171     public void Dispose()
172     {
173         try
174         {
175             Close();
176 
177             if (_workbooks != null)
178             {
179                 Marshal.ReleaseComObject(_workbooks);
180             }
181             if (_excel != null)
182             {
183                 _excel.Quit();
184                 Marshal.ReleaseComObject(_excel);
185             }
186         }
187         catch (Exception ex)
188         {
189             Console.WriteLine("dispose ExcelApp object failed", ex);
190         }
191 
192         _workbooks = null;
193         _excel = null;
194     }
195 
196     #endregion
197 }
198 
199 
200 public class Disposable
201 {
202     public static Disposable<T> Create<T>(T o) where T : class
203     {
204         return new Disposable<T>(o);
205     }
206 }
207 
208 public class Disposable<T> : IDisposable where T : class
209 {
210     public T Value;
211 
212     internal Disposable(T o)
213     {
214         Value = o;
215     }
216 
217     public void Dispose()
218     {
219         if (Value != null)
220         {
221             Marshal.ReleaseComObject(Value);
222         }
223     }
224 }
复制代码
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);
        }
    }
}
复制代码

调用示例:

 1 using (var excel = new ExcelApp())
 2 {
 3     excel.AddWorkbook();
 4 
 5     using (var range = Disposable.Create(excel.GetCell(0, 0)))
 6     {
 7         using (var font = Disposable.Create(range.Value.Font))
 8         {
 9             font.Value.Color = 255;
10         }
11 
12         range.Value.Value = 200;
13     }
14 }
复制代码
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不能退出的问题总算圆满解决了,贴出来和大家分享一下,免得其他人也在这上面浪费时间

原文地址:https://www.cnblogs.com/spriteflk/p/4754516.html