0

There is a known issue that after losing reference to IRibbonUI ribbon object, there is no way to retrieve a reference to it. The only way is to restart the application (I am talking about MS Access)

An idea to bypass this issue in MS Excel came up by Rory A. (about 12 years ago...). can be seen here.

What I did, instead of saving the reference to the object in Excel table cell, I simply saved it in a table. When it came to the line of code where it attempts to copy the reference back to an access object it causes the application to crash. The function RetrieveObjRef is called after losing reference to ribbon. For testing, I needed to reach the case where I lose reference to ribbon. I simply hit the reset button in the VBA IDE.

Any help would be appreciated.

My code: Module #1 - the original place where we save reference to ribbon:

Public Sub OnRibbonLoad(ribbon As IRibbonUI)
        ...
        Set gobjRibbon = ribbon
        Set gobjMainRibbon = ribbon

In Module #2:

    Sub StoreObjRef(obj As Object)
    ...
    Dim strx As String
    #If VBA7 Then
        Dim longObj As LongPtr
    #Else
        Dim longObj As Long
    #End If
    
    longObj = ObjPtr(obj)
    strx = "DELETE * FROM ribbonRef"
    Call runsqlstr(strx)
    
    strx = "INSERT INTO ribbonRef (objRef) SELECT " & longObj
    Call runsqlstr(strx)
    ...
    End Sub
                           
    Sub RetrieveObjRef()
    ...
    Dim obj As Object
    #If VBA7 Then
        Dim longObj As LongPtr
    #Else
        Dim longObj As Long
    #End If
    longObj = Nz(dlookupado("objRef", "ribbonRef", , True), 0)
    
    If longObj <> 0 Then
        Call CopyMemory(obj, longObj, 4) ' This line causes application crash!!!'
        Set gobjRibbon = obj
        Set gobjMainRibbon = obj
    End If
    ...
    End Sub

In Module #3

    #If VBA7 Then
        Public Declare PtrSafe Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" (Destination As Any, source As Any, ByVal length As LongPtr)
    #Else
        Public Declare Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" (Destination As Any, source As Any, ByVal length As Long)
    #End If

And finally, in Module #4:

        If gobjMainRibbon Is Nothing Then
            Call RetrieveObjRef
        End If
    
        Call StoreObjRef(gobjMainRibbon)

ribbonRef.objRef

I tried saving the reference value in access table ("ribbonRef"), I was expecting that if this worked for many others, why should it not work for me

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • Try using `CopyMemory obj, longObj, LenB(longObj)` - no need for `Call` – Ike Dec 13 '22 at 09:59
  • 2
    Are you using 64-bits Office? If so, you probably want to make sure you're also able to store 64-bits integers in that table. Also, this entire approach is a bad idea, you can just have a global to store the object, and if that global is somehow lost then I wouldn't trust this code one bit to retrieve the right reference. – Erik A Dec 13 '22 at 10:00
  • 1
    Ah, I see you've added the table definition, `Long Integer` is a `Long` and can only store 32-bits, meaning your code is truncating the pointer which will cause crashes. You need a BigInt, and support for that has only been added recently, or as a workaround, you could just store it as binary data, e.g. `CREATE TABLE Table1(ID AUTOINCREMENT , Pointer VARBINARY(16))` – Erik A Dec 13 '22 at 10:09
  • 1
    *"after losing reference to IRibbonUI ribbon object, there is no way to retrieve a reference to it"* - This reads like *"not losing the reference"* is the actual solution, so store the interface pointer in a safe place. If your code cannot arrange for that, the [Running Object Table](https://learn.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-irunningobjecttable) would be an option. – IInspectable Dec 13 '22 at 10:30
  • @ErikA, that's the whole point. The reference is kept in a global and yet it gets lost. That is why so many dealt with this issue as you may see for example in Rory's answer – Oriel Tzvi Shaer Dec 13 '22 at 10:50
  • @Ike, your suggestion solved the problem! Thank-you. Now, how do I mark your answer as 'accepted'? – Oriel Tzvi Shaer Dec 13 '22 at 10:59
  • @ErikA, what you wrote about needing a BigInt is very important point. I need to see how to create such data type for Access local table... So your answer is helping me solve a problem I was certainly going to face with users in 64-bit platform. Thank-you – Oriel Tzvi Shaer Dec 13 '22 at 11:25
  • That doesn't make sense. If indeed your global gets destroyed, then it will `Release()` its ref count on the `IRibbonUI`. Pulling out the pointer, serializing it, and later deserializing and using it works by coincidence, at best. That pointer has officially relinquished its privilege to touch the interface by dropping its ref count. The question you probably should have asked instead is this: *"How do I make sure that object X lives at least as long as some other object?"* – IInspectable Dec 13 '22 at 11:54
  • That is, after having verified that the statement *"There is a known issue that after losing reference to IRibbonUI ribbon object, there is no way to retrieve a reference to it."* is actually true. I'm having a feeling that it isn't, and that you can always get a reference to any live `IRibbonUI` interface, at any time, from just about anywhere. Just takes research to find out how to navigate the UI tree (which is probably poorly documented). – IInspectable Dec 13 '22 at 12:07
  • @IInspectable, 1 - "Running Object Table" looks interesting, but I'm not familiar with it, and it would need me to get into it, while I'm not sure it's what I need. Thank-you still. 2 - I agree it doesn't make sense, at first glance, but if it were to get reset, then I would expect the OnLoad event of ribbon to triggered once more, which it doesn't. 3 - I've quite verified this statement, at least for myself, after trying myself to solve in a "normal" way, and seeing many posts saying there is no way to solve this other than restarting the application – Oriel Tzvi Shaer Dec 13 '22 at 13:45
  • *"if it were to get reset, then I would expect the OnLoad event of ribbon to triggered once more"* - That's not how things work. It's not the control behind the `IRibbonUI` interface that gets reset, but your reference to the interface. It's a COM pointer under the hood, and when that gets dropped it tells the COM server that you are done using the control, and if there aren't any other outstanding references to it, the *control* is eligible for destruction. The only way for your code to make sure that the control doesn't get destroyed is by keeping a reference to its interface alive. – IInspectable Dec 14 '22 at 09:42
  • @IInspectable, is there a way I could continue this conversation with you in private, I would like to further understand, and I understand it doesn't suit this comments area... – Oriel Tzvi Shaer Dec 15 '22 at 06:36

2 Answers2

0

Try using CopyMemory obj, longObj, LenB(longObj) - no need for Call

Ike
  • 9,580
  • 4
  • 13
  • 29
0

I agree with @IInspectable that this solution is problematic, though this is the best I can get at the moment, and it seems good for me.

Also, please see here Tom van Stiphout's words.

For the sake of those trying to get this working, I'm writing down here the solution in my case, with help from @Ike and @ErikA (yet not forgetting @IInspectable 's important comments!):

I also found a way to make sure that my reference was saved in this instance of opening Access file, with help from Phil.Wheeler's answer here

In Module:

#If VBA7 Then
    Public Declare PtrSafe Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" (Destination As Any, source As Any, ByVal length As LongPtr)
#Else
    Public Declare Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" (Destination As Any, source As Any, ByVal length As Long)
#End If

...

Sub StoreObjRef(obj As Object)
    On Error GoTo HandleErr
#If VBA7 Then
    Dim longObj As LongPtr
#Else
    Dim longObj As Long
#End If

longObj = ObjPtr(obj)
If longObj <> Nz(dlookupado("objRef", "ribbonRef", , True), 0) Then
    Call runsqlstr("DELETE * FROM ribbonRef")
    Call runsqlstr("INSERT INTO ribbonRef (objRef) SELECT CStr(" & longObj & ")")

    DoCmd.OpenForm "refFlag", acNormal, , , , acHidden
End If

ExitHere:
    Exit Sub
Resume
HandleErr:
    ...
End Sub
                       
Sub RetrieveObjRef()
' Retrieve the object reference
On Error GoTo HandleErr
Dim obj As Object
#If VBA7 Then
    Dim longObj As LongPtr
#Else
    Dim longObj As Long
#End If
longObj = Nz(dlookupado("objRef", "ribbonRef", , True), 0)

' If refFlag form is open then we know that the reference was saved in this opening of Access file
If longObj <> 0 And CurrentProject.AllForms("refFlag").IsLoaded Then
    CopyMemory obj, longObj, LenB(longObj)
    Set gobjRibbon = obj
    Set gobjMainRibbon = obj
End If

ExitHere:
    Exit Sub
Resume
HandleErr:
    ...
End Sub

Storing the reference right after setting of reference object:

Public Sub OnRibbonLoad(ribbon As IRibbonUI)
    ...
    
    Set gobjRibbon = ribbon
    Set gobjMainRibbon = ribbon

    Call StoreObjRef(gobjMainRibbon)
    ...

And finally, use it where I need:

If gobjMainRibbon Is Nothing Then Call RetrieveObjRef

ribbonRef table:

ribbonRef

Tested on both MS Access 64 bit and 32 bit versions.

Though be aware to @IInspectable 's comment

It's not the control behind the IRibbonUI interface that gets reset, but your reference to the interface. It's a COM pointer under the hood, and when that gets dropped it tells the COM server that you are done using the control, and if there aren't any other outstanding references to it, the control is eligible for destruction. The only way for your code to make sure that the control doesn't get destroyed is by keeping a reference to its interface alive.

... So we may have got a reference to the same place in memory, though it could be that that place in memory will be used for something else... and that might cause app to crash... ...