我正在 C# () 中使用 Excel 互操作,并将以下代码放在我的 finally 子句中:ApplicationClass
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
尽管这种工作,但即使在我关闭Excel之后,该过程仍在后台进行。它仅在我的应用程序手动关闭后才会释放。Excel.exe
我做错了什么,或者是否有其他方法可以确保正确处置互操作对象?
网友回答:
您实际上可以干净地释放 Excel 应用程序对象,但您必须小心。
从理论上讲,为您访问的每个 COM 对象维护命名引用然后显式释放它的建议是正确的,但不幸的是,在实践中很难管理。如果有人滑到任何地方并使用“两个点”,或者通过循环或任何其他类似类型的命令迭代单元格,那么您将拥有未引用的 COM 对象并面临挂起的风险。在这种情况下,将无法在代码中找到原因;你必须通过眼睛检查所有代码,并希望找到原因,这对于一个大型项目来说几乎是不可能的。Marshal.FinalReleaseComObject()
for each
好消息是,您实际上不必维护对您使用的每个 COM 对象的命名变量引用。相反,调用 然后释放您不持有引用的所有(通常是次要的)对象,然后显式释放您持有命名变量引用的对象。GC.Collect()
GC.WaitForPendingFinalizers()
您还应该按重要性的相反顺序释放命名引用:首先是范围对象,然后是工作表、工作簿,最后是 Excel 应用程序对象。
例如,假设您有一个名为 的范围对象变量 ,一个名为 的工作表变量 、一个名为工作簿变量和一个名为 的 Excel 应用程序变量,那么您的清理代码可能如下所示:xlRng
xlSheet
xlBook
xlApp
// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);
xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);
xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);
在大多数代码示例中,你将看到从 .NET 清理 COM 对象,和调用的次数如下:GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
但是,除非您使用的是Visual Studio Tools for Office(VSTO),否则这应该是必需的,它使用终结器,这些终结器会导致在完成队列中提升整个对象图。此类对象在下一次垃圾回收之前不会释放。但是,如果不使用 VSTO,则应该能够调用一次。GC.Collect()
GC.WaitForPendingFinalizers()
我知道明确调用是禁忌(当然这样做两次听起来很痛苦),但老实说,没有办法绕过它。通过正常操作,您将生成隐藏对象,您没有引用,因此您无法通过调用 以外的任何其他方式释放这些对象。GC.Collect()
GC.Collect()
这是一个复杂的话题,但这确实是它的全部内容。为清理过程建立此模板后,您可以正常编码,而无需包装器等。🙂
我这里有一个关于这个的教程:
使用 VB.Net / COM 互操作自动执行办公程序
它是为 VB.NET 编写的,但不要因此而推迟,其原理与使用 C# 时完全相同。
网友回答:
Excel 不会退出,因为您的应用程序仍保留对 COM 对象的引用。
我猜您正在调用 COM 对象的至少一个成员,而没有将其分配给变量。
对我来说,它是excelApp.Worksheets对象,我直接使用它,而没有将其分配给变量:
Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);
我不知道内部 C# 为工作表 COM 对象创建了一个包装器,我的代码没有发布该包装器(因为我不知道),这是 Excel 未卸载的原因。
我在此页面上找到了问题的解决方案,该页面也有一个很好的规则,用于在 C# 中使用 COM 对象:
切勿对 COM 对象使用两个点。
因此,有了这些知识,执行上述操作的正确方法是:
Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);
验尸更新:
我希望每个读者都非常仔细地阅读Hans Passant的这个答案,因为它解释了我和许多其他开发人员偶然发现的陷阱。当我几年前写这个答案时,我不知道调试器对垃圾收集器的影响,并得出了错误的结论。为了历史起见,我保持我的答案不变,但请阅读此链接,不要走“两个点”的道路:了解 .NET 中的垃圾回收和使用 IDisposable 清理 Excel 互操作对象
网友回答:
前言:我的回答包含两种解决方案,所以阅读时要小心,不要错过任何东西。
对于如何卸载 Excel 实例,有不同的方法和建议,例如:
但!有时所有这些选项都无济于事或不适合!
例如,昨天我发现在我的一个函数(适用于 excel)中,Excel 在函数结束后继续运行。我什么都试过了!我彻底检查了整个函数 10 次,并为所有内容添加了 Marshal.FinalReleaseComObject()!我也有GC。Collect() 和 GC。WaitForPendingFinalizers()。我检查了隐藏的消息框。我试图将WM_CLOSE消息发送到Excel主窗口。我在单独的应用程序域中执行了我的函数并卸载了该域。没有任何帮助!关闭所有 excel 实例的选项是不合适的,因为如果用户在执行我的函数(也适用于 Excel)期间手动启动另一个 Excel 实例,那么该实例也将由我的函数关闭。我敢打赌用户不会高兴!所以,老实说,这是一个蹩脚的选择(没有冒犯的家伙)。所以我花了几个小时才找到一个好的(以我的拙见)解决方案:通过其主窗口的hWnd杀死excel进程(这是第一个解决方案)。
下面是简单的代码:
[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 (Win32Exception)
{
return false;
}
catch (NotSupportedException)
{
return false;
}
catch (InvalidOperationException)
{
return false;
}
return true;
}
/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running).
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
if (processID == 0)
throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
Process.GetProcessById((int)processID).Kill();
}
如您所见,我根据 Try-Parse 模式提供了两种方法(我认为在这里很合适):如果无法杀死进程(例如进程不再存在),一种方法不会引发异常,如果进程未被杀死,另一种方法会引发异常。此代码中唯一的弱点是安全权限。从理论上讲,用户可能没有终止进程的权限,但在 99.99% 的情况下,用户具有此类权限。我还使用访客帐户对其进行了测试——它运行良好。
因此,使用 Excel 的代码可能如下所示:
int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);
瞧!Excel已终止!🙂
好的,让我们回到第二个解决方案,正如我在文章开头所承诺的那样。 第二个解决方案是调用 GC。
Collect() 和 GC。WaitForPendingFinalizers()。是的,它们确实有效,但您需要在这里小心!
很多人说(我说)打电话给GC。Collect() 没有帮助。但它无济于事的原因是,如果仍然有对 COM 对象的引用!GC最常见的原因之一。Collect() 没有帮助是在调试模式下运行项目。在调试模式下,在方法结束之前,不会对不再真正引用的对象进行垃圾回收。
所以,如果你尝试过GC。Collect() 和 GC。WaitForPendingFinalizers() 它没有帮助,请尝试执行以下操作:
1) 尝试在发布模式下运行项目并检查 Excel 是否正确关闭
2)将使用Excel的方法包装在单独的方法中。
所以,而不是这样的东西:
void GenerateWorkbook(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
你写:
void GenerateWorkbook(...)
{
try
{
GenerateWorkbookInternal(...);
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private void GenerateWorkbookInternal(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
}
}
现在,Excel将关闭=)
模板简介:该模板名称为【如何正确清理 Excel 互操作对象?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【C#】栏目查找您需要的精美模板。