Excel进程未在VB.net中关闭

时间:2013-01-08 19:22:28

标签: vb.net excel com interop

我正在使用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上运行。 任何帮助都会很棒! 感谢

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
  • 的COM对象
  • 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()

.Quit()方法:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel._application.quit?view=excel-pia#Microsoft_Office_Interop_Excel__Application_Quit

.Saved()方法:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel._workbook.saved?view=excel-pia#Microsoft_Office_Interop_Excel__Workbook_Saved

答案 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