Excel interop COM doesn't close
Excel interop COM doesn't close
我有一段代码,它打开一个电子表格,读取一些值,然后关闭表格。我需要对多个文件执行此操作。我遇到的问题是Excel应用程序实例没有退出,因此当我运行多个文件的进程时,会产生多个excel.exe进程。有什么办法可以让Excel关闭吗?
static void ParseFile(string file) { try { log("解析:" + file); Excel.Application excel = new Excel.Application(); Excel.Workbook wb = excel.Workbooks.Open(file); Excel.Worksheet ws = wb.Worksheets[1]; for (int i = 2; i < 27; i++) { log(ws.Cells[i, 1].Text); } wb.Close(false); excel.Quit(); GC.Collect(); GC.WaitForPendingFinalizers(); Marshal.FinalReleaseComObject(ws); Marshal.FinalReleaseComObject(wb); Marshal.FinalReleaseComObject(excel); excel = null; ws = null; wb = null; System.GC.Collect(); } catch (Exception ex) { log(ex.Message); } }
Excel interop COM doesn't close的问题出现的原因是Excel.exe进程没有在程序退出后关闭。这个问题可以通过使用一个实现了IDisposable接口的包装对象来解决,以便可以在C#的using语句中使用。这样做的好处是可以提高代码可读性,并且适用于任何COM对象。
下面是一个使用运行时调度的示例:
using System; using System.Reflection; using System.Runtime.InteropServices; public class ComRef: IDisposable where T : class { private T reference; public T Reference { get { return reference; } } public ComRef(T o) { reference = o; } public void Dispose() { if (reference != null) { Marshal.ReleaseComObject(reference); reference = null; } } } public class Test { static void Main() { Type excelAppType = Type.GetTypeFromProgID("Excel.Application"); using (var comRef = new ComRef
如果已经导入了Excel的类型库,或者任何其他类型库,可以使用更友好的方法:
public class CoClassComRef: ComRef where T : class, new() { public CoClassComRef() : base(new T()) { } } public class Test { static void Main() { using (var comRef = new CoClassComRef ()) { var excel = comRef.Reference; // ... excel.Quit(); } } }
只需确保不要将comRef.Reference捕获到超过using语句的字段或变量中。
注意,我没有考虑线程安全性和正确的Dispose实现。如果只在using语句中使用ComRef,线程安全性并不重要。正确的Dispose实现应该与终结器一起使用,但在这里没有必要,因为using基本上是try-finally。如果在不使用using语句中使用ComRef并且没有调用Dispose,ComRef将被垃圾回收,其中包含的COM对象也将被释放。
最后,我没有使用Marshal.FinalReleaseComObject,因为当您确信要释放底层的COM对象时,通常会使用它(至少是从托管环境中的所有引用)。但是,如果您感到幸运,可以创建一个新的构造函数,该构造函数还接收一个布尔值,指示是否应调用FinalReleaseComObject而不是ReleaseComObject。对于这些方法的任何一个进行网络搜索的第一个结果都会指向详细说明它们通常为邪恶的文章和博客帖子。
以上是修订后的代码,但在程序退出后仍然会留下Excel.exe实例。
我会对您的答案进行评论。
Excel interop COM doesn't close问题的出现原因是没有正确释放Excel对象的资源。解决方法是使用Marshal.FinalReleaseComObject()方法释放资源,并将打开和关闭Excel应用程序的部分移至单独的函数中。
以下是经过重构的代码:
static Excel.Application OpenExcel(){ Excel.Application excel = null; try{ excel = new Excel.Application(); return excel; } catch(Exception ex){ log(ex.Message); return null; } } static void ParseFile(string file) { try { System.Console.WriteLine("parsing:" + file); Excel.Workbook wb = excel.Workbooks.Open(file); Excel.Worksheet ws = wb.Worksheets[1]; for (int i = 2; i < 27; i++) { log(ws.Cells[i, 1].Text); } wb.Close(false); } catch (Exception ex) { log(ex.Message); } finally{ Marshal.FinalReleaseComObject(ws); Marshal.FinalReleaseComObject(wb); ws = null; wb = null; } } static void CloseExcel(Excel.Application excel){ try{ excel.Quit(); } catch(Exception ex){ log(ex.Message); } finally{ Marshal.FinalReleaseComObject(excel); excel = null; } }
使用方法如下:
Excel.Application excel = OpenExcel(); if(excel != null){ // 循环解析文件 ParseFile("fileName"); // 解析完所有文件后关闭Excel CloseExcel(excel); }
这段代码已经经过测试,从OpenExcel返回的对象在FinalReleaseComObject之后已经完全释放。
对我来说,这段代码并没有起作用,它仍然会留下Excel实例运行……让我感到困惑。
ParseFile()如何访问excel变量?这是一个全局变量还是类级别的变量?
它是一个类级别的变量,主要目的是告诉OP将代码重构为单独的函数。
Excel interop COM doesn't close的问题出现的原因是使用了Excel对象后没有正确释放资源,导致进程无法关闭。解决方法是通过逐个释放COM对象来确保资源被正确清理。
在给出的代码中,excel.Workbooks
和wb.Worksheets
返回的是COM对象,这些对象没有被赋给任何变量,因此无法完全控制它们的生命周期,更重要的是,无法显式地IUnknown::Release
它们。同时,do some stuff here
可能还持有其他对象的引用,当关闭Excel时,这些对象的引用将会失效。这样做可能会起作用,因为它们只剩下被释放的部分,这时.NET的RCWs或COM的IPC可能会在本地服务器关闭时压制某些错误。
这种错误压制很可能只会在应用程序退出时发生。您可能需要检查使用Excel对象的代码,确保您没有在任何地方保留对它们的引用(即使只有一个)。如果可能的话,尝试使用我在代码中展示的ComRef
/CoClassComRef
来处理每个对象。虽然这样做会造成一些不美观的缩进,但如果代码量不大,这种方法可能是可行的,并且可能解决当前在ParseFile
方法外仍然存在的对象。
另外,可以参考其他回答中提供的方法,通过逐个释放COM对象的方式来关闭Excel进程,而不是直接杀死进程。这样可以避免影响使用Excel实例的其他部分,并确保资源得到正确清理。