[Solved] IAccessible - Accessible Objects

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

[Solved] IAccessible - Accessible Objects

17 Dec 2015, 03:02

I'm currently playing around with the Acc library to understand how things are working. I tried to create own functions for the IAccessible properties and succeeded in most cases except IAccessible.accSelection (description).

When used with a 'modern' explorer file list (DirectUIHWND) it returns something recognized as an object by AHK, but I found no way to access this object. ComObjValue() and also ComObjType() return empty strings and ComObjQuery() throws the exeption 'No valid COM object!'.

The following test script will get this result if select a single item and press Ctrl+Shift+s while the mouse cursor is over an an empty area of the explorer file list. Can anyone tell me how to access this object?

Code: Select all

#NoEnv
SetBatchLines, -1

^+s::
   Acc := Acc_ObjectFromPoint()
   MsgBox, % Acc_GetRoleText(Acc_Role(Acc))
   Selection := Acc_Selection(Acc)
   If !Selection.Length()
      MsgBox, 0, Selection, Not Found!
   Else {
      Msg := ""
      For Each, Selected In Selection {
         If IsObject(Selected)
            Msg .= Acc_Name(Selected) . " - " . Acc_GetRoleText(Acc_Role(Selected)) . "`n"
         Else
            Msg .= Acc_Name(Acc, Selected) . " - " . Acc_GetRoleText(Acc_Role(Acc, Selected)) . "`n"
      }
      MsgBox, 0, Selection, %Msg%
   }
Return

; ----------------------------------------------------------------------------------------------------------------------------------
; Retrieves the address of the IAccessible interface pointer for the object displayed at a specified point on the screen.
; msdn.microsoft.com/en-us/library/windows/desktop/dd317977(v=vs.85).aspx
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_ObjectFromPoint(ByRef ChildID := "", X := "", Y := "") {
   If (X = "") || (Y = "")
      DllCall("GetCursorPos", "Int64P", PT)
   Else
      PT := (X & 0xFFFFFFFF) | ((Y & 0xFFFFFFFF) << 32)
   VarSetCapacity(CID, 24, 0)
   RC := DllCall("Oleacc.dll\AccessibleObjectFromPoint", "Int64", PT, "PtrP", pAcc, "Ptr", &CID)
   ChildID := NumGet(CID, 8, "Ptr")
   Return (RC = 0 ? ComObj(9, pAcc, 1) : RC)
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Retrieves the localized string that describes the object's role for the specified role value.
; msdn.microsoft.com/en-us/library/windows/desktop/dd318089(v=vs.85).aspx
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_GetRoleText(Role) {
   If (Size := DllCall("Oleacc.dll\GetRoleText", "UInt", Role, "Ptr", 0, "UInt", 0)) {
      VarSetCapacity(RoleText, Size * 2, 0)
      DllCall("Oleacc.dll\GetRoleText", "UInt", Role, "Str", RoleText, "UInt", Size + 1)
      Return RoleText
   }
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Retrieves the name of the object. All objects support this property.
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_Name(Acc, ChildID := 0) {
   Try
      Name := Acc.accName(ChildID)
   Catch E
      Return Acc_Error(A_ThisFunc, E)
   Return Name
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Retrieves information that describes the role of the specified object. All objects support this property.
; Role constants: msdn.microsoft.com/en-us/library/dd373608(v=vs.85).aspx
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_Role(Acc, ChildID := 0) {
   Try
      Role := Acc.accRole(ChildID)
   Catch E
      Return Acc_Error(A_ThisFunc, E)
   Return Role
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Retrieves the selected children of this object. All objects that support selection must support this property
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_Selection(Acc) {
   Selection := []
   Try
      Selected := Acc.accSelection
   Catch E
      Return Acc_Error(A_ThisFunc, E)
   If IsObject(Selected) {
      MsgBox, % "Object type: " . ComObjType(Selected)
      If !(ComObjType(Selected, "Name") = "IUnknown")
         Selection := Acc_EnumVariant(Selected)
      Else If (ComObjType(Selected, "Name") = "IAccessible")
         Selection.Push(Selected)
      Else
         Selection.Push(Acc_Query(Selected))
   }
   Else If (Selected <> "")
      Selection.Push(Selected)
   Return (Selection.Length() > 0 ? Selection : "")
}
; ==================================================================================================================================
; Auxiliary Functions - Do not call!
; ==================================================================================================================================
; ----------------------------------------------------------------------------------------------------------------------------------
; Loads the Oleacc.dll on start-up
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_Load() {
   Static AccMod := DllCall("LoadLibrary", "Str", "Oleacc.dll", "UPtr")
   Return AccMod
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Enumerates children using the IEnumVariant interface.
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_EnumVariant(pUnk) {
   Static Next := A_PtrSize * 3
   Try
      pEnumVariant := ComObjQuery(pUnk, "{00020404-0000-0000-C000-000000000046}") ; IEnumVariant
   Catch E
      Return Acc_Error(A_ThisFunc, E)
   pVTBL := NumGet(pEnumVariant + 0, "UPtr")
   Selection := []
   MsgBox, Bingo!
   Loop {
      VarSetCapacity(V, 24, 0) ; VARIANT structure
      Fetched := 0
      DllCall(NumGet(pVTBL + Next, "UInt"), 1, "Ptr", &V, "UIntP", Fetched)
      If (Fetched = 0)
         Break
      VarType := NumGet(V, 0, "UShort")
      Selected := NumGet(V, 8, VarType = 9 ? "UPtr" : "Int")
      If (VarType = 9) {
         If (ComObjType(Child, "Name") = "IAccessible")
            Selection.Push(Selected)
         Else
            Selection.Push(Acc_Query(Selected))
      }
      Else
         Selection.Push(Selected)
   }
   Return Selection.Length() ? Selection : ""
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Reports COM errors, if wanted.
; Set ReportAccError to True for testing.
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_Error(Func, E, RetVal := "") {
   Static ReportAccError := 1
   If (ReportAccError) {
      If IsObject(E)
         ErrorMsg := "Exception thrown!`n`nWhat: " . E.What . "`nFile: " . E.File . "`nLine: " . E.Line
                   . "`n`nMessage: " . E.Message . "`n`nExtra: " . E.Extra
      Else
         ErrorMsg := E
      MsgBox, 20, Error in %Func%(), %ErrorMsg%`n`n`Do you want to continue?
      IfMsgBox, No
         ExitApp
   }
   Return RetVal
}
; ----------------------------------------------------------------------------------------------------------------------------------
; Retrieves the IAccessible interface from an IDispatch interface, if any, and releases the IDispatch interface on success.
; Thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_Query(Acc) {
   Try
      IA := ComObjQuery(Acc, "{618736e0-3c3d-11cf-810c-00aa00389b71}")
   Catch E
      Return Acc_Error(A_ThisFunc, E)
   ObjRelease(Acc)
   Return ComObj(9, IA, 1)
}
Last edited by just me on 17 Dec 2015, 09:35, edited 1 time in total.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: IAccessible - Accessible Objects

17 Dec 2015, 04:35

It's an enumerator object - IEnumVARIANT is wrapped automatically to support for-loops. The only thing you can do with it is call Next.

At the moment, you can safely assume that any object returned by a COM method/property is either a ComObject or an enumerator.

In v2, type(Selected) returns "ComEnum".
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: IAccessible - Accessible Objects

17 Dec 2015, 05:29

lexikos wrote:At the moment, you can safely assume that any object returned by a COM method/property is either a ComObject or an enumerator.
Thanks a lot, lexikos, I have to keep this in mind.

An additional question, is it safe to use something like

Code: Select all

If ComObjType(Object)
   it's a COM object
Else
   it's an enumarator object
to distinct between the two types?
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: IAccessible - Accessible Objects

17 Dec 2015, 06:54

It's hard to say.

ComObjType is documented to return an empty string if the parameters are invalid, so it comes down to how the parameters should be interpreted:
ComObject: A wrapper object containing a COM object or typed value.
ComEnum is a kind of wrapper object containing a COM object (IEnumVARIANT), but it's not the kind of wrapper object expected by the function; i.e. it's currently invalid, so an empty string will be returned.

I suppose that in a future version, ComObjType could return VT_UNKNOWN and ComObjValue could return the interface pointer. If you want to be future-proof, you could allow for that possibility as well. I don't foresee any other possibilities.

I think ComEnum was really only intended to be used internally by the for-loop. I've just realized that if you try to pass it back to a COM method, it will actually pass the ComEnum object, not the IEnumVARIANT interface pointer. ComEnum objects also aren't recognized by any of the COM functions, as you've found with ComObjQuery.

I don't think ComObjType(Selected, "Name") = "IUnknown" will ever be true. The two-parameter modes of ComObjType use type information retrieved via IDispatch::GetTypeInfo. That won't work with interfaces not derived from IDispatch, like IEnumVARIANT, for obvious reasons.

Also, there appears to be a flaw of logic in the following code:

Code: Select all

      If !(ComObjType(Selected, "Name") = "IUnknown")
         Selection := Acc_EnumVariant(Selected)
      Else If (ComObjType(Selected, "Name") = "IAccessible")
         Selection.Push(Selected)
If the type is "IAccessible", it is obviously not "IUnknown". Unless I'm misreading the code, the second branch can never be executed. Actually, since it will probably never return "IUnknown", I suppose the third branch can never be executed either.
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [Solved] IAccessible - Accessible Objects

17 Dec 2015, 09:58

Thanks, lexikos, for your answer and for looking at my code sample. I tried to solve the problem for several hours before I posted this help request and changed everything in the related code again and again trying to get and access an IUnknown interface, because I was absolutely clueless about the enumerator objects. So the posted version won't work correctly, but is showing the problematic result for the given example.

This is working for me now:

Code: Select all

; ----------------------------------------------------------------------------------------------------------------------------------
; Retrieves the selected children of this object. All objects that support selection must support this property
; ----------------------------------------------------------------------------------------------------------------------------------
Acc_Selection(Acc) {
   Selection := []
   Try
      Selected := Acc.accSelection
   Catch E
      Return Acc_Error(A_ThisFunc, E)
   If IsObject(Selected) {
      If (Name := ComObjType(Selected, "Name")) {
         If (Name = "IAccessible")
            Selection.Push(Selected)
         Else If (IA := Acc_Query(Selected))
            Selection.Push(IA)
      }
      Else {
         While Selected.Next(Value, VarType)
            Selection.Push(Value)
      }
   }
   Else If (Selected <> "")
      Selection.Push(Selected)
   Return (Selection.Length() ? Selection : "")
}
Is it future-proof?
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: [Solved] IAccessible - Accessible Objects

17 Dec 2015, 16:13

No, I suppose not.
I wrote:I suppose that in a future version, ComObjType could return VT_UNKNOWN and ComObjValue could return the interface pointer. If you want to be future-proof, you could allow for that possibility as well. I don't foresee any other possibilities.
I was referring to the IEnumVARIANT interface pointer, which Acc_EnumVariant accepts.
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [Solved] IAccessible - Accessible Objects

18 Dec 2015, 02:56

I do not understand you.
You wrote:I don't think ComObjType(Selected, "Name") = "IUnknown" will ever be true. The two-parameter modes of ComObjType use type information retrieved via IDispatch::GetTypeInfo. That won't work with interfaces not derived from IDispatch, like IEnumVARIANT, for obvious reasons.
So do you mean the type of the returned object will be changed in future releases?
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: [Solved] IAccessible - Accessible Objects

18 Dec 2015, 04:33

No, I think you understand me; I just hadn't thought it through properly.

ComObjType not supporting enumerator objects could be considered a bug and might be fixed, but the ability to call .Next() must be retained, so your script is safe in that respect.

However, it's just occurred to me that ComObjType(Selected, "Name") can fail in other cases, such as for objects lacking type information or with a poor IDispatch implementation. It would be safer to check ComObjType(Selected). If blank, it's probably an enumerator object. If VT_UNKNOWN (13), you can query for IEnumVARIANT or try Acc_EnumVariant - but to test it you would have to get hold of an IEnumVARIANT pointer and manually wrap it.

Note: As of v1.1.17, it is possible for a COM method/property to return an AutoHotkey object, but only if your script passed one to a COM method/property in the first place. For instance, you can store AutoHotkey objects in a Scripting.Dictionary. However, I don't imagine there's any risk of an AutoHotkey object being returned by an IAccessible object.
just me
Posts: 9458
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: [Solved] IAccessible - Accessible Objects

18 Dec 2015, 05:05

Thanks again. I removed the Acc_IEnumVariant() function, but I will hopefully keep in mind, that it might be an option for future releases (if implemented correctly).

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: downstairs, filipemb, OrangeCat and 169 guests