Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[solved] Why I fail to create an enum object of XL.Range()?


  • Please log in to reply
12 replies to this topic
trismarck
  • Members
  • 390 posts
  • Last active: Nov 25 2015 12:35 PM
  • Joined: 02 Dec 2010
Creating the enum object explicitly fails, while for, in loop creates the same? enum object internally (at least that's how I understand how enum works / the documentation)
car := {"name" : "Ferrari"}
enum := ObjNewEnum(car)
msgbox, % isObject(enum) ; 1

oExcel := ComObjCreate("Excel.Application")
oExcel.Workbooks.Add
oExcel.Visible := true
enum := ObjNewEnum(oExcel.Range("A1", "A5"))
msgbox, % isObject(enum) ; 0 - fails

for c in oExcel.Range("A1", "A5")
	msgbox, % A_index ; 1,2,3,4,5 - works
	; for in creates enum object internally


jethrow
  • Moderators
  • 2854 posts
  • Last active: May 17 2017 01:57 AM
  • Joined: 24 May 2009
Certain Com Objects have a _NewEnum member, which will allow them to work with AHK's for-loop. The for-loop will internally call the _NewEnum() method, and fall back on the _NewEnum property if need be. For AHK Objects, the _NewEnum() method & ObjNewEnum() perform the same (outside of modified behavior), but this is not the same for Com Objects.

trismarck
  • Members
  • 390 posts
  • Last active: Nov 25 2015 12:35 PM
  • Joined: 02 Dec 2010
Thanks for the reply.
So it's like the ._NewEnum() method can be used 'externally' (in ahk script) only by ahk_object objects? (objects created by Object() function)
That is a little bit strange as it appears that .Next() should work with every object, not just ahk_object (at least that's how I understand the documentation). ... or maybe I'm wrong again. Link. Are 'additional parameters' the parameters I can use _when I overload the enum object [] operators_ inside ahk_object? (object[k, v]). When I've read this documentation page the first time, I've understood it in a way that .Next() can have i.e. more than two parameters _if_ there is for example _a COM object_ that supports three parameters (COM object that has some cumbersome internal data structure inside it). I wasn't interpreting the 'additional parameters' in terms of enum object operator overloading. Is .Next() also only available externally to ahk_objects?

jethrow
  • Moderators
  • 2854 posts
  • Last active: May 17 2017 01:57 AM
  • Joined: 24 May 2009

So it's like the ._NewEnum() method can be used 'externally' (in ahk script) only by ahk_object objects? (objects created by Object() function)

:?: - I'm not sure you're getting at:
oExcel := ComObjCreate("Excel.Application")
oExcel.Workbooks.Add
   
enum := oExcel.Range("A1", "A5")._NewEnum
Loop 5
	enum.next(c), t .= c.address "`n"

oExcel.Quit
MsgBox %t%


trismarck
  • Members
  • 390 posts
  • Last active: Nov 25 2015 12:35 PM
  • Joined: 02 Dec 2010
Thanks again.

When I was testing a similar piece of code earlier, I wrote:
oExcel := ComObjCreate("Excel.Application")
oExcel.Workbooks.Add
   
enum := oExcel.Range("A1", "A5[color=red]").Ne[/color]wEnum
MsgBox, % IsObject(enum)
Loop 5
   enum.next(c), t .= c.address "`n"

oExcel.Quit
MsgBox %t%
and I was wondering why the code fails.

[quote name="Documentation]Related
For-loop, Object.NewEnum()
[/quote]
So ObjNewEnum() works for ahk_objects only, while any_object._NewEnum() may work with certain 'other' objects also?

jethrow
  • Moderators
  • 2854 posts
  • Last active: May 17 2017 01:57 AM
  • Joined: 24 May 2009
It my sound too simple, but an object has to define a _NewEnum member for _NewEnum to work. By default, AHK Objects have a _NewEnum method. Many Com Objects have this defined as well. This allows them to be used with a for-loop. A _NewEnum member, however, up to the person who designed the Com Object to implement. The ObjMethodName functions are AHK specific - they do not call the MethodName directly.

... I was wondering why the code fails.

The Com Object does not have a NewEnum property implemented. It does, however, have a _NewEnum property.

trismarck
  • Members
  • 390 posts
  • Last active: Nov 25 2015 12:35 PM
  • Joined: 02 Dec 2010

result := obj.Method(params)
result := obj.Assign(params)
result := obj.Remove(params)
result := obj._NewEnum(params)
Calling the method this way allows each object to implement its own behaviour for that method. However, if an object overrides a built-in method (i.e. an object overrides an Assign() method), it (the object) typically needs a way to access the original functionality (original functionality: the Assign built-in method). It (the object) can do that (access the original functionality) by using the function form:

result := ObjMethod(obj, params)
result := ObjAssign(obj, params)
result := ObjRemove(obj, params)
result := ObjNewEnum(obj, params)
//Lets suppose I have two kinds of objects: ahk_objects and oth_objects ({oth_objects} = {all objects} \ {ahk_objects}). Note that while I can override the 'ahk_object' built-in method, I can't overload 'oth_object' built-in method. That is why I _need_ ObjNewEnum() for ahk_objects and at the same time I _don't need_ ObjNewEnum() for oth_objects. ObjNewEnum() restores overriden built-in method functionality, oth_objects can't have overriden built-in methods (or can they?*).

Calling the function directly is marginally faster since the name can be resolved at load-time, but the syntax is generally less intuitive. Any custom behaviour implemented by the object is bypassed, so these functions should typically be used only by the object itself.

Ok.
Just for the reference, see also: link. I've overextended Lexikos answer onto _NewEnum() method.

//Edit: * they can't:
car := { "name" : "Ferrari" }
	enum := cer.newEnum()
	;MsgBox, % IsObject(enum)

	oExcel := ComObjCreate("Excel.Application")
	oExcel.Visible := true
	oExcel.Workbooks.Add

; add custom property / method to COM object
	oExcel.customFunc := Func("oExcel_customFunc") ; unknown name
; use ahk built-in method on COM object
	oExcel.Insert("Krysia", 987) ; unknown name

	oExcel_customFunc() {
		MsgBox, dupa
	}


; Conclusion: it is futile to either:
	; add custom property to COM object
	; use AHK built-in method on COM object
		; I can see the contradiction now: how can COM object have AHK built-in method?
	
	; once object X becomes a COM object (ComObjActive() or ComObjCreate() results are assigned to an object X)
	; the object X now 'contains' only COM specific methods and none AHK built-in methods!
		; Note: it is just a coincidence that both ahk_objects and COM objects both have _NewEnum() method.
		; This is an 'exception' from the rule above (once ahk_object becomes COM object, ahk_object removes 
		; all ahk built-in methods from ahk_object's body (and adds COM specific methods to its body) ).

//edit2: note the difference between _NewEnum() and _NewEnum. COM objects don't have _NewEnum() method but COM objects have _NewEnum property. AHK objects have _NewEnum() method (and _NewEnum property?)

trismarck
  • Members
  • 390 posts
  • Last active: Nov 25 2015 12:35 PM
  • Joined: 02 Dec 2010
I'm also curious how did you know that COM Excel Range objects have _NewEnum property, as this is undocumented on msdn?

jethrow
  • Moderators
  • 2854 posts
  • Last active: May 17 2017 01:57 AM
  • Joined: 24 May 2009

COM objects don't have _NewEnum() method but COM objects have _NewEnum property.

It depends on the specific Com Object. You pointed out the Excel Range object has a _NewEnum property, but the ShellWindows object has a _NewEnum() method. Hence why I refer to the _NewEnum member :wink:

Certain Com Objects have a _NewEnum member, which will allow them to work with AHK's for-loop. The for-loop will internally call the _NewEnum() method, and fall back on the _NewEnum property if need be.


I'm also curious how did you know that COM Excel Range objects have _NewEnum property

It wouldn't work with the for-loop if it didn't have a _NewEnum property.


... or you could enumerate the Range Members:
oExcel := ComObjCreate("Excel.Application")
oExcel.Workbooks.Add

pti := GetTypeInfo(oExcel.Range("A1", "A5"))
Members := EnumComMembers(pti)
oExcel.Quit

Gui, Add, ListView, x12 y5 w250 h350, ID|Name|Type
Loop Parse, Members, `n
	RegExMatch(A_LoopField, "(\S*)\t(\S*)\s(.*)", item)
 , LV_Add("", item1, item2, item3)
LV_ModifyCol()
Gui, Show, , Excel Range Members

return

GuiClose:
	ExitApp



; http://www.autohotkey.com/forum/topic76198.html&p=472279#472279
EnumComMembers(pti) { ; releases ITypeInfo Interface
   static InvKind := {1:"[method]", 2:"[get]", 4:"[put]", 8:"[putref]"}
   { ; Com Methods
      GetTypeAttr := vTable(pti, 3)
      ReleaseTypeAttr := vTable(pti, 19)
      GetRefTypeOfImplType := vTable(pti, 8)
      GetRefTypeInfo := vTable(pti, 14)
      GetFuncDesc := vTable(pti, 5)
      ReleaseFuncDesc := vTable(pti, 20)
      GetDocumentation := vTable(pti, 12)
   }
   { ; get cFuncs (number of functions)
      DllCall(GetTypeAttr, "ptr",pti, "ptr*",typeAttr)
      cFuncs := NumGet(typeAttr+0, 40+A_PtrSize, "short")
      cImplTypes := NumGet(typeAttr+0, 44+A_PtrSize, "short")
      DllCall(ReleaseTypeAttr, "ptr",pti, "ptr",typeAttr)
   }
   if cImplTypes { ; Get Inherited Class (cImplTypes should be true)
      DllCall(GetRefTypeOfImplType, "ptr",pti, "int",0, "int*",pRefType)
      DllCall(GetRefTypeInfo, "ptr",pti, "ptr",pRefType, "ptr*", pti2)
      DllCall(GetDocumentation, "ptr",pti2, "int",-1, "ptr*",Name, "ptr",0, "ptr",0, "ptr",0) ; get Interface Name
      if StrGet(Name) != "IDispatch"
         t .= EnumComMembers(pti2) "`n"
      else,
         ObjRelease(pti2)
   }
   Loop, %cFuncs% { ; get Member IDs
      DllCall(GetFuncDesc, "ptr",pti, "int",A_Index-1, "ptr*",FuncDesc)
      ID := NumGet(FuncDesc+0, "short") ; get Member ID
      n := NumGet(FuncDesc+0, 4+3*A_PtrSize, "int") ; get InvKind
      ; Args := NumGet(FuncDesc+0, 12+3*A_PtrSize, "short") ; get Num of Args
      ; Opt := NumGet(FuncDesc+0, 14+3*A_PtrSize, "short") ; get Num of Opt Args
      DllCall(ReleaseFuncDesc, "ptr",pti, "ptr",FuncDesc)
      DllCall(GetDocumentation, "ptr",pti, "int",ID, "ptr*",Name, "ptr",0, "ptr",0, "ptr",0)
      if StrGet(Name, "UTF-16") ; Exclude Members that didn't return a Name
         t .= ID "`t" StrGet(Name, "UTF-16") "  " InvKind[n] "`n"
   }
   { ; formatting & cleanup
      t := SubStr(t,1,-1)
      Sort, t, ND`n
      ObjRelease(pti)
   }
   return, t
}


GetTypeInfo(ptr) {
    if ComObjType(ptr)=9
        ptr := ComObjValue(ptr)
    { ; Check if *ptr* has ITypeInfo Interface
        GetTypeInfoCount := vtable(ptr, 3)
        DllCall(GetTypeInfoCount, "ptr",ptr, "ptr*",HasITypeInfo)
        if Not HasITypeInfo {
            MsgBox ITypeInfo Interface not supported
            Exit
        }
    }
    GetTypeInfo := vTable(ptr, 4)
    if DllCall(GetTypeInfo, "ptr",ptr, "uint",0, "uint",0, "ptr*",pti)=0
        return, pti
}
vTable(ptr, n) { ; see ComObjQuery documentation
    return NumGet(NumGet(ptr+0), n*A_PtrSize)
}

EDIT - updated per finc's post

trismarck
  • Members
  • 390 posts
  • Last active: Nov 25 2015 12:35 PM
  • Joined: 02 Dec 2010
Ingenious. :D
Is it normal that the name of the member is only one letter?
ID	Name
-----------------------------
-4	_  [get]
0	_  [method]
0	_  [get]
0	_  [put]
0	_  [method]

6	V  [put]
6	V  [get]
46	G  [method]
110	N  [get]
110	N  [put]
111	C  [method]
112	C  [method]
113	C  [method]
114	A  [method]
117	D  [method]
118	C  [get]
122	W  [get]
123	H  [get]
126	T  [get]
127	L  [get]
129	I  [get]
134	O  [put]
134	O  [get]
136	H  [put]
136	H  [get]
137	V  [put]
137	V  [get]
138	T  [get]
146	F  [get]
148	A  [get]
149	C  [get]
150	P  [get]
170	I  [put]
170	I  [get]
193	N  [get]
193	N  [put]
197	R  [get]
201	I  [get]
201	I  [put]
208	M  [get]
208	M  [put]
209	S  [put]
209	S  [get]
213	C  [method]
226	R  [method]
235	S  [method]
236	A  [get]
237	A  [method]
238	C  [get]
239	C  [method]
240	C  [get]
241	C  [get]
242	C  [put]
242	C  [get]
243	C  [get]
244	U  [method]
245	D  [method]
246	E  [get]
247	E  [get]
248	F  [method]
249	F  [method]
250	F  [method]
251	F  [method]
252	I  [method]
253	L  [method]
254	O  [get]
255	P  [get]
255	P  [put]
256	R  [get]
257	R  [get]
258	R  [get]
259	R  [method]
260	S  [get]
260	S  [put]
261	F  [get]
261	F  [put]
262	F  [put]
262	F  [get]
263	F  [get]
263	F  [put]
264	F  [put]
264	F  [get]
265	F  [put]
265	F  [get]
266	H  [get]
267	H  [get]
268	H  [put]
268	H  [get]
269	L  [get]
269	L  [put]
271	O  [get]
271	O  [put]
272	R  [get]
272	R  [put]
273	S  [get]
274	U  [get]
274	U  [put]
275	U  [put]
275	U  [get]
276	W  [put]
276	W  [get]
279	C  [method]
281	P  [method]
304	A  [method]
348	W  [get]
398	F  [method]
399	F  [method]
400	F  [method]
410	S  [method]
435	B  [get]
437	A  [get]
441	A  [method]
448	A  [method]
449	A  [method]
457	C  [method]
458	C  [method]
464	D  [method]
472	G  [method]
477	P  [method]
481	S  [method]
482	C  [method]
495	J  [method]
496	S  [method]
497	T  [method]
500	E  [get]
501	C  [get]
502	N  [get]
503	P  [get]
504	P  [get]
505	C  [method]
510	C  [method]
511	R  [method]
543	D  [get]
544	P  [get]
545	D  [get]
546	D  [get]
551	C  [method]
564	M  [method]
565	C  [method]
568	A  [get]
571	F  [method]
585	S  [get]
585	S  [put]
586	F  [get]
586	F  [put]
603	C  [get]
691	L  [get]
716	P  [get]
731	P  [get]
740	P  [get]
793	A  [method]
876	A  [method]
877	S  [method]
878	S  [method]
879	S  [method]
880	S  [method]
881	S  [method]
882	S  [method]
883	R  [method]
905	_  [method]
910	C  [get]
916	S  [get]
975	R  [get]
975	R  [put]
1027	_  [method]
1032	N  [method]
1036	A  [method]
1037	C  [method]
1040	T  [method]
1063	A  [put]
1063	A  [get]
1067	B  [method]
1097	N  [put]
1097	N  [get]
1127	N  [method]
1131	E  [method]
1152	C  [method]
1185	A  [method]
1187	L  [get]
1380	F  [put]
1380	F  [get]
1381	I  [method]
1384	U  [method]
1385	M  [get]
1386	Q  [get]
1387	V  [get]
1388	V  [get]
1388	V  [put]
1389	A  [method]
1390	C  [method]
1391	P  [get]
1392	F  [get]
1393	H  [get]
1772	_  [method]
1811	P  [get]
1812	S  [method]
1813	I  [put]
1813	I  [get]
1928	P  [method]
2013	P  [get]
2014	D  [method]
2015	E  [get]
2016	S  [get]
2017	S  [method]
2020	A  [get]
2123	M  [get]
2257	L  [get]
2258	X  [get]
2361	P  [method]
2364	C  [method]
2491	S  [get]
2492	R  [method]
2493	E  [method]
2499	C  [get]
:D

jethrow
  • Moderators
  • 2854 posts
  • Last active: May 17 2017 01:57 AM
  • Joined: 24 May 2009
I recommend using the Unicode AHK build.

trismarck
  • Members
  • 390 posts
  • Last active: Nov 25 2015 12:35 PM
  • Joined: 02 Dec 2010
Haha!
ID	Name
-----------------------------
-4	_NewEnum  [get]
0	_Default  [get]
0	_Default  [put]
0	_Default  [method]
0	_Default  [method]
6	Value  [get]
6	Value  [put]
46	Group  [method]
110	Name  [get]
110	Name  [put]
111	Clear  [method]
112	ClearFormats  [method]
113	ClearContents  [method]
114	AutoFormat  [method]
117	Delete  [method]
...
Thank you again for all the input jethrow.

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
This Unicode problem could have been avoided by using this:
if StrGet(Name[color=red], "UTF-16"[/color]) ; Exclude Members that didn't return a Name
         t .= ID "`t" StrGet(Name[color=red], "UTF-16"[/color]) "  " InvKind[n] "`n"