2013-01-08 43 views
7

我使用interop.excel创建了一个excel文件,并且进程未关闭。 这是我正在尝试使用的代码。Excel进程不在VB.net中关闭

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

你确信这是不是从以前的测试留下了一个实例? – Fionnuala

+0

是的,我在每次测试后检查任务管理器并删除所有excel实例。 – jmcsmith

+0

我不认为这是你的问题的原因,但'releaseObject'可能不会做你想象的那样。由于你传递'obj'' ByVal','obj = Nothing'对'xlWorkSheet','xlWorkBook'等变量没有任何影响。 – prprcupofcoffee

回答

1

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

+0

我在调用releaseObject之前调用xlWorkBook.Close()和xlApp.Quit()。使用finalRelease尝试,结果相同。 – jmcsmith

+0

我认为你的订单在退出时是错误的:我认为你必须将这些参数设置为空,然后退出工作簿然后将其设置为无,然后退出应用程序并退出。 但在进一步的调查中,我看到你打电话给gc只收一次 - 叫它两次。如果没有,对象被标记为释放,但不被释放。 –

1

GC.Collect对于放置它的位置没有多大意义,如果有什么应该在从converToExcel返回后称之为。您也许需要等待终结者运行。就我个人而言,我认为Hans的答案是要走的路,但我从C#编写office插件的个人经验知道,有时候需要做手工引用计数,特别是当您需要与旧版Office兼容时。 (有许多文档记录的问题,尤其是处理办公室事件时,只能通过手工引用计数才能可靠地解决。还有一些COM库在GC错误排序时根本不喜欢,但那不是这种情况与Office)

于是就在你的代码中的实际问题:有三个中间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 

如果要格外小心,你会想从办公室API处理异常和调用ReleaseComObject的最后子句中。定义一个通用包装并写入using-clause而不是try-finally会很有帮助(使包装不是一个类,所以你不会在堆上分配这些包装)。

11

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

当您正在使用的内存看不到时,它会变得非常困难。您的代码中肯定是这种情况,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版本时一样。

+0

尽管您的解决方案通常是顺其自然的方式,但并不总是奏效。当您需要与较旧的Office版本兼容时,有许多记录的情况下需要进行手动引用计数,特别是在编写加载和处理事件时。还有一些COM库(来自微软,但幸运的是不是Office)不喜欢它们的对象被GC以错误的顺序发布。 – Zarat

+0

这是相当普遍的哗众取宠。当一个任意的小变化有很大的副作用时都是常见的,使它看起来都像黑魔法。这些“许多记录在案的案例”当然需要引用。这里是我的:http://blogs.msdn.com/b/visualstudio/archive/2010/03/01/marshal-releasecomobject-considered-dangerous.aspx –

+0

对不起,从快速谷歌搜索找不到它们,但是当几年前,我研究了这个话题,有很多用户报告说办公室没有关闭托管插件(最常见的原因是订阅事件),甚至有些(非常罕见)由于GC在时间办公室无法处理时发布引用而导致的崩溃用。可能所有固定在现代版本的办公室(和.NET 4有助于事件订阅),所以只有在您必须向后兼容时才相关。还有一些关于它的Microsoft知识库文章,但它们很难找到。 – Zarat

-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 
+0

杀死进程很少是正确的解决方案,它不允许目标进行适当的清理。对于发布的问题,存在干净的解决方案,所以你不应该回头去追杀excel。 – Zarat

+0

有时它不会清理Excel进程,并且在重复生成excel的同时,会创建多个1 excel进程,这会导致进程和系统速度变慢。这是我使用的问题,并且此解决方案可以正常工作我的情况:) – Codebits

+3

它可能适用于您自己的情况,但将它作为通用解决方案而不解释其具有的后果将其表示无效。例如,它会杀死任何* Excel进程,包括用户打开的进程。大多数人不想安装一个程序,只是为了发现它会杀死他们的Excel实例,仅仅是因为程序员无法想象一个干净的方式来与Excel进行交互。 – Zarat

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 

此方法关闭especific进程打开。

0
'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 
+0

欢迎来到Stack Overflow!虽然这段代码可能会回答这个问题,但最好包含一些_context_,解释它的工作原理和_when_使用它。从长远来看,仅有代码的答案是没有用的。 –