Jump to content

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

COM_InvokeDeep - climb a COM tree more easily


  • Please log in to reply
10 replies to this topic
paulwarr
  • Members
  • 32 posts
  • Last active: Dec 04 2009 02:50 AM
  • Joined: 21 Sep 2006
COM_InvokeDeep(res, dotted-path, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
(COM Standard Library required)

The COM Standard Library allows us to drill deeply into COM objects. The function below may make that a bit easier for you to use. It parses the "dotted-path" of a COM object (using COM_Invoke to obtain the appropriate pointer to each parent, where objects in a hierarchy are separated by "."). In this example,pBody2 := COM_InvokeDeep(pweb, "document.frames[1].document.body")pBody2 returns a pointer to the body portion of the HTML document located in the second frame of the loaded web page, where pweb is a pointer to the parent IID_IHTMLWindow2.

COM_InvokeDeep can make both method calls - for example, document.getElementsByTagName[span] - and element[item] calls - for example, document.childNodes[1]

For the last loop (the element after the last period), one can pass up to 8 additional arguments to the COM_Invoke command (Note: one can pass up to 10 arguments to a COM object with COM_Invoke, but COM_InvokeDeep will consume up to two of those arguments).

Rules for use:
a) If there's a period in an element's name, don't use that name. Instead, refer to it by its element ordinal. So, for example, one will occasionally find frames in web pages that are named like the following: this.isanoddly.named.frame . Since COM_InvokeDeep (obviously) parses on the periods, returned pointers will be invalid. Instead, identify the frame by its ordinal: document.frames[0] (for first frame in the document)

B) Don't use parentheses () inside the "dotted-path" - use square brackets [] instead.

c) Don't put quotes (single or double) inside the brackets when passing a parameter to a COM object's method:
bad: document.getElementsByTagName["span"] or document.getElementsByTagName['span']
good: document.getElementsByTagName[span][/list]Sean's COM_Invoke runs the guts of this function. COM_InvokeDeep was developed by lots of folks in the AHK community in this thread (wOxxOm, daonlyfreez, Lexikos, me). Hope it's helpful to you. :)
; Usage:
;   res := COM_InvokeDeep(res, dotted-path, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
; Example:
;   pBody2 := COM_InvokeDeep(pweb, "document.frames[1].document.body")
;   (returns pointer to the body portion of the HTML document located in the second frame on the
;   loaded web page, where pweb is a pointer to the parent IID_IHTMLWindow2.
COM_InvokeDeep(obj, path, arg1="vT_NoNe", arg2="vT_NoNe", arg3="vT_NoNe", arg4="vT_NoNe", arg5="vT_NoNe", arg6="vT_NoNe", arg7="vT_NoNe", arg8="vT_NoNe")
{
   res := obj
   COM_AddRef(res) ; compensate for loop's Release()
   PathCt := 0
   Loop, Parse, Path, .
   {
      PathCt++
   }
   Loop, Parse, Path, ., ]
   {
      prop := A_LoopField
      value =
      StringGetPos, i, A_LoopField, [
      IfEqual, ErrorLevel, 0 ; contains index
      {
         StringLeft, prop, A_LoopField, %i%
         StringMid, value, A_LoopField, % i+2
      }
      If (value != "") ; contains index or parameter passed to a method, enclosed in "[]"
      {
         If (prop = "item") or (RegExMatch(value, "^[0-9]+$") = false) ; "item" already specified, or method call
         {
            If (A_Index < PathCt)
               propobj := COM_Invoke(res, prop, value)
            Else
               propobj := COM_Invoke(res, prop, value, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
            COM_Release(res)
            res := propobj
         }
         Else
         {
            propobj := COM_Invoke(res, prop)
            If (A_Index < PathCt)
               itemobj := COM_Invoke(propobj, "Item", value)
            Else
               itemobj := COM_Invoke(propobj, "Item", value, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
            COM_Release(res)
            COM_Release(propobj)
            res := itemobj
         }
      }
      Else
      {
         If (A_Index < PathCt)
            propobj := COM_Invoke(res, prop)
         Else
            propobj := COM_Invoke(res, prop, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
         COM_Release(res)
         res := propobj
      }
      if !res ; no sense in continuing - object not found (returns 0 or null)
         break
   }
   Return res
}


daonlyfreez
  • Members
  • 995 posts
  • Last active: Jan 23 2013 08:16 AM
  • Joined: 16 Mar 2005
Thank you very much for this!

8)
Posted Image mirror 1mirror 2mirror 3ahk4.me • PM or Posted Image

paulwarr
  • Members
  • 32 posts
  • Last active: Dec 04 2009 02:50 AM
  • Joined: 21 Sep 2006
Thanks! But all I did was tweak what you and wOxxOm had already posted. And thankfully, Lexikos has a much better eye for code than I do! :D

amokoura
  • Members
  • 15 posts
  • Last active: May 15 2009 11:17 AM
  • Joined: 14 Nov 2006
The standard library should have this included, very nice!
I recommend AutoIt instead of AHK.

tank
  • Administrators
  • 4345 posts
  • AutoHotkey Foundation
  • Last active: May 02 2019 09:16 PM
  • Joined: 21 Dec 2007
I have modified my copy of this as such
the reason is because when sending a 64 bit integer (Common with account numbers) the value became truncated. very nice by the way :lol: 8) :D
COM_InvokeDeep(obj, path, arg1="vT_NoNe", arg2="vT_NoNe", arg3="vT_NoNe", arg4="vT_NoNe", arg5="vT_NoNe", arg6="vT_NoNe", arg7="vT_NoNe", arg8="vT_NoNe")
{
   res := obj
   COM_AddRef(res) ; compensate for loop's Release()
   PathCt := 0
   Loop, Parse, Path, .
   {
	  PathCt++
   }
   Loop, Parse, Path, ., ]
   {
      prop := A_LoopField
      value =
      StringGetPos, i, A_LoopField, [
      IfEqual, ErrorLevel, 0 ; contains index
      {
         StringLeft, prop, A_LoopField, %i%
         StringMid, value, A_LoopField, % i+2
      }
      If (value != "") ; contains index or parameter passed to a method, enclosed in "[]"
      {
         If (prop = "item") or (RegExMatch(value, "^[0-9]+$") = false) ; "item" already specified, or method call
         {
            If (A_Index < PathCt)
               propobj := COM_Invoke(res, prop, value)
            Else
               propobj := COM_Invoke(res, prop, value, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
            COM_Release(res),	VarSetCapacity(res,		0)
            res := propobj
         }
         Else
         {
            propobj := COM_Invoke(res, prop)
            If (A_Index < PathCt)
               itemobj := COM_Invoke(propobj, "Item", value)
            Else
               itemobj := COM_Invoke(propobj, "Item", value, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
            COM_Release(res),	VarSetCapacity(res,		0)
            COM_Release(propobj),	VarSetCapacity(propobj,		0)
            res := itemobj
         }
      }
      Else
      {
         If (A_Index < PathCt)
            propobj := COM_Invoke(res, prop)
         Else
         {
			sParams	:= 12345678
			int:=False
			Loop,	Parse,	sParams
				If	arg%A_LoopField% is Integer
				{
					int:=true
					Break
				}
			If int
			{	
				Loop,	Parse,	sParams
				{	
						If	(arg%A_LoopField% == "vT_NoNe")
						{	
							arg%A_LoopField% := ""
							VT_BSTR%A_LoopField%:=""
						}
						Else
							VT_BSTR%A_LoopField%:=8
				}
				propobj := COM_Invoke_(res, prop, VT_BSTR1,arg1,VT_BSTR2,arg2, VT_BSTR3,arg3, VT_BSTR4,arg4, VT_BSTR5,arg5, VT_BSTR6,arg6, VT_BSTR7,arg7, VT_BSTR8,arg8)
			}
			Else
				propobj := COM_Invoke(res, prop, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
		 }
         COM_Release(res),	VarSetCapacity(res,		0)
         res := propobj
      }
      if !res ; no sense in continuing - object not found (returns 0 or null)
         break
   }
   Return res
}

Never lose.
WIN or LEARN.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
Inspired by this post, I wrote this. If there is no objection, I may combine this into COM_Invoke().
COM_InvokeDeep(pdsp, name,  arg0="vT_NoNe", arg1="vT_NoNe", arg2="vT_NoNe", arg3="vT_NoNe", arg4="vT_NoNe", arg5="vT_NoNe", arg6="vT_NoNe", arg7="vT_NoNe", arg8="vT_NoNe", arg9="vT_NoNe")
{
	If	InStr(name,".")
	{
		name .=	"[["
		COM_AddRef(pdsp)
		Loop,	Parse,	name, .
		{
			name :=	A_LoopField
			If	InStr(name,"[")
			Loop,	Parse,	name, [,'"]
			If	A_Index = 1
				name :=	A_LoopField
			Else If	A_Index = 2
				argn :=	A_LoopField
			Else	bend :=	True
			Else	argn :=	"vT_NoNe"
			If  Not	bend
				pobj :=	COM_Invoke(pdsp,name,argn)
			Else If	argn !=
				pobj :=	COM_Invoke(pdsp,name,argn,arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
			Else	pobj :=	COM_Invoke(pdsp,name,arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8,arg9)
			COM_Release(pdsp)
			pdsp :=	pobj
		}
		Return	pdsp
	}
}


tank
  • Administrators
  • 4345 posts
  • AutoHotkey Foundation
  • Last active: May 02 2019 09:16 PM
  • Joined: 21 Dec 2007
Fabulously shorter and distinct.
Couple things come to mind
I for one and perhaps I am the only one :?
Use COM_InvokeDeep with excell and account numbers. As Invoke will truncate large numbers it might be good to allow BSTR
consider the following code that works untill we get a large number
COM_CoInitialize()
comoWB:=COM_InvokeDeep(oExcel:=COM_ActiveXObject("Excel.Application")
					,"workbooks.add")
COM_Invoke(oExcel, "Visible=", true)
COM_Release(oExcel),oExcel:=0

;~ COM_InvokeDeep(oWB
;~ 					,"worksheets.item[Name or sheetn style reference].cells.item[Column].item[Row].value"
;~ 					,"Some Value to write")
loop 10 ; loop thru cells in a workbook
COM_InvokeDeep(oWB
					,"worksheets.item[sheet1].cells.item[" A_Index "].item[2].value"
					,A_Index)
COM_Release(oWB),oWB:=0
COM_CoUninitialize()
if i pass say a 16 digit integer it of course gets truncated
Now I could easily for my own private purposes test the arg0 to see if its an digits only and sub in COM_Invoke_() and use 8 to make it a string for the type
But I'm having some dificulty making it as short and clean as yours
My previous post in this thread describes the best tactic i could come up with

Ideas?
Never lose.
WIN or LEARN.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

As Invoke will truncate large numbers it might be good to allow BSTR

if  var is integer
may be rewritten in AHK as
if  var+0!="" && !InStr(var,".")
So, something like
if  var is [color=red]int32[/color]
can be written as
if  var+0!="" && !InStr(var,".") && var<0x80000000 && var>=-0x80000000


tank
  • Administrators
  • 4345 posts
  • AutoHotkey Foundation
  • Last active: May 02 2019 09:16 PM
  • Joined: 21 Dec 2007
This is what i ended up with in the end it was shorter to just do this
COM_InvokeDeep(pdsp, name,  arg0="vT_NoNe", arg1="vT_NoNe", arg2="vT_NoNe", arg3="vT_NoNe", arg4="vT_NoNe", arg5="vT_NoNe", arg6="vT_NoNe", arg7="vT_NoNe", arg8="vT_NoNe", arg9="vT_NoNe")
{
	If   InStr(name,".")
	{
	name .=   "[["
	COM_AddRef(pdsp)
	Loop,   Parse,   name, .
	{
		name :=   A_LoopField
		If   InStr(name,"[")
			Loop,   Parse,   name, [,'"]
				If   A_Index = 1
					name :=   A_LoopField
				Else If   A_Index = 2
					argn :=   A_LoopField
				Else   bend :=   True
		Else   argn :=   "vT_NoNe"
		If  Not   bend
			pobj :=   COM_Invoke(pdsp,name,argn)
		Else If   argn !=
            pobj :=   COM_Invoke(pdsp,name,argn,arg0,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8) 
		Else   pobj := [color=red] COM_Invoke_(pdsp,name
											,BSTR0:=(arg0!="vT_NoNe" ? 8 : ""),arg0
											,BSTR1:=(arg1!="vT_NoNe" ? 8 : ""),arg1
											,BSTR2:=(arg2!="vT_NoNe" ? 8 : ""),arg2
											,BSTR3:=(arg3!="vT_NoNe" ? 8 : ""),arg3
											,BSTR4:=(arg4!="vT_NoNe" ? 8 : ""),arg4
											,BSTR4:=(arg5!="vT_NoNe" ? 8 : ""),arg5
											,BSTR5:=(arg6!="vT_NoNe" ? 8 : ""),arg6
											,BSTR6:=(arg7!="vT_NoNe" ? 8 : ""),arg7
											,BSTR7:=(arg8!="vT_NoNe" ? 8 : ""),arg8
											,BSTR8:=(arg9!="vT_NoNe" ? 8 : ""),arg9)[/color]
		COM_Release(pdsp)
		pdsp :=   pobj
	}
	Return   pdsp
	}
}
and it works pretty fast
I am certain that someone much smarter than me will resolve this in a short method using Sean's suggestions. But I dont seem equal to the task

Sean I(am i the only one?) have no objection to either this or any other version of Deep i have long been adding it any how
Never lose.
WIN or LEARN.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

This is what i ended up with in the end it was shorter to just do this

You don't need to use COM_Invoke_(). What I meant was to replace the following condition in COM_Invoke()
If   arg%A_LoopField% Is Not Integer
with
If   arg%A_LoopField%+0=="" || InStr(arg%A_LoopField%,".") || arg%A_LoopField%>=0x80000000 || arg%A_LoopField%<-0x80000000

Sean I(am i the only one?) have no objection to either this or any other version of Deep i have long been adding it any how

I didn't try to incorporate it as I hadn't decided the level of support, should be full support or sufficient with limited support, and there already existed ones like ez_invoke() and/or COM_InvokeDeep(). Anyway, I added to COM.ahk the above condition after commenting out, so, you may just switch the two. I also incorporated the code so as to call COM_InvokeDeep() directly inside COM_Invoke(). You may download and experiment it.

tank
  • Administrators
  • 4345 posts
  • AutoHotkey Foundation
  • Last active: May 02 2019 09:16 PM
  • Joined: 21 Dec 2007
Doh i almost tried that but im pretty dumb thanks I Have Tested :D
I tested out with the alternate if statement and it works perfectly with my large integers
Thank you tons Sean :D
you are still my hero
Never lose.
WIN or LEARN.