@lexikos, perhaps it would be clearer to change the documentation for ComObjQuery from InterfacePointer := ComObjQuery(ComObj, SID, IID) to something like InterfaceComObj := ComObjQuery(ComObj, SID, IID) to make it clear that a pointer isn't being returned.
I already
updated the return value section to reflect the fact that passing IID_IDispatch affects the variant type of the return value (the same as with
ComObject(clsid, iid)), but overlooked that variable name. Thanks.
Also, could ComValue() perhaps be modified to accept a ComValue object and handle the ObjAddRef?
It already does, as I mentioned above.
lexikos wrote: ↑18 Aug 2022, 20:35
The conversion code shares implementation with COM methods, which allow passing a ComValue to specify the type of a parameter. In other words, it is possible to take a ComValue of one variant type, and use ComValue() to convert it to another variant type.
I've already
updated the documentation to clarify this.
Value
The value to wrap.
If this is a pure integer and VarType is not VT_R4, VT_R8, VT_DATE or VT_CY, its value is used directly; in particular, VT_BSTR, VT_DISPATCH and VT_UNKNOWN can be initialized with a pointer value.
In any other case, the value is copied into a temporary VARIANT using the same rules as normal COM methods calls. If the source variant type is not equal to VarType, conversion is attempted by calling VariantChangeType with a wFlags value of 0. An exception is thrown if conversion fails.
...
Conversion from VT_UNKNOWN to VT_DISPATCH results in a call to
IUnknown::QueryInterface, which may produce an interface pointer different to the original, and will throw an exception if the object does not implement IDispatch. By contrast, if Value is an integer and VarType is VT_DISPATCH, the value is used directly, and therefore must be an IDispatch-compatible interface pointer.
Source: ComValue - Syntax & Usage | AutoHotkey v2
In my debugging prior to writing this documentation, it appeared that ComValue(9,...) was returning an object that wrapped the same interface pointer, already known to be
IAccessible*. Just now, I took the original broken code and replaced
"Ptr", AccPtr with
"Ptr", ComObjQuery(Acc, IID.IAccessible). This fixed the crashing, which indicates that
AccPtr was
not IAccessible*. Further debugging revealed that although most of the children were already
IAccessible*, there are some for which ComObjQuery returns a different pointer, and then ComValue(9,...) returns the original pointer.
Btw, AccChild := ComValue(9, child) didn't work, results in the same error.
That's because the children are only guaranteed to be
IDispatch*, which is only sometimes (perhaps usually) the same address as the object's
IAccessible*. Since
child is an integer, ComValue does not call QueryInterface.
if (ComObjType(Acc,"Name") != "IAccessible") {
This needs to be replaced. Note what "Name" returns:
The correct way to determine that you have an
IAccessible* is to call QueryInterface, a.k.a. ComObjQuery. You can do that inline in DllCall's parameters, as I showed above.
NewError := Error("" , -1)
I'm not sure what purpose this was supposed to have, but it appears to be overwriting the previously assigned Error with one that has no message. I think that should be removed, and
NewError.Message should be added to the MsgBox call so that the actual error is displayed...
global IID
You don't need this anymore (you're already using the global object
com without declaration, so may as well remove this one).