我一直在努力找出分布在大约 40 个最终用户中的 PPT 插件中的错误原因。
问题:功能区状态丢失/功能区UI 对象丢失。
对于某些用户,最终Rib
对象变为Nothing
.
用户向我保证,他们没有收到任何运行时错误或脚本错误(来自我们也通过此加载项调用的 COM 对象)。未处理的错误,如果用户点击End
会导致状态丢失。
没有一个用户能够可靠地重现导致观察到的故障的场景。这使得故障排除非常困难。我希望不希望有一些明显的东西我错过了,或者我没有预料到。
我目前如何处理丢失或 RibbonUI
为了解决这个问题,我将指向功能区的对象指针存储在三个地方,这对我来说似乎有点矫枉过正,但显然还不够:
- 一个被调用的类对象
cbRibbon
有一个.RibbonUI
被赋值的属性;Set cbRibbon.RibbonUI = Rib
在功能区的onLoad
回调过程中。所以我们有一个byRef
对象本身的副本。如果丝带什么都不是,理论上我可以Set rib = cbRibbon.RibbonUI
,除非cbRibbon
对象也超出范围,否则这可行。 - 该
cbRibbon
对象具有.Pointer
分配的属性:cbRibbon.Pointer = ObjPtr(Rib)
。 - 一个
CustomDocumentProperty
名为“RibbonPointer”的指针也用于存储对对象指针的引用。(注意:这甚至在状态丢失之后仍然存在)
因此,您可以看到我已经对此进行了一些思考,试图复制存储此指针的方式,就像将其存储在 Excel 中的隐藏工作表/范围中一样。
附加信息
我可以从强大的客户端日志记录中看到,此错误似乎通常发生但并非总是在以下过程中发生,该过程用于刷新/使功能区及其控件无效。
每当我需要动态刷新功能区或其部分控件时,都会调用此过程:
Call RefreshRibbon(id)
该错误似乎(有时,我不能强调这一点:该错误不能按需复制)发生在完全刷新期间,称为:
Call RefreshRibbon("")
这是进行失效的过程:
Sub RefreshRibbon(id As String)
If Rib Is Nothing Then
If RibbonError(id) Then GoTo ErrorExit
End If
Select Case id
Case vbNullString, "", "RibbonUI"
Call Logger.LogEvent("RefreshRibbon: Rib.Invalidate", Array("RibbonUI", _
"Ribbon:" & CStr(Not Rib Is Nothing), _
"Pointer:" & ObjPtr(Rib)))
Rib.Invalidate
Case Else
Call Logger.LogEvent("RefreshRibbon: Rib.InvalidateControl", Array(id, _
"Ribbon:" & CStr(Not Rib Is Nothing), _
"Pointer:" & ObjPtr(Rib)))
Rib.InvalidateControl id
End Select
Exit Sub
ErrorExit:
End Sub
如您所见,我在此过程中做的第一件事是测试Rib
对象的Nothing
-ness。如果计算结果为True
,则 RibbonUI 对象以某种方式丢失。
然后错误函数尝试重新实例化功能区:首先来自cbRibbon.RibbonUI
,然后来自cbRibbon.Pointer
,如果两者都失败,则来自CustomDocumentProperties("RibbonPointer")
值。如果这些都没有成功,那么我们会显示一个致命错误,并提示用户关闭 PowerPoint 应用程序。如果其中任何一项成功,则功能区将以编程方式重新加载,一切都会继续工作。
这是该过程的代码。请注意,它调用了我没有包含代码的其他几个过程。这些是辅助函数或记录器函数。该.GetPointer
方法实际上调用 WinAPICopyMemory
函数以从其指针值重新加载对象。
Function RibbonError(id As String) As Boolean
'Checks for state loss of the ribbon
Dim ret As Boolean
If id = vbNullString Then id = "RibbonUI"
Call Logger.LogEvent("RibbonError", Array("Checking for Error with Ribbon" & vbCrLf & _
"id: " & id, _
"Pointer: " & ObjPtr(Rib), _
"cbPointer: " & cbRibbon.Pointer))
If Not Rib Is Nothing Then
GoTo EarlyExit
End If
On Error Resume Next
'Attempt to restore from class object:
Set Rib = cbRibbon.ribbonUI
'Attempt to restore from Pointer reference if that fails:
If Rib Is Nothing Then
'Call Logger.LogEvent("Attempt to Restore from cbRibbon", Array(cbRibbon.Pointer))
If Not CLng(cbRibbon.Pointer) = 0 Then
Set Rib = cbRibbon.GetRibbon(cbRibbon.Pointer)
End If
End If
'Attempt to restore from CDP
If Rib Is Nothing Then
'Call Logger.LogEvent("Attempt to Restore from CDP", Array(MyDoc.CustomDocumentProperties("RibbonPointer")))
If HasCustomProperty("RibbonPointer") Then
cbRibbon.Pointer = CLng(MyDoc.CustomDocumentProperties("RibbonPointer"))
Set Rib = cbRibbon.GetRibbon(cbRibbon.Pointer)
End If
End If
On Error GoTo 0
If Rib Is Nothing Then
Debug.Print "Pointer value was: " & cbRibbon.Pointer
'Since we can't restore from an invalid pointer, erase this in the CDP
' a value of "0" will set Rib = Nothing, anything else will crash the appliation
Call SetCustomProperty("RibbonPointer", "0")
Else
'Reload the restored ribbon:
Call RibbonOnLoad(Rib)
Call SetCustomProperty("RibbonPointer", ObjPtr(Rib))
cbRibbon.Pointer = ObjPtr(Rib)
End If
'Make sure the ribbon exists or was able to be restored
ret = (Rib Is Nothing)
If ret Then
'Inform the user
MsgBox "A fatal error has been encountered. Please save & restart the presentation", vbCritical, Application.Name
'Log the event to file
Call Logger.LogEvent("RibbonError", Array("FATAL ERROR"))
Call ReleaseTrap
End If
EarlyExit:
RibbonError = ret
End Function
所有这些在理论上都非常有效,实际上我可以直接终止运行时(通过调用End
语句或其他方式),并且这些过程按预期重置功能区。
那么,我错过了什么?