Get IAccessible of UserForm control without reference

Jack George

New Member
Joined
Jul 8, 2023
Messages
6
Office Version
  1. 2019
Platform
  1. Windows
I know we can get IAccessible of an object by first declaring a variable of type IAccessible and set it to the object we want the interface from, but that requires a reference to a TypeLib in VBE menu -> Tools ->References.
Wondering if we can do that without reference and with API only. Ideally we don't want to have to reference TypeLib for IUnknown either.

The following is what I've got so far (didn't get to sleep last night just for this):

Code in UserForm module with a textbox named "TextBox1" on it
VBA Code:
Option Explicit

#If Win64 Then
    Private Const PTR_SIZE As LongLong = 8
#Else
    Private Const PTR_SIZE As Long = 4
    Private Enum LongPtr
        [_]
    End Enum
#End If

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Declare PtrSafe Function DispCallFunc Lib "oleAut32.dll" ( _
    ByVal pvInstance As LongPtr, _
    ByVal offsetinVft As LongPtr, _
    ByVal CallConv As Long, _
    ByVal retTYP As Integer, _
    ByVal paCNT As Long, _
    ByVal paTypes As LongPtr, _
    ByVal paValues As LongPtr, _
    ByVal retVAR As LongPtr _
) As Long

Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    Destination As Any, _
    Source As Any, _
    ByVal Length As LongPtr _
)


Private Sub UserForm_Initialize()

    Dim DispCallFuncResult As Long
    Dim result As Long
    
    'IID_IAccessible: {618736E0-3C3D-11CF-810C-00AA00389B71}
    'DEFINE_GUID(IID_IAccessible, 0x618736e0, 0x3c3d, 0x11cf, 0x81, 0x0c, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
    Dim acc_guid As GUID
    With acc_guid
        .Data1 = &H618736E0
        .Data2 = &H3C3D
        .Data3 = &H11CF
        .Data4(0) = &H81
        .Data4(1) = &HC
        .Data4(2) = &H0
        .Data4(3) = &HAA
        .Data4(4) = &H0
        .Data4(5) = &H38
        .Data4(6) = &H9B
        .Data4(7) = &H71
    End With
    
    Dim pacc As LongPtr

    Dim varTypes(0 To 1) As Integer
    varTypes(0) = 29 ' VT_USERDEFINED
    varTypes(1) = VarType(pacc)

    Dim varPointers(0 To 1) As LongPtr
    varPointers(0) = VarPtr(acc_guid.Data1)
    varPointers(1) = VarPtr(pacc)

    ' Get IAccessible of TextBox1 with call to IUnknown::QueryInterface(REFIID,void**)
    DispCallFuncResult = DispCallFunc(ObjPtr(TextBox1), 0, 4, 3, 2, VarPtr(varTypes(0)), VarPtr(varPointers(0)), VarPtr(result))

    Debug.Print result, DispCallFuncResult
    
    Dim acc As Object
    If pacc <> 0 Then
        CopyMemory acc, ByVal pacc, PTR_SIZE
    End If

    Dim racc As IAccessible ' test only. our acc should equal to racc
    Set racc = TextBox1
    Debug.Print "accValue: " & racc.accValue(0&)
    
    Debug.Assert acc Is racc
    Debug.Print acc.accValue(0&)

End Sub

WARNING: The above code will crash Excel for sure...DO NOT run it directly.

Don't know what have I done wrong, or is there any better way to get the IAccessible without "type" other than "Object".
Any help would be appreciated.

Regards
 
@Jack George

Thanks for the feedback and glad you got this working.

While on the subject of IAccessible <==> IDispatch interfaces, here is a curious vba oddity when attempting to go the other way around (ie: from IAccessible ==> IDispatch):

We know that IAccessible is a dual interface so, in theory, casting an IAccessible pointer to IDispatch (Object) should work using the vb Set statement but it doesn't work.

Here is an example:

TextBox1 on Userform:

VBA Code:
Private Sub UserForm_Click()

    Dim iDispatch As Object
    Dim iAcc As IAccessible
 
    Set iDispatch = Me.TextBox1
 
    Debug.Print ObjPtr(iDispatch)
 
    Set iAcc = iDispatch  '<== Cast IDispatch Interface to IAccessible Interface.
 
    MsgBox iAcc.accValue(0&)
 
    'Now let's do the reverse:
    Set iDispatch = iAcc  '<== Cast IAccessible Interface back to IDispatch Interface.
 
    Debug.Print ObjPtr(iDispatch)
 
    MsgBox iDispatch.Value  '<== Automation Error !!!

End Sub

Being able to get a Dispatch (Object) pointer from the Accessible Interface pointer that is returned by the AccessibleObjectFromPoint api is one of those situations where this would be helpful.

I guess, a solution to this problem would be if we went low level and used the DispCallFunc api (just like we did here but in the reverse direction).
 
Upvote 0

Excel Facts

Back into an answer in Excel
Use Data, What-If Analysis, Goal Seek to find the correct input cell value to reach a desired result
@Jack George

Thanks for the feedback and glad you got this working.

While on the subject of IAccessible <==> IDispatch interfaces, here is a curious vba oddity when attempting to go the other way around (ie: from IAccessible ==> IDispatch):

We know that IAccessible is a dual interface so, in theory, casting an IAccessible pointer to IDispatch (Object) should work using the vb Set statement but it doesn't work.

Here is an example:

TextBox1 on Userform:

VBA Code:
Private Sub UserForm_Click()

    Dim iDispatch As Object
    Dim iAcc As IAccessible
 
    Set iDispatch = Me.TextBox1
 
    Debug.Print ObjPtr(iDispatch)
 
    Set iAcc = iDispatch  '<== Cast IDispatch Interface to IAccessible Interface.
 
    MsgBox iAcc.accValue(0&)
 
    'Now let's do the reverse:
    Set iDispatch = iAcc  '<== Cast IAccessible Interface back to IDispatch Interface.
 
    Debug.Print ObjPtr(iDispatch)
 
    MsgBox iDispatch.Value  '<== Automation Error !!!

End Sub

Being able to get a Dispatch (Object) pointer from the Accessible Interface pointer that is returned by the AccessibleObjectFromPoint api is one of those situations where this would be helpful.

I guess, a solution to this problem would be if we went low level and used the DispCallFunc api (just like we did here but in the reverse direction).
Sorry for not replying sooner.

I think the reason the code didn't work is deal to the fact that after the line
VBA Code:
Set iDispatch = iAcc  '<== Cast IAccessible Interface back to IDispatch Interface.
the two objects iDispatch and iAcc points to the same object:
VBA Code:
Debug.Assert ObjPtr(iDispatch) = ObjPtr(iAcc)
in other words, the iDispatch object is still IAccessible and because iDispatch is Dimed as "Object". It "SHOULD" work if iDispatch were dimmed as "IDispatch":
Code:
Dim iDispatch As IDispatch
But that line of code won't even compile because IDispatch is a "protected" interface.

I guess, a solution to this problem would be if we went low level and used the DispCallFunc api (just like we did here but in the reverse direction).
I agree and feel the same. I attempted to achieve that but no luck. I have too little knowledge about those low level stuff.

Still, I feel like it's achievable.
 
Upvote 0
@Jack George
It "SHOULD" work if iDispatch were dimmed as "IDispatch":
Sounds like the right answer.

I will give the DispCallFunc method a shot later on and see if we can come up with a way of casting IAccessible to IDispatch.

Thanks for the follow up.
 
Upvote 0

Forum statistics

Threads
1,223,705
Messages
6,173,986
Members
452,541
Latest member
haasro02

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top