运行 完成之前 COM 对象上的例程?

Run a routine on COM object before finalize?

在下面定义的 class 中,我使用 public 属性 来保存一个 Excel.Application 对象实例(这是我对单例的尝试 class).

每当 Finalize 例程运行并调用 RestoreApp() 时,它会在第一次尝试访问该 Application 对象的成员时抛出一个 InvalidComObjectException(粘贴在下面) .

我写了这个简单的测试例程试图弄清楚发生了什么,我验证了我可以访问这个 属性 before the Finalize方法被调用,但当 RetoreApp() 方法尝试访问相同的 属性 时,我总是得到 InvalidComObjectException

MSDN reference for Object Lifetime 说:

Before releasing objects, the CLR automatically calls the Finalize method for objects that define a Sub Finalize procedure. The Finalize method can contain code that needs to execute just before an object is destroyed...

但是,也许 COM 对象是一种特殊情况,在 Finalize 方法 运行 之前对象被部分销毁(RCW 与底层 COM 对象断开连接)?我不知道——这似乎就是正在发生的事情。

谁能告诉我如何在我的对象与 COM 对象断开连接之前确保 ResoreApp() 过程是 运行?


测试:

Sub Main()
    Dim ExcelTest As New MyExcel
    Debug.Print(ExcelTest.Excel Is Nothing)
    Debug.Print(ExcelTest.Excel.DisplayStatusBar)
End Sub

异常:

{"COM object that has been separated from its underlying RCW cannot be used."} _className: Nothing
_COMPlusExceptionCode: -532462766
_data: Nothing
_dynamicMethods: Nothing
_exceptionMethod: Nothing
_exceptionMethodString: Nothing
_helpURL: Nothing
_HResult: -2146233049
_innerException: Nothing
_ipForWatsonBuckets: 0
_message: "COM object that has been separated from its underlying RCW cannot be used."
_remoteStackIndex: 0
_remoteStackTraceString: Nothing
_safeSerializationManager: {System.Runtime.Serialization.SafeSerializationManager}
_source: Nothing
_stackTrace: {SByte()}
_stackTraceString: Nothing
_watsonBuckets: Nothing
_xcode: -532462766
_xptrs: 0
Data: {System.Collections.ListDictionaryInternal}
HelpLink: Nothing
HResult: -2146233049
InnerException: Nothing
IPForWatsonBuckets: 0
IsTransient: False
Message: "COM object that has been separated from its underlying RCW cannot be used."
RemoteStackTrace: Nothing
s_EDILock: {Object}
Source: "mscorlib"
StackTrace: " at System.StubHelpers.StubHelpers.StubRegisterRCW(Object pThis) at
Microsoft.Office.Interop.Excel.ApplicationClass.get_DisplayStatusBar()"
TargetSite: {Void StubRegisterRCW(System.Object)}
WatsonBuckets: Nothing


Class:

Imports Microsoft.Office
Imports Microsoft.Office.Interop.Excel
Imports System.Windows.Forms


Public Class MyExcel

    Dim CreateNew As Boolean
    Dim Prepped As Boolean
    Dim Prepping As Boolean


    Dim pExcel As Microsoft.Office.Interop.Excel.Application
    Dim pStatBar As Boolean
    Dim pScreenUpdates As Boolean
    Dim pEventsEnabled As Boolean
    Dim pAlerts As Boolean


    Private Property ScreenUpdates As Boolean
        Get
            Return pScreenUpdates
        End Get
        Set(value As Boolean)
            pScreenUpdates = value
        End Set
    End Property
    Private Property EventsEnabled As Boolean
        Get
            Return pEventsEnabled
        End Get
        Set(value As Boolean)
            pEventsEnabled = value
        End Set
    End Property
    Private Property StatBar As Boolean
        Get
            Return pStatBar
        End Get
        Set(value As Boolean)
            pStatBar = value
        End Set
    End Property

    Private Property AlertsDisplay As Boolean
        Get
            Return pAlerts
        End Get
        Set(value As Boolean)
            pAlerts = value
        End Set
    End Property

    Public ReadOnly Property Excel() As Microsoft.Office.Interop.Excel.Application
        Get
            Try
                If Not CreateNew Then
                    If pExcel Is Nothing Then pExcel = GetObject([Class]:="Excel.Application")
                End If

                If pExcel Is Nothing Then pExcel = CreateObject("Excel.Application")
            Catch ex As Exception
                MessageBox.Show(ex.ToString)
            End Try
            Return pExcel
        End Get
    End Property

    Public Sub New(Optional ByVal NewExcel As Boolean = True)
        CreateNew = NewExcel
        PrepApp()
    End Sub

    Private Sub PrepApp()
        'Set prepping to true to avoid creating a stack overflow
        Try
            With Me.Excel
                ScreenUpdates = .ScreenUpdating
                EventsEnabled = .EnableEvents
                StatBar = .DisplayStatusBar
                AlertsDisplay = .DisplayAlerts
                .DisplayStatusBar = True
                .ScreenUpdating = False
                .EnableEvents = False
                .DisplayAlerts = False
            End With
        Catch ex As Exception
            MessageBox.Show(ex.ToString)
        End Try
    End Sub

    Private Sub RestoreApp()
        'Restores the original values for screen updating, event triggers, and status bar display
        Try
            With Me.Excel
                .DisplayStatusBar = StatBar
                .ScreenUpdating = ScreenUpdates
                .EnableEvents = EventsEnabled
                .DisplayAlerts = AlertsDisplay
            End With
        Catch ex As Exception
            MessageBox.Show(ex.ToString)
        End Try
    End Sub

    Protected Overrides Sub Finalize()
        RestoreApp()
        MyBase.Finalize()
    End Sub
End Class

VB.Net 终结器是不确定的,因此不允许安全访问 COM 创建的成员。 您可以通过实现 IDisposable 接口来使用确定性处理器: https://msdn.microsoft.com/en-us/library/s9bwddyx(v=vs.90).aspx

Sub Main()
    Dim xl As New MyExcel
    ...
    xl.Dispose()
End Sub

另一种方法是在终结器中使用新的 COM 实例来恢复您的设置:

Private Sub RestoreApp()
    Dim xl As Microsoft.Office.Interop.Excel.Application = pExcel
    If Me.CreateNew Then
        xl = CreateObject("Excel.Application")
    Else
        xl = GetObject(, "Excel.Application")
    End If

    xl.DisplayStatusBar = StatBar
    xl.ScreenUpdating = ScreenUpdates
    xl.EnableEvents = EventsEnabled
    xl.DisplayAlerts = AlertsDisplay
    xl.Quit()
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xl)
End Sub