Excel进程不会在VB.net中closures

我正在使用interop.excel创build一个excel文件,并且这个过程没有closures。 这是我正在尝试使用的代码。

Private Sub converToExcel(fileLoc As String, ds As DataSet) Dim xlApp As Excel.Application Dim xlWorkBook As Excel.Workbook Dim xlWorkBooks As Excel.Workbooks Dim xlWorkSheet As Excel.Worksheet Dim misValue As Object = System.Reflection.Missing.Value Dim i As Integer Dim j As Integer xlApp = New Excel.Application xlWorkBooks = xlApp.Workbooks xlWorkBook = xlWorkBooks.Add(misValue) xlWorkSheet = xlWorkBook.Sheets("sheet1") For i = 0 To ds.Tables(0).Rows.Count - 1 For j = 0 To ds.Tables(0).Columns.Count - 1 xlWorkSheet.Columns.NumberFormat = "@" xlWorkSheet.Cells(i + 1, j + 1) = String.Format("{0}", ds.Tables(0).Rows(i).Item(j).ToString()) Next Next xlWorkSheet.SaveAs(fileLoc) xlWorkBook.Close() xlApp.Quit() releaseObject(xlWorkSheet) releaseObject(xlWorkBook) releaseObject(xlWorkBooks) releaseObject(xlApp) End Sub Private Sub releaseObject(ByVal obj As Object) Try System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) obj = Nothing Catch ex As Exception obj = Nothing Finally GC.Collect() End Try End Sub 

我想我错过了一个COM对象,但似乎找不到解决scheme。 另外值得一提的是,这是在64位Windows 8上运行的。任何帮助都会很棒! 谢谢

像这样的手动内存pipe理从来没有工作。 这是一个很长时间以来所知道的问题,也是垃圾收集者被发明的核心原因。 程序员只是永远忘记释放内存。

当你看不到正在使用的内存时,会变得非常困难。 在你的代码中肯定是这样, xlWorkSheet.Cells(i + 1, j + 1)expression式使用不less于三个引用。 一个用于由Cells属性返回的范围对象,一个用于由i+1select的子范围对象,另一个用于由j+1select的子范围对象。 VB.NET语言提供的非常好的语法糖,没有它写COM代码是非常痛苦的。 但没有帮助,让你看到参考。 您不但无法在源代码中看到它,debugging器也无法帮助您查看它们。

这在.NET中是一个很好解决的问题,它有一个垃圾收集器,它可以看到一切 。 最基本的问题是你不给它一个机会来解决你的问题。 你犯的错误是你停了 。 可能通过在最后一条语句上设置一个断点,然后查看任务pipe理器并看到Excel.exe仍在运行。 是的,这很正常。 垃圾收集不是即时的

调用GC.Collect()应该使其立即可用,但在运行项目的Debug版本的特定情况下不起作用。 局部variables的生命周期可以扩展到方法的末尾,帮助你在Autos / Locals / Watch窗口中看到它们。 换句话说,GC.Collect()实际上并不收集任何接口引用。 更多关于这个职位的行为。

简单的解决方法是不停止 。 继续做有用的事情给垃圾收集器一个运行的理由。 或者让程序自完成后终止,Excel在终结器线程最后一次运行时终止。 这是有效的,因为具有引用的局部variables不在范围之内。

但无论如何,大家都想要即时修复。 你可以通过删除所有的releaseObject()调用来获得它。 而是这样做,而不是:

 converToExcel(path, dset) GC.Collect() GC.WaitForPendingFinalizers() 

换句话说,在方法返回强制收集。 局部variables不再处于范围之内,因此它们不能保留到Excel引用。 它现在也可以在你debugging的时候工作,就像你在没有debugging器的情况下运行Release版本的时候一样。

尝试System.Runtime.InteropServices.Marshal.FinalReleaseComObject,应该帮助…还应该调用xlWorkBook.Close()和xlapp.quit,如果我记得正确。 先打电话给他们,然后把他们设置为无。

GC.Collect在放置它的位置没有什么意义,如果有任何事情, converToExcel 返回之后应该调用它。 也可能需要等待终结者运行。 就个人而言,我认为汉斯的答案是要走的路,但是我从个人经验写的C#中的办公室插件知道,有时需要做手工引用计数,特别是当您需要与旧版本兼容时。 (有很多文档logging的问题,尤其是处理办公事件时,只能通过手工引用计数才能可靠地解决,另外一些COM库在按照GC的错误顺序发布时根本就不喜欢,但不是这样与办公室。)

因此,在您的代码中的实际问题:有三个中间COM对象没有发布在这里:

  • xlWorkBook.Sheets返回Excel.Sheetstypes的集合
  • xlWorkSheet.Columns返回一个Excel.Rangetypes的COM对象
  • xlWorkSheet.Cells也返回一个Excel.Range对象

除此之外,如果Marshal.ReleaseComObject引发exception,则在手动引用计数中做了错误,因此我不会将其包装在exception处理程序中。 在执行手动引用计数时,每次跨越COM-> NET边界时都必须释放一次COM对象,这意味着Excel.Range对象需要在循环的每次迭代中释放。

这里是适当地终止我的Excel的代码:

 Imports Microsoft.Office.Interop Imports System.Runtime.InteropServices Private Sub converToExcel(fileLoc As String, ds As DataSet) Dim xlApp As New Excel.Application Dim xlWorkBooks As Excel.Workbooks = xlApp.Workbooks Dim xlWorkBook As Excel.Workbook = xlWorkBooks.Add(System.Reflection.Missing.Value) Dim xlWorkSheets As Excel.Sheets = xlWorkBook.Sheets ' accessing the sheet by index because name is localized and your code will fail in non-english office versions Dim xlWorkSheet As Excel.Worksheet = xlWorkSheets(1) For i = 0 To ds.Tables(0).Rows.Count - 1 For j = 0 To ds.Tables(0).Columns.Count - 1 ' couldn't this be moved outside the loop? Dim xlColumns As Excel.Range = xlWorkSheet.Columns xlColumns.NumberFormat = "@" Marshal.ReleaseComObject(xlColumns) Dim xlCells As Excel.Range = xlWorkSheet.Cells xlCells(i + 1, j + 1) = ds.Tables(0).Rows(i).Item(j).ToString() Marshal.ReleaseComObject(xlCells) Next Next xlWorkSheet.SaveAs(fileLoc) 'xlWorkBook.Close() -- not really necessary xlApp.Quit() Marshal.ReleaseComObject(xlWorkSheet) Marshal.ReleaseComObject(xlWorkSheets) Marshal.ReleaseComObject(xlWorkBook) Marshal.ReleaseComObject(xlWorkBooks) Marshal.ReleaseComObject(xlApp) End Sub 

如果你想特别小心,你会想处理来自office API的exception,并在finally子句中调用ReleaseComObject。 定义一个通用的包装器并写入using-clause而不是try-finally会很有帮助(使包装器不是一个类,所以你不会在堆上分配这些包装器)。

终于解决了:)

 Private Function useSomeExcel(ByVal Excelfilename As String) Dim objExcel As Excel.Application Dim objWorkBook As Excel.Workbook Dim objWorkSheets As Excel.Worksheet Dim datestart As Date = Date.Now objExcel = CreateObject("Excel.Application") 'This opens... objWorkBook = objExcel.Workbooks.Open(Excelfilename) ' ... excel process Dim dateEnd As Date = Date.Now End_Excel_App(datestart, dateEnd) ' This closes excel proces End Function 

使用这种方法

  Private Sub End_Excel_App(datestart As Date, dateEnd As Date) Dim xlp() As Process = Process.GetProcessesByName("EXCEL") For Each Process As Process In xlp If Process.StartTime >= datestart And Process.StartTime <= dateEnd Then Process.Kill() Exit For End If Next End Sub 

此方法closures特定的进程打开。

 'Get the PID from the wHnd and kill the process. ' open the spreadsheet ImportFileName = OpenFileDialog1.FileName excel = New Microsoft.Office.Interop.Excel.ApplicationClass wBook = excel.Workbooks.Open(ImportFileName) hWnd = excel.Hwnd Dim id As Integer = GetWindowThreadProcessId(hWnd, ExcelPID) 

 Sub CloseExcelFile() Try ' first try this wBook.Saved = True wBook.Close() excel.Quit() ' then this. System.Runtime.InteropServices.Marshal.ReleaseComObject(excel) excel = Nothing ' This appears to be the only way to close excel! Dim oProcess As Process oProcess = Process.GetProcessById(ExcelPID) If oProcess IsNot Nothing Then oProcess.Kill() End If Catch ex As Exception excel = Nothing Finally GC.Collect() End Try End Sub 
 Dim xlp() As Process = Process.GetProcessesByName("EXCEL") For Each Process As Process In xlp Process.Kill() If Process.GetProcessesByName("EXCEL").Count = 0 Then Exit For End If Next