2013-01-08 49 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_使用它。從長遠來看,僅有代碼的答案是沒有用的。 –