2

专家,请让我知道我能做到这一点的最佳方法......

我正在开发一个 VB.Net 应用程序,该应用程序使用 Microsoft.Office.Interop.Excel 对象库在工作簿中创建工作表并在这些工作表中创建数据透视表。

我的代码看起来像这样:

Dim ExcelApp As New Microsoft.Office.Interop.Excel.Application
Dim wbk As Microsoft.Office.Interop.Excel.Workbook = Nothing
Dim wksRawData As Microsoft.Office.Interop.Excel.Worksheet = Nothing
Dim wksPvtTbl As Microsoft.Office.Interop.Excel.Worksheet = Nothing
Dim pvtCache As Microsoft.Office.Interop.Excel.PivotCache = Nothing
Dim pvtTables As Microsoft.Office.Interop.Excel.PivotTables = Nothing
Dim pvtTable As Microsoft.Office.Interop.Excel.PivotTable = Nothing
Dim r1 As Microsoft.Office.Interop.Excel.PivotField = Nothing
Dim r2 As Microsoft.Office.Interop.Excel.PivotField = Nothing
Dim df1 As Microsoft.Office.Interop.Excel.PivotField = Nothing

Try

... Create the objects, put in the information

Catch ex As Exception
   MessageBox.Show("There was an error creating the Excel file", "Error Creating File", MessageBoxButtons.OK)
Finally

   ReleaseObject(r1)
   ReleaseObject(r2)
   ReleaseObject(df1)
   ReleaseObject(pvtTable)
   ReleaseObject(pvtTables)
   ReleaseObject(pvtCache)
   ReleaseObject(wksRawData)
   ReleaseObject(wksPvtTbl)
   ReleaseObject(wbk)

   ExcelApp.DisplayAlerts = True
   ExcelApp.Quit()
   ReleaseObject(ExcelApp)

   ExcelApp = Nothing
   wbk = Nothing
   wksRawData = Nothing
   GC.Collect()
End Try

然后我的发布对象的代码如下所示:

Public Sub ReleaseObject(ByRef Reference As Microsoft.Office.Interop.Excel.Application)
    Dim i As Integer
    If Reference IsNot Nothing Then
        i = System.Runtime.InteropServices.Marshal.ReleaseComObject(Reference)

        While i > 0
            i = System.Runtime.InteropServices.Marshal.ReleaseComObject(Reference)
        End While

        Reference = Nothing
    End If
End Sub

Public Sub ReleaseObject(ByRef Reference As Microsoft.Office.Interop.Excel.Workbook)
    Dim i As Integer
    If Reference IsNot Nothing Then
        i = System.Runtime.InteropServices.Marshal.ReleaseComObject(Reference)

        While i > 0
            i = System.Runtime.InteropServices.Marshal.ReleaseComObject(Reference)
        End While

        Reference = Nothing
    End If
End Sub

... etc

我知道这类问题有很多解决方案,但我迷失在所有不同的解决方案中,不知道什么最适合我目前的情况……这是一个很好的方法吗?如果不是,什么是更有效的方法??

谢谢!!!

4

5 回答 5

3

不要手动搜索和调用释放。如果应用程序发生灾难性崩溃,Interop 进程仍可能运行松散。

我会通过Job Objects将 Interop 进程注册到操作系统。

这是一个解决方案:https ://stackoverflow.com/a/1307180/160146

于 2012-10-16T17:07:42.800 回答
0

我的答案不是最好的,但它有效:(杀死您的应用程序使用的 Excel 进程)

如何正确清理 Excel 互操作对象?

于 2012-10-16T13:55:17.193 回答
0

过去我遇到过 Excel 无法正常关闭的问题。

这是我所做的:

  • 确保你 .Close() 和 .Dispose() 所有的工作簿,然后所有的应用程序
  • 还有 Marshal.ReleaseComObject(),但只有一次。我从来没有像你那样做for循环。
  • 然后 GC.Collect() 和 GC.WaitForPendingFinalizers()
  • 通常在开始时将 DisplayAlerts 和 Interactive 设置为 false。确保没有弹出窗口在后台运行。

我从来没有使用互操作做数据透视表。

于 2012-10-16T14:49:10.197 回答
0

我分享 VB.net 代码:

Imports System.Runtime.InteropServices
Namespace Jobs

Public Class Job
    Implements IDisposable

    <DllImport("kernel32.dll", CharSet:=CharSet.Unicode)>', 
    CallingConvention:=CallingConvention.Cdecl)>
    Shared Function CreateJobObject(a As IntPtr, lpName As String) As IntPtr
    End Function

    <DllImport("kernel32.dll")>
    Public Shared Function SetInformationJobObject(hJob As IntPtr, infoType As 
    JobObjectInfoType, lpJobObjectInfo As IntPtr, cbJobObjectInfoLength As UInteger) As Boolean
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function CloseHandle(Token As IntPtr) As Boolean
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function AssignProcessToJobObject(job As IntPtr, process As IntPtr) as Boolean
    End Function

    Private m_handle As IntPtr
    Private m_disposed As Boolean = False

    Public Sub New()

        m_handle = CreateJobObject(IntPtr.Zero, Nothing)

        Dim info As JOBOBJECT_BASIC_LIMIT_INFORMATION = New JOBOBJECT_BASIC_LIMIT_INFORMATION()
        info.LimitFlags = &H2000

        Dim extendedInfo = New JOBOBJECT_EXTENDED_LIMIT_INFORMATION()
        extendedInfo.BasicLimitInformation = info

        Dim length As Integer = Marshal.SizeOf(GetType(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))
        Dim extendedInfoPtr As IntPtr = Marshal.AllocHGlobal(length)
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, False)

        If (Not SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, length)) Then
            Throw New Exception(String.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()))
        End If
    End Sub

#Region "IDisposableMembers"

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

#End Region
    Private Sub Dispose(disposing As Boolean)
        If m_disposed Then
            Return
        End If
        If disposing Then
        End If
        Close()
        m_disposed = True
    End Sub

    Public Sub Close()
        CloseHandle(m_handle)
        m_handle = IntPtr.Zero
    End Sub

    Public Function AddProcess(handle As IntPtr) As Boolean
        Return AssignProcessToJobObject(m_handle, handle)
    End Function

End Class

Public Enum JobObjectInfoType
    AssociateCompletionPortInformation = 7
    BasicLimitInformation = 2
    BasicUIRestrictions = 4
    EndOfJobTimeInformation = 6
    ExtendedLimitInformation = 9
    SecurityLimitInformation = 5
    GroupInformation = 11
End Enum

<StructLayout(LayoutKind.Sequential)>
Public Structure SECURITY_ATTRIBUTES
    Public nLength As Integer
    Public lpSecurityDescriptor As IntPtr
    Public bInheritHandle As Integer
End Structure

<StructLayout(LayoutKind.Sequential)>
Structure JOBOBJECT_BASIC_LIMIT_INFORMATION
    Public PerProcessUserTimeLimit As Int64
    Public PerJobUserTimeLimit As Int64
    Public LimitFlags As Int16
    Public MinimumWorkingSetSize As UInt32
    Public MaximumWorkingSetSize As UInt32
    Public ActiveProcessLimit As Int16
    Public Affinity As Int64
    Public PriorityClass As Int16
    Public SchedulingClass As Int16
End Structure

<StructLayout(LayoutKind.Sequential)>
Structure IO_COUNTERS
    Public ReadOperationCount As UInt64
    Public WriteOperationCount As UInt64
    Public OtherOperationCount As UInt64
    Public ReadTransferCount As UInt64
    Public WriteTransferCount As UInt64
    Public OtherTransferCount As UInt64
End Structure

<StructLayout(LayoutKind.Sequential)>
Structure JOBOBJECT_EXTENDED_LIMIT_INFORMATION
    Public BasicLimitInformation As JOBOBJECT_BASIC_LIMIT_INFORMATION
    Public IoInfo As IO_COUNTERS
    Public ProcessMemoryLimit As UInt32
    Public JobMemoryLimit As UInt32
    Public PeakProcessMemoryUsed As UInt32
    Public PeakJobMemoryUsed As UInt32
End Structure

End Namespace

并且,在您计划实施的课程中:

<DllImport("user32.dll", SetLastError:=True)>
Public Shared Function GetWindowThreadProcessId(hWnd As IntPtr, ByRef lpdwProcessId As UInteger) As UInteger
End Function


'Implements
Dim oExcelApp = New Microsoft.Office.Interop.Excel.Application
Dim job As Jobs.Job = New Jobs.Job()
Dim pid As UInteger = 0
GetWindowThreadProcessId(New IntPtr(oExcelApp.Hwnd), pid)
job.AddProcess(Process.GetProcessById(pid).Handle)

oExcelApp.Workbooks.Open(RutaArchivoExcel)

'Code work code here

oExcelApp.Workbooks(1).Close()
oExcelApp.Quit()

'Then Dispose correctly the excel app and windows distroy appropiately the process
job.Dispose()

我用 Microsoft Excel 2016 对此进行了测试。

于 2019-01-04T19:04:14.750 回答
0

我已确保所有对象都通过后期绑定单独声明、使用和释放,并用“ONE DOT”仔细声明每个对象。确保 Excel 范围、工作表、工作簿和应用程序都被创建为对象并以相反的顺序关闭或退出,请注意没有未回答的对话框可以停止关闭或退出。使用了 InteropServices.Marshal.FinalReleaseComObject 并将 ReleaseComObject 放在一个 while 循环中,等待 count 变为零,以努力释放每一件小事。始终在 try/catch 块的 finally 子句中将对象设置为空。完成垃圾收集并等待终结者。确保我正在使用已编译的代码而不是在调试器中进行测试。换句话说,我已经阅读了每一个专家的意见并尝试了每一个专家的建议。任务管理器中仍然有孤立的 Excel 进程,在重新启动 PC 之前永远不会清理这些进程。唯一真正有效的方法是在我完成后终止进程。在我完成所有其他操作以退出、释放和垃圾收集之前,我不会杀死它。但是我不能让孤立的 excel 进程徘徊等待永远不会完成的 .NET 垃圾收集。

于 2019-01-13T04:02:06.557 回答