Excel Process not closing in VB.net

后端 未结 8 1073
星月不相逢
星月不相逢 2020-12-03 09:00

I am creating an excel file using interop.excel and the process is not closing. This is the code i am trying to use.

 Private Sub converToExcel(fileLoc As S         


        
相关标签:
8条回答
  • 2020-12-03 09:17

    I did not see anyone properly address what was occuring and instead, tried to create work arounds for it.

    What is happening here is that the workbook is prompting, in the background, to be saved. In your code, you're saving the worksheet and not the workbook. You can either trick it and set the saved state of the workbook to true or save the workbook before exiting the excel applicaiton.

    I was also having this issue. The Excel process would run the entire time the application was open. By adding the xlWorkBook.Saved = True line, the process would end after the call to xlApp.Quit(). In my case, I did not need to save the excel file, only reference it for values.

    Option #1 - Do not save the workbook:

    xlWorkSheet.SaveAs(fileLoc)
    xlWorkBook.Saved = True       ' Add this line here.
    'xlWorkBook.Close()           ' This line shouldn't be necessary anymore.
    xlApp.Quit()
    

    Option #2 - Save the workbook to a new file:

    '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()
    

    Option #3 - Save the workbook to an existing file:

    '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() Method:
    https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel._application.quit?view=excel-pia#Microsoft_Office_Interop_Excel__Application_Quit

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

    0 讨论(0)
  • 2020-12-03 09:22
    '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 讨论(0)
  • 2020-12-03 09:25

    Manual memory management like this just never works. This is a problem that's been known for very a long time and the core reason that garbage collectors were invented. Programmers just forever forget to release memory.

    It gets extra hard when you can't see the memory being used. Which is certainly the case in your code, the xlWorkSheet.Cells(i + 1, j + 1) expression uses no less than three references. One for the range object returned by the Cells property, one for a sub-range object selected by i+1 and another for the sub-range object selected by j+1. Very nice syntax sugar provided by the VB.NET language, writing COM code without it is pretty doggone painful. But not helpful to let you see the references. Not only can't you see it in your source code, there is absolutely nothing the debugger can do to help you see them either.

    This is very much a solved problem in .NET, it has a garbage collector and it can see everything. The most basic problem is that you don't give it a chance to solve your problem. The mistake you made is that you stopped. Probably by setting a breakpoint on the last statement and then looking in Task Manager and seeing Excel.exe still running. Yes, that's normal. Garbage collection is not instant.

    Calling GC.Collect() is supposed to make it instant, but that doesn't work in the specific case of running the Debug build of your project. The lifetime of local variables gets then extended to the end of the method, help you see them in the Autos/Locals/Watch window. In other words, GC.Collect() doesn't actually collect any of the interface references. More about that behavior in this post.

    The simple workaround is to not stop. Keep doing useful things to give the garbage collector a reason to run. Or letting your program terminate since it is done, Excel terminates when the finalizer thread runs for the last time. Which works because the local variables that had the references are not in scope anymore.

    But everybody wants the instant fix anyway. You get it by deleting all the releaseObject() calls. And doing it like this instead:

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

    Or in other words, force a collection after the method has returned. The local variables are no longer in scope so they can't hold on to an Excel reference. It will now also work when you debug it, like it already did when you ran the Release build without a debugger.

    0 讨论(0)
  • 2020-12-03 09:25

    It's just as simple as adding this line in your code, just after opening the Workbook:

    oExcel.displayalerts = False

    0 讨论(0)
  • 2020-12-03 09:26

    The GC.Collect makes not much sense where you placed it, if anything you should call it after you return from converToExcel. Also you may need to wait for finalizers to run. Personally I think Hans' answer is the way to go, but I know from personal experience writing office addins in C# that sometimes its necessary to do manual reference counting, in particular when you need to be compatible with older office versions. (There are many documented problems, in particular when handling events from office, which can only be reliably solved by manual reference counting. Also some COM libraries don't like at all when released in the wrong order by GC, but thats not the case with office.)

    So on to the actual problem in your code: there are three intermediate COM objects not released here:

    • xlWorkBook.Sheets returns a collection of type Excel.Sheets
    • xlWorkSheet.Columns returns a COM object of type Excel.Range
    • xlWorkSheet.Cells also returns an Excel.Range object

    Besides this, if Marshal.ReleaseComObject throws an exception you did something wrong in your manual reference counting, therefore I wouldn't wrap it in an exception handler. When doing manual reference counting you must release every COM object once for every time it crosses the COM->NET boundary, meaning the Excel.Range objects need to be released in every iteration of the loop.

    Here's code which properly terminates Excel for me:

    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
    

    If you want to be extra careful you'd want to handle exceptions from the office API and call ReleaseComObject inside finally-clauses. It can be helpful to define a generic wrapper and write using-clauses instead of try-finally (make the wrapper a structure not a class so you don't allocate the wrappers on the heap).

    0 讨论(0)
  • 2020-12-03 09:32
    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 讨论(0)
提交回复
热议问题