我正在使用interop.excel创建一个excel文件,并且该进程未关闭。 这是我试图使用的代码。
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对象,但似乎无法找到解决方案。 另外作为一个注释,这是在64位Windows 8上运行。 任何帮助都会很棒! 感谢
答案 0 :(得分:14)
当看不到正在使用的内存时,会变得更加困难。在您的代码中肯定是这种情况,xlWorkSheet.Cells(i + 1, j + 1)
表达式使用不少于三个引用。一个用于Cells属性返回的范围对象,一个用于由i+1
选择的子范围对象,另一个用于由j+1
选择的子范围对象。 VB.NET语言提供了非常好的语法糖,没有它就编写COM代码非常痛苦。但是没有帮助让你看到参考文献。您不仅无法在源代码中看到它,调试器也无法帮助您查看它们。
这在.NET中是一个非常解决的问题,它有一个垃圾收集器,它可以看到一切。最基本的问题是你没有给它机会来解决你的问题。你犯的错误是停止了。可能通过在最后一个语句上设置断点,然后查看任务管理器并看到Excel.exe仍在运行。是的,这是正常的。垃圾收集不是即时。
调用GC.Collect()应该让它立即生效,但是在运行项目的Debug版本的特定情况下这不起作用。然后将局部变量的生命周期扩展到方法的末尾,帮助您在Autos / Locals / Watch窗口中查看它们。换句话说,GC.Collect()实际上并不收集任何的接口引用。有关this post中的行为的更多信息。
简单的解决方法是不停止。继续做有用的事情,让垃圾收集器成为运行的理由。或者让程序在完成后终止,当终结器线程最后一次运行时,Excel终止。这是有效的,因为具有引用的局部变量不再在范围内。
但是无论如何,每个人都想要即时修复。你可以通过删除所有releaseObject()调用来获得它。并且这样做:
converToExcel(path, dset)
GC.Collect()
GC.WaitForPendingFinalizers()
或换句话说,在方法返回后强制收集。局部变量不再在范围内,因此它们无法保留Excel引用。它现在也可以在你调试它时工作,就像你在没有调试器的情况下运行Release版本时那样。
答案 1 :(得分:1)
尝试System.Runtime.InteropServices.Marshal.FinalReleaseComObject,这应该有帮助......如果我没记错的话你也应该调用xlWorkBook.Close()和xlapp.quit。首先调用它们然后将它们设置为空。
答案 2 :(得分:1)
GC.Collect在您放置它的位置没有多大意义,如果您从converToExcel
返回后应该将其命名为。此外,您可能需要等待终结器运行。就个人而言,我认为汉斯的答案是要走的路,但我从个人经验中知道在C#中编写办公室插件,有时需要进行手动引用计数,特别是当您需要与旧办公室版本兼容时。 (有许多记录在案的问题,特别是处理来自办公室的事件时,只能通过手动引用计数可靠地解决。还有一些COM库在GC发布错误的顺序时根本不喜欢,但事实并非如此与办公室。)
关于代码中的实际问题:这里没有发布三个中间COM对象:
xlWorkBook.Sheets
返回Excel.Sheets
xlWorkSheet.Columns
返回类型为Excel.Range
xlWorkSheet.Cells
也会返回Excel.Range
对象除此之外,如果Marshal.ReleaseComObject抛出异常,你在手动引用计数中做错了,因此我不会将它包装在异常处理程序中。在进行手动引用计数时,每次COM对象越过COM-> NET边界时都必须释放一次,这意味着需要在循环的每次迭代中释放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中的异常并在finally子句中调用ReleaseComObject。定义一个通用的包装器并编写using子句而不是try-finally可能会有所帮助(使包装器成为一个结构而不是一个类,这样就不会在堆上分配包装器。)
答案 3 :(得分:1)
'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
答案 4 :(得分:0)
终于解决了:)
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
此方法关闭特定的流程。
答案 5 :(得分:0)
我没有看到任何人正确地解决正在发生的事情,而是尝试为其解决问题。
这里发生的是工作簿在后台提示要保存。在您的代码中,您要保存工作表而不是工作簿。您可以欺骗它并将工作簿的保存状态设置为true,或者在退出excel应用程序之前保存工作簿。
我也遇到了这个问题。 Excel进程将在打开应用程序的整个过程中运行。通过添加xlWorkBook.Saved = True行,该过程将在调用xlApp.Quit()之后结束。就我而言,我不需要保存excel文件,只需引用它的值即可。
选项1-不保存工作簿:
xlWorkSheet.SaveAs(fileLoc)
xlWorkBook.Saved = True ' Add this line here.
'xlWorkBook.Close() ' This line shouldn't be necessary anymore.
xlApp.Quit()
选项2-将工作簿保存到新文件:
'xlWorkSheet.SaveAs(fileLoc) ' Not needed
xlWorkBook.SaveAs(fileLoc) ' If the file doesn't exist
'xlWorkBook.Close() ' This line shouldn't be necessary anymore.
xlApp.Quit()
选项3-将工作簿保存到现有文件中:
'xlWorkSheet.SaveAs(fileLoc) ' Not needed
xlWorkBook.Save(fileLoc) ' If the file does exist
'xlWorkBook.Close() ' This line shouldn't be necessary anymore.
xlApp.Quit()
答案 6 :(得分:0)
就像在打开工作簿之后在代码中添加此行一样简单:
img(1:50, :, :) = [200, 134, 12]
img(1:50, :, :) = [[200, 134, 12]]
img(1:50, :, :) = reshape([200, 134, 12], 1, 1, 3)
答案 7 :(得分:-1)
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