8

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

4

8 回答 8

17

像这样的手动内存管理永远不会奏效。这是一个众所周知的问题,也是垃圾收集器被发明的核心原因。程序员永远忘记释放内存。

当您看不到正在使用的内存时,它会变得更加困难。在您的代码中肯定是这种情况,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() 实际上并不收集任何接口引用。有关此行为的更多信息,请参阅这篇文章

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

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

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

或者换句话说,在方法返回后强制收集。局部变量不再在范围内,因此它们无法保留 Excel 引用。现在,当您调试它时,它也可以工作,就像您在没有调试器的情况下运行 Release 构建时一样。

于 2013-01-08T21:12:56.197 回答
1

试试 System.Runtime.InteropServices.Marshal.FinalReleaseComObject,这应该会有所帮助……如果我没记错的话,你也应该调用 xlWorkBook.Close() 和 xlapp.quit。首先调用它们,然后将它们设置为空。

于 2013-01-08T20:05:48.730 回答
1

GC.Collect 在你放置它的地方没有多大意义,如果你应该converToExcel. 此外,您可能需要等待终结器运行。就我个人而言,我认为 Hans 的答案是可行的方法,但我从用 C# 编写 office 插件的个人经验中知道,有时需要进行手动引用计数,特别是当您需要与旧的 office 版本兼容时。(有许多记录在案的问题,特别是在处理来自办公室的事件时,只能通过手动引用计数可靠地解决。此外,当 GC 以错误的顺序释放时,一些 COM 库根本不喜欢,但事实并非如此与办公室。)

继续讨论代码中的实际问题:此处未发布三个中间 COM 对象:

  • xlWorkBook.Sheets返回类型的集合Excel.Sheets
  • xlWorkSheet.Columns返回类型为 COM 的对象Excel.Range
  • xlWorkSheet.Cells还返回一个Excel.Range对象

除此之外,如果 Marshal.ReleaseComObject 抛出异常,您在手动引用计数中做错了,因此我不会将它包装在异常处理程序中。在进行手动引用计数时,您必须在每次越过 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 的异常并在 finally 子句中调用 ReleaseComObject。定义通用包装器并编写 using-clauses 而不是 try-finally 会很有帮助(使包装器成为结构而不是类,因此您不会在堆上分配包装器)。

于 2013-01-08T20:11:40.687 回答
1

终于解决了:)

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

此方法关闭打开的特定进程。

于 2015-05-06T22:10:16.317 回答
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
于 2016-05-09T18:33:47.007 回答
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

于 2019-02-15T15:34:04.703 回答
0

就像在你的代码中添加这一行一样简单,就在打开工作簿之后:

oExcel.displayalerts = False

于 2019-09-24T14:21:48.027 回答
-2
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
于 2013-10-26T07:30:22.767 回答