Enumerator::Next(Var *aKey, Var *aVal) needs 0, 1 or 2
Var pointers
https://github.com/Lexikos/AutoHotkey_L/blob/master/source/script_object.cpp#L1645
when u do
comenum.next(k, v), on the way in, whatever parameters u pass in, get encoded as a
VARIANT(since thats what a COM method would normally expect to work with). theres no mechanism to signal the uninitialized variable is supposed to be treated byref, so the variable's value(an empty string) get encoded as variant containing an empty
VT_BSTR
https://github.com/Lexikos/AutoHotkey_L/blob/master/source/script_com.cpp#L1242
https://github.com/Lexikos/AutoHotkey_L/blob/83323202a7e48350a5e8a514e1cdd2f4de1f5977/source/script_com.cpp#L767-L768
https://github.com/Lexikos/AutoHotkey_L/blob/83323202a7e48350a5e8a514e1cdd2f4de1f5977/source/var.h#L519-L520
https://github.com/Lexikos/AutoHotkey_L/blob/83323202a7e48350a5e8a514e1cdd2f4de1f5977/source/script_com.cpp#L788-L791
later, some COM magic happens and its finally time to invoke ur IDispatch-queried-method(ie
Enumerator::Next(Var *aKey, Var *aVal)). u want to convert ur VARIANTs back into
Variables, but thats impossible, since theyre VARIANTS of empty BSTRings now
https://github.com/Lexikos/AutoHotkey_L/blob/83323202a7e48350a5e8a514e1cdd2f4de1f5977/source/script_com.cpp#L1651
https://github.com/Lexikos/AutoHotkey_L/blob/83323202a7e48350a5e8a514e1cdd2f4de1f5977/source/script_com.cpp#L602-L634
u invoke. these 2 macros check if the from-VARIANT-converted-tokens u passed in are convertible to
Vars - theyre not(remember: theyre empty strings now), so a nullptr is assigned instead. if u pass nullptrs in, the enumerator's Next skip its assignment routines and just iterates over to the next element
https://github.com/Lexikos/AutoHotkey_L/blob/master/source/script_object.cpp#L1638-L1639
https://github.com/Lexikos/AutoHotkey_L/blob/master/source/script_func_impl.h#L26
https://github.com/Lexikos/AutoHotkey_L/blob/master/source/script_object.cpp#L1650
https://github.com/Lexikos/AutoHotkey_L/blob/master/source/script_object.cpp#L1659
when u do
comenum.next(ComObject(0x400c,&k),ComObject(0x400c,&v)), its a similar problem, except with Objects instead of strings. u cant recover the original
Var, so the enumerator will always run with nullptrs as arguments
u can skip IDispatch and call the method directly. this somewhat works, but is unusable. for 32bit u need the
__thiscall shim
viewtopic.php?f=76&t=91105&p=402798, since
Enumerator is a COM object(inherit from IUnknown) but the signature lacks
__stdcall. for 64bit, u can ignore this. but theres still a problem if the Var is supposed to contain strings - u need to deallocate them before the Var goes out of scope. if u deallocate them, u get heap corruption and the script crashes. if u dont, then u get a memory leak obviously. maybe u can debug this further if u need it that bad
Code: Select all
#Requires AutoHotkey v1.1.33.10
a := {aaa:111}
objenum := a._NewEnum()
pObjEnum := &objenum
; some constants from the ahk src
ALLOC_MALLOC := 2
VAR_LOCAL := 0x02
VAR_DECLARED := 0x40
VAR_DECLARE_LOCAL := VAR_DECLARED | VAR_LOCAL
VAR_NORMAL := 1
; x64 only code, artificially create "fake" variables
; 1>class Var size(48):
; 1> +---
; 1> 0 | mContentsInt64
; 1> 0 | mContentsDouble
; 1> 0 | mObject
; 1> 8 | mByteContents
; 1> 8 | mCharContents
; 1>16 | mByteLength
; 1>16 | mAliasFor
; 1>24 | mByteCapacity
; 1>24 | mBIV
; 1>32 | mHowAllocated
; 1>33 | mAttrib
; 1>34 | mScope
; 1>35 | mType
; 1> | <alignment member> (size=4)
; 1>40 | mName
VarSetCapacity(k, sizeofVar := 48, 0)
NumPut(ALLOC_MALLOC, k, mHowAllocated := 32, "UChar")
NumPut(VAR_DECLARE_LOCAL, k, mScope := 34, "UChar")
NumPut(VAR_NORMAL, k, mType := 35, "UChar")
VarSetCapacity(v, sizeofVar, 0)
NumPut(ALLOC_MALLOC, v, mHowAllocated, "UChar")
NumPut(VAR_DECLARE_LOCAL, v, mScope, "UChar")
NumPut(VAR_NORMAL, v, mType, "UChar")
; "ComCall" the Next method
; 1>class Object::Enumerator size(16):
; 1> +---
; 1> 0 | +--- (base class EnumBase)
; 1> 0 | | +--- (base class ObjectBase)
; 1> 0 | | | +--- (base class IObjectComCompatible)
; 1> 0 | | | | +--- (base class IObject)
; 1> 0 | | | | | +--- (base class IDispatch)
; 1> 0 | | | | | | +--- (base class IUnknown)
; 1> 0 | | | | | | | {vfptr}
; 1> | | | | | | +---
; 1> | | | | | +---
; 1> | | | | +---
; 1> | | | +---
; 1> 4 | | | mRefCount
; 1> | | +---
; 1> | +---
; 1> 8 | mObject
; 1>12 | mOffset
; 1> +---
; 1>Object::Enumerator::$vftable@:
; 1> | &Enumerator_meta
; 1> | 0
; 1> 0 | &IObjectComCompatible::QueryInterface
; 1> 1 | &ObjectBase::AddRef
; 1> 2 | &ObjectBase::Release
; 1> 3 | &IObjectComCompatible::GetTypeInfoCount
; 1> 4 | &IObjectComCompatible::GetTypeInfo
; 1> 5 | &IObjectComCompatible::GetIDsOfNames
; 1> 6 | &IObjectComCompatible::Invoke
; 1> 7 | &EnumBase::Invoke
; 1> 8 | &Object::Enumerator::Type
; 1> 9 | &ObjectBase::DebugWriteProperty
; 1>10 | &ObjectBase::Delete
; 1>11 | &Object::Enumerator::{dtor}
; 1>12 | &Object::Enumerator::Next
; x64 only!!, x86 requires the __thiscall shim
; int Object::Enumerator::Next(Var *aKey, Var *aVal)
hr := DllCall(NumGet(NumGet(pObjEnum+0, "Ptr"), 12 * A_PtrSize, "Ptr"), "Ptr", pObjEnum, "Ptr", &k, "Ptr", &v, "Int")
key := fakeVarContentsToNormalVar(&k)
val := fakeVarContentsToNormalVar(&v)
MsgBox % key "`n" val
fakeVarContentsToNormalVar(pFakeVar) {
; more constants
static VAR_ATTRIB_OBJECT := 0x02 ; mObject contains an object; mutually exclusive of the cache attribs.
static VAR_ATTRIB_HAS_VALID_INT64 := 0x10 ; Cache type 1. Mutually exclusive of the other two.
static VAR_ATTRIB_HAS_VALID_DOUBLE := 0x20 ; Cache type 2. Mutually exclusive of the other two.
; figure out what the Var contains
mAttrib := NumGet(pFakeVar+33, "UChar")
; 1>class Var size(48):
; 1> +---
; 1> 0 | mContentsInt64
; 1> 0 | mContentsDouble
; 1> 0 | mObject
; 1> 8 | mByteContents
; 1> 8 | mCharContents
; 1>16 | mByteLength
; 1>16 | mAliasFor
; 1>24 | mByteCapacity
; 1>24 | mBIV
; 1>32 | mHowAllocated
; 1>33 | mAttrib
; 1>34 | mScope
; 1>35 | mType
; 1> | <alignment member> (size=4)
; 1>40 | mName
switch
{
case (mAttrib & VAR_ATTRIB_OBJECT):
pObj := NumGet(pFakeVar+0, "Ptr") ; get ptr from Var.mObject
contents := Object(pObj) ; reconstruct the obj from the pointer and assign it to a "normal" ahk var
ObjRelease(pObj) ; this counteracts the AddRef() that Var::ObjectAssign did when storing the object in our "fake" enumerator outparam ahk var
case (mAttrib & VAR_ATTRIB_HAS_VALID_INT64):
contents := NumGet(pFakeVar+0, "Int64") ; nothing special to do here, get int from Var.mContentsInt64
case (mAttrib & VAR_ATTRIB_HAS_VALID_DOUBLE):
contents := NumGet(pFakeVar+0, "Double") ; nothing special to do here, get int from Var.mContentsDouble
default: ; otherwise it must be a string
pStr := NumGet(pFakeVar+8, "Ptr") ; get strPtr from Var.mCharContents
contents := StrGet(pStr+0) ; get string from strPtr
; also need to free the ALLOC_MALLOC'd memory for the string, but this
; crashes the script, so i gave up on it. u have a memory leak here
; DllCall("msvcrt\free", "Ptr", pStr, "cdecl")
}
return contents
}
in v2, this was probably "fixed" when
VarRef and the byref changes got introduced. for v1, idk if this will ever be "fixed", assuming its at all fixable. maybe with a new type
VT_PTR or
VT_USERDEFINED. seems like a very niche case either way