我在C#(ApplicationClass)中使用Excel互操作,并在finally子句中放置了以下代码:
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
尽管这种方法有效,但即使在我关闭Excel之后,Excel.exe进程仍处于后台。它只在我的应用程序被手动关闭后发布。
我做错了什么,或者是否有其他方法可以确保正确处理互操作对象?
您需要意识到Excel对您正在运行的文化也非常敏感。
您可能会发现,在调用Excel函数之前,需要将区域性设置为EN-US。这并不适用于所有函数,但适用于其中一些函数。
CultureInfo en_US = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
string filePathLocal = _applicationObject.ActiveWorkbook.Path;
System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;
即使您正在使用VSTO,这也适用。
有关详细信息:http://support.microsoft.com/default.aspx?scid=kb;我们;第320369季度
为了说明Excel不关闭的原因,即使在读取和创建时为每个对象创建直接引用,也需要使用“For”循环。
For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
objWorkBook.Close 'or whatever
FinalReleaseComObject(objWorkBook)
objWorkBook = Nothing
Next
'The above does not work, and this is the workaround:
For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
objTempWorkBook.Saved = True
objTempWorkBook.Close(False, Type.Missing, Type.Missing)
FinalReleaseComObject(objTempWorkBook)
objTempWorkBook = Nothing
Next
我找到了一个有用的通用模板,它可以帮助实现COM对象的正确处置模式,这些对象在超出范围时需要调用Marshal.ReleaseComObject:
用法:
using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
try
{
using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
{
// do something with your workbook....
}
}
finally
{
excelApplicationWrapper.ComObject.Quit();
}
}
模板:
public class AutoReleaseComObject<T> : IDisposable
{
private T m_comObject;
private bool m_armed = true;
private bool m_disposed = false;
public AutoReleaseComObject(T comObject)
{
Debug.Assert(comObject != null);
m_comObject = comObject;
}
#if DEBUG
~AutoReleaseComObject()
{
// We should have been disposed using Dispose().
Debug.WriteLine("Finalize being called, should have been disposed");
if (this.ComObject != null)
{
Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
}
//Debug.Assert(false);
}
#endif
public T ComObject
{
get
{
Debug.Assert(!m_disposed);
return m_comObject;
}
}
private string ComObjectName
{
get
{
if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
{
return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
}
return null;
}
}
public void Disarm()
{
Debug.Assert(!m_disposed);
m_armed = false;
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
#endregion
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
if (m_armed)
{
int refcnt = 0;
do
{
refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
} while (refcnt > 0);
m_comObject = default(T);
}
m_disposed = true;
}
}
}
参考:
http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/
到目前为止,似乎所有的答案都涉及其中一些:
终止进程使用GC.Collect()跟踪每个COM对象并正确释放它。
这让我意识到这个问题有多么困难:)
我一直在开发一个库来简化对Excel的访问,我正在努力确保使用它的人不会留下一片混乱(手指交叉)。
我没有直接在Interop提供的接口上进行编写,而是使用扩展方法来简化工作。类似于ApplicationHelpers.CreateExcel()或工作簿.CreateWorksheet(“mySheetNameThatWillBeValidated”)。自然,任何创建的东西都可能会在以后的清理中导致问题,所以我实际上更倾向于在最后的手段中终止这个过程。然而,正确清理(第三种选择)可能是破坏性最小、控制性最强的。
因此,在这种情况下,我想知道这样做是否不是最好的:
public abstract class ReleaseContainer<T>
{
private readonly Action<T> actionOnT;
protected ReleaseContainer(T releasible, Action<T> actionOnT)
{
this.actionOnT = actionOnT;
this.Releasible = releasible;
}
~ReleaseContainer()
{
Release();
}
public T Releasible { get; private set; }
private void Release()
{
actionOnT(Releasible);
Releasible = default(T);
}
}
我用“不可行”来避免与一次性使用混淆。但将其扩展到IDisposable应该很容易。
这样的实现:
public class ApplicationContainer : ReleaseContainer<Application>
{
public ApplicationContainer()
: base(new Application(), ActionOnExcel)
{
}
private static void ActionOnExcel(Application application)
{
application.Show(); // extension method. want to make sure the app is visible.
application.Quit();
Marshal.FinalReleaseComObject(application);
}
}
可以对所有类型的COM对象执行类似的操作。
在工厂方法中:
public static Application CreateExcelApplication(bool hidden = false)
{
var excel = new ApplicationContainer().Releasible;
excel.Visible = !hidden;
return excel;
}
我希望每个容器都会被GC正确地销毁,因此会自动调用Quit和Marshal.FinalReleaseComObject。
评论?或者这是对第三类问题的回答?