发布Excel Interop com对象的最佳方式

时间:2012-10-16 13:46:48

标签: .net vb.net interop

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

我正在开发一个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

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

感谢!!!

5 个答案:

答案 0 :(得分:2)

不要手动搜索和呼叫释放。如果应用程序灾难性地崩溃,Interop进程仍然可能会松动。

我会通过Job Objects向操作系统注册Interop流程。

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

答案 1 :(得分:0)

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

How do I properly clean up Excel interop objects?

答案 2 :(得分:0)

过去我遇到麻烦没有正常关闭。

以下是我的工作:

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

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

答案 3 :(得分: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对此进行了测试。

答案 4 :(得分:0)

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