我遇到了Excel Interop的问题。
即使我重新发布实例,Excel.exe也不会关闭。
这是我的代码:
using xl = Microsoft.Office.Interop.Excel;
xl.Application excel = new xl.Application();
excel.Visible = true;
excel.ScreenUpdating = false;
if (wordFile.Contains(".csv") || wordFile.Contains(".xls"))
{
//typeExcel become a string of the document name
string typeExcel = wordFile.ToString();
xl.Workbook workbook = excel.Workbooks.Open(typeExcel,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing);
object outputFileName = null;
if (wordFile.Contains(".xls"))
{
outputFileName = wordFile.Replace(".xls", ".pdf");
}
else if (wordFile.Contains(".csv"))
{
outputFileName = wordFile.Replace(".csv", ".pdf");
}
workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outputFileName,
XlFixedFormatQuality.xlQualityStandard, oMissing,
oMissing, oMissing, oMissing, oMissing, oMissing);
object saveChanges = xl.XlSaveAction.xlDoNotSaveChanges;
((xl._Workbook)workbook).Close(saveChanges, oMissing, oMissing);
Marshal.ReleaseComObject(workbook);
workbook = null;
}
我看到了Marshal.RealeaseComObject
它应该是有用的,但没有。
我该如何解决这个问题?
谢谢。
答案 0 :(得分:63)
简单规则:避免使用双点调用表达式,例如:
var workbook = excel.Workbooks.Open(/*params*/)
...因为通过这种方式,您不仅可以为workbook
创建RCW个对象,还可以为Workbooks
创建{{3}}个对象,并且您也应该将其释放(如果引用的话,这是不可能的)该对象未得到维护。)
所以,正确的方法是:
var workbooks = excel.Workbooks;
var workbook = workbooks.Open(/*params*/)
//business logic here
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);
答案 1 :(得分:16)
这是我写的一段代码,因为我遇到了和你一样的问题。基本上,您需要关闭工作簿,退出应用程序,然后释放所有COM对象(而不仅仅是Excel应用程序对象)。最后,调用垃圾收集器进行测量。
/// <summary>
/// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
/// </summary>
public void Dispose()
{
// Cleanup
xWorkbook.Close(false);
xApp.Quit();
// Manual disposal because of COM
while (Marshal.ReleaseComObject(xApp) != 0) { }
while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
while (Marshal.ReleaseComObject(xCharts) != 0) { }
while (Marshal.ReleaseComObject(xMyChart) != 0) { }
while (Marshal.ReleaseComObject(xGraph) != 0) { }
while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
while (Marshal.ReleaseComObject(xSeries) != 0) { }
xApp = null;
xWorkbook = null;
xWorksheets = null;
xWorksheet = null;
xCharts = null;
xMyChart = null;
xGraph = null;
xSeriesColl = null;
xSeries = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
答案 2 :(得分:9)
规则 - 永远不要再使用那一个点
- 一个点
var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);
- 两个或更多点
(Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);
- 示例
using Microsoft.Office.Interop.Excel;
Application xls = null;
Workbooks workBooks = null;
Workbook workBook = null;
Sheets sheets = null;
Worksheet workSheet1 = null;
Worksheet workSheet2 = null;
workBooks = xls.Workbooks;
workBook = workBooks.Open(workSpaceFile);
sheets = workBook.Worksheets;
workSheet1 = (Worksheet)sheets[1];
// removing from Memory
if (xls != null)
{
foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
{
ReleaseObject(sheet);
}
ReleaseObject(sheets);
workBook.Close();
ReleaseObject(workBook);
ReleaseObject(workBooks);
xls.Application.Quit(); // THIS IS WHAT IS CAUSES EXCEL TO CLOSE
xls.Quit();
ReleaseObject(xls);
sheets = null;
workBook = null;
workBooks = null;
xls = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
答案 3 :(得分:7)
在您的代码中,您有:
excel.Workbooks.Open(...)
excel.Workbooks
正在创建一个COM对象。然后,您将从该COM对象调用Open
函数。但是,在完成后,您不会释放COM对象。
这是处理COM对象时的常见问题。基本上,表达式中不应该有多个点,因为在完成后需要清理COM对象。
这个话题太大了,无法在答案中完全探索,但我认为你会发现Jake Ginnivan关于这个主题的文章非常有帮助:VSTO and COM Interop
如果您厌倦了所有这些ReleaseComObject调用,您可能会发现这个问题很有用:
How to properly clean up Excel interop object in C#, 2012 edition
答案 4 :(得分:6)
摆脱所有引用是很棘手的,因为你必须猜测是否有这样的调用:
var workbook = excel.Workbooks.Open("")
创建一个Workbooks
的实例,但不保留对它的引用。
甚至引用如:
targetRange.Columns.AutoFit()
在您不知情的情况下创建.Columns()
的实例并且未正确发布。
我最后写了一个包含对象引用列表的类,它可以按相反的顺序处理所有对象。
当您使用返回对象本身的Excel互操作时,该类有一个对象列表和Add()
函数用于您引用的任何内容:
public List<Object> _interopObjectList = new List<Object>();
public Excel.Application add(Excel.Application obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Range add(Excel.Range obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbook add(Excel.Workbook obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheet add(Excel.Worksheet obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheets add(Excel.Worksheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Sheets add(Excel.Sheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbooks add(Excel.Workbooks obj)
{
_interopObjectList.Add(obj);
return obj;
}
然后取消注册对象我使用了以下代码:
//Release all registered interop objects in reverse order
public void unregister()
{
//Loop object list in reverse order and release Office object
for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
{ ReleaseComObject(_interopObjectList[i]); }
//Clear object list
_interopObjectList.Clear();
}
/// <summary>
/// Release a com interop object
/// </summary>
/// <param name="obj"></param>
public static void ReleaseComObject(object obj)
{
if (obj != null && InteropServices.Marshal.IsComObject(obj))
try
{
InteropServices.Marshal.FinalReleaseComObject(obj);
}
catch { }
finally
{
obj = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
然后原则是创建类并捕获这样的引用:
//Create helper class
xlsHandler xlObj = new xlsHandler();
..
//Sample - Capture reference to excel application
Excel.Application _excelApp = xlObj.add(new Excel.Application());
..
//Sample - Call .Autofit() on a cell range and capture reference to .Columns()
xlObj.add(_targetCell.Columns).AutoFit();
..
//Release all objects collected by helper class
xlObj.unregister();
也许不是美丽的代码,但可能激发一些有用的东西。
答案 5 :(得分:3)
如其他答案中所述,使用两个点将创建无法由Marshal.FinalReleaseComObject
关闭的隐藏引用。我只是想分享我的解决方案,这样就不需要记住Marshal.FinalReleaseComObject
- 它很容易被遗漏,并且很难找到罪魁祸首。
我使用通用IDisposable包装类,可以在任何COM对象上使用。它就像一个魅力,它保持一切美观和干净。我甚至可以重用私有字段(例如this.worksheet
)。由于IDisposable的性质(Dispose方法以finally
运行),它还会在出现错误时自动释放对象。
using Microsoft.Office.Interop.Excel;
public class ExcelService
{
private _Worksheet worksheet;
private class ComObject<TType> : IDisposable
{
public TType Instance { get; set; }
public ComObject(TType instance)
{
this.Instance = instance;
}
public void Dispose()
{
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
}
}
public void CreateExcelFile(string fullFilePath)
{
using (var comApplication = new ComObject<Application>(new Application()))
{
var excelInstance = comApplication.Instance;
excelInstance.Visible = false;
excelInstance.DisplayAlerts = false;
try
{
using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
{
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Action";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Status";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "ItemPrices";
this.worksheet.Activate();
using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
{
comRange.Instance.Select();
comWindow.Instance.FreezePanes = true;
}
}
if (this.fullFilePath != null)
{
var currentWorkbook = (workbook.Instance as _Workbook);
currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
currentWorkbook.Close(false);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
throw;
}
finally
{
// Close Excel instance
excelInstance.Quit();
}
}
}
}
答案 6 :(得分:1)
如果您绝望。 除非您了解它的作用,否则不要使用此方法:
foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
{
proc.Kill();
}
注意:这会杀死名为&#34; EXCEL&#34;。
的每个进程我必须这样做,因为即使我已经关闭了我的代码中的每个COM对象,我仍然有顽固的Excel.exe进程挂在那里。当然,这绝不是最佳解决方案。
答案 7 :(得分:1)
或者,您可以按照here所解释的那样终止Excel流程。
首先,导入 SendMessage 功能:
[DisplayFormat(DataFormatString = "{0:0.##}")]
然后,将WM_CLOSE消息发送到主窗口:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
答案 8 :(得分:1)
不要让这个变得太复杂!! 只需创建一个简单的方法,然后将该方法称为 如下:
// to kill the EXCELsheet file process from process Bar
private void KillSpecificExcelFileProcess() {
foreach (Process clsProcess in Process.GetProcesses())
if (clsProcess.ProcessName.Equals("EXCEL")) //Process Excel?
clsProcess.Kill();
}
答案 9 :(得分:0)
我有同样的问题,我们可以解决问题而不会有任何查杀,我们总是忘记关闭我们使用过的Microsoft.Office.Interop.Excel类接口,所以这里是代码片段并按照结构和方式清除对象,也关注代码中的Sheets接口这是我们经常关闭应用程序,工作簿,工作簿,范围,工作表的主要罪魁祸首,但我们忘记或不知不觉发布Sheets对象或使用的接口,所以这里是代码:< / p>
<body>
<div id=header>
this is header
</div>
<div class="row">
<div id="span3">
<div id="nav">
</div>
</div>
<div id="span9">
<h1>heading1</h1>
<p>some content</p>
<h1>heading2</h1>
<p>some content</p>
<h1>heading3</h1>
<p>some content</p>
</div>
</div>
<script>
$('#nav').tocify();
</script>
</body>
答案 10 :(得分:0)
@Denis Molodtsov为了帮助您,建议杀死所有名为“ EXCEL”的进程。那似乎是在自找麻烦。已经有许多答案描述了如何通过与COM interop配合使用来使调用excel.quit()后停止进程的方法。最好使它工作。
@Kevin Vuilleumier建议将WM_CLOSE发送到Excel窗口。我打算对此进行测试。
如果出于某些原因需要终止Excel App Object的Excel进程,则可以使用以下类似方法专门针对它:
using System.Diagnostics;
using System.Runtime.InteropServices;
// . . .
[DllImport("user32.dll", SetLastError=true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
// . . .
uint excelAppPid;
uint tid = GetWindowThreadProcessId(excel.Hwnd, out excelAppPid);
if (tid)
{
Process excelAppProc = Process.GetProcessById($excelPid)
if (excelAppProc)
{
excelAppProc.Kill()
}
}
我没有时间在C#中进行全面测试,但是我在Powershell中进行了快速测试,但我遇到了Excel无法终止的问题,这种方法行得通。
这很简单。 Excel App对象的Hwnd属性是Excel进程的隐藏窗口句柄。将excel.Hwnd传递给GetWindowThreadProcessId以获取进程ID。使用它来打开进程,最后调用Kill()。
至少我们确定我们正在扼杀正确的过程。好吧,很确定。如果Excel进程已经正常终止,则其进程ID可以被新进程重用。为了限制这种可能性,重要的是不要在调用excel.quit()和尝试终止之间等待。
答案 11 :(得分:0)
我自己做了几次测试,检查了不同的答案后,这是最短的代码,让这个过程在几秒钟后消失:
var excelApp = new Microsoft.Office.Interop.Excel.Application();
var workbooks = excelApp.Workbooks;
try
{
var wb = workbooks.Open(filePath);
// Use worksheet, etc.
Worksheet sheet = wb.Worksheets.get_Item(1);
}
finally
{
excelApp.Quit();
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excelApp);
}
尽管有关于双点神话的消息,但在我自己的测试中,如果我没有 Workbooks
的变量,该过程将永远保持下去。似乎确实调用 excelApp.Workbooks
在内存中创建了一些阻止垃圾收集器处理 excel.exe 的对象。这意味着这将进程留在内存中:
try
{
// Do not
var wb = excelApp.Workbooks.Open("");
}
finally
{
excelApp.Quit();
// Do not
Marshal.ReleaseComObject(excelApp.Workbooks);
Marshal.ReleaseComObject(excelApp);
}