ahk v1 IObjectComCompatible::Invoke does not support accepting return values from arguments? Topic is solved

Report problems with documented functionality
User avatar
thqby
Posts: 396
Joined: 16 Apr 2021, 11:18
Contact:

ahk v1 IObjectComCompatible::Invoke does not support accepting return values from arguments?

Post by thqby » 22 Jan 2022, 10:58

v1

Code: Select all

a := {a:1,b:2,c:3}
objenum := a._NewEnum()
comenum := ComObject(9, &objenum)
comenum.next(k, v)  ; No value returned
VarSetCapacity(k, 24, 0), VarSetCapacity(v, 24, 0)
comenum.next(ComObject(0x400c,&k),ComObject(0x400c,&v))
k := StrGet(NumGet(&k+8,"ptr"),"utf-16"), v := StrGet(NumGet(&v+8,"ptr"),"utf-16") ; No value returned
objenum.next(k, v) ; k = "c" v = "3"
v2

Code: Select all

a := {a:1,b:2,c:3}
objenum := a.OwnProps()
comenum := ComObjFromPtr(ObjPtrAddRef(objenum))
comenum(&k, &v)  ; k = "a" v = 1
comenum(ComValue(0x400c, (k := Buffer(24, 0)).Ptr), ComValue(0x400c, (v := Buffer(24, 0)).Ptr))
k := StrGet(NumGet(k, 8, "ptr")), v := NumGet(v, 8, "int64") ; k = "b" v = 2
objenum(&k, &v) ; k = "c" v = 3

swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: ahk v1 IObjectComCompatible::Invoke does not support accepting return values from arguments?

Post by swagfag » 23 Jan 2022, 15:12

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

User avatar
thqby
Posts: 396
Joined: 16 Apr 2021, 11:18
Contact:

Re: ahk v1 IObjectComCompatible::Invoke does not support accepting return values from arguments?

Post by thqby » 23 Jan 2022, 20:14

This can be done through a raw interface call, but that seems to complicate matters.
https://github.com/Lexikos/AutoHotkey_L/blob/83323202a7e48350a5e8a514e1cdd2f4de1f5977/source/script_com.cpp#L1726-L1731
I think the problem is that the VT_BYREF parameter is not marked and the corresponding parameter is updated after the call ends.

For example
https://github.com/Lexikos/AutoHotkey_L/blob/alpha/source/script_com.cpp#L1728-L1735

User avatar
thqby
Posts: 396
Joined: 16 Apr 2021, 11:18
Contact:

Re: ahk v1 IObjectComCompatible::Invoke does not support accepting return values from arguments?

Post by thqby » 10 Feb 2022, 04:03

swagfag wrote:
23 Jan 2022, 15:12
; 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")
It seems that you can use GetProcessHeaps, HeapValidate, HeapWalk these winapis to find the heap where the memory block is located, and then call HeapFree to release it. And heap handle always is equal to DllCall('GetProcessHeap', 'ptr') in the MT version.

Code: Select all

findHeap(block) {
	c := 10
	loop {
		buf := Buffer(A_PtrSize * (n := c))
		c := DllCall('GetProcessHeaps', 'uint', n, 'ptr', buf, 'uint')
	} until c <= n
	if !c
		throw
	PROCESS_HEAP_ENTRY := Buffer(3 * A_PtrSize + 16, 0)
	NumPut('short', 1, PROCESS_HEAP_ENTRY, A_PtrSize + 6)
	loop c {
		if DllCall('HeapValidate', 'ptr', heap := NumGet(buf, (A_Index - 1) * A_PtrSize, 'ptr'), 'int', 0, 'ptr', block) {
			NumPut('ptr', 0, PROCESS_HEAP_ENTRY)
			if DllCall('HeapWalk', 'ptr', heap, 'ptr', PROCESS_HEAP_ENTRY) {
				lpData := NumGet(PROCESS_HEAP_ENTRY, 'ptr')
				cbData := NumGet(PROCESS_HEAP_ENTRY, A_PtrSize, 'uint')
				lpFirstBlock := NumGet(PROCESS_HEAP_ENTRY, A_PtrSize + 16, 'ptr')
				lpLastBlock := NumGet(PROCESS_HEAP_ENTRY, 2 * A_PtrSize + 16, 'ptr')
				if lpFirstBlock <= block && block < lpLastBlock
					return heap
			}
		}
	}
	return 0
}

lexikos
Posts: 9551
Joined: 30 Sep 2013, 04:07
Contact:

Re: ahk v1 IObjectComCompatible::Invoke does not support accepting return values from arguments?  Topic is solved

Post by lexikos » 08 Apr 2022, 23:28

Correct, this is not supported by v1.
v2-changes wrote:Calls to AutoHotkey objects via the IDispatch interface now transparently support VT_BYREF parameters.

Post Reply

Return to “Bug Reports”