Jump to content

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

DispatchObj (prototype)


  • Please log in to reply
25 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Note: This script is no longer maintained. For an equivalent script compatible with AutoHotkey_L, see ComDispatch.

DispatchObj

Requirement: COM Standard Library

DispatchObj allows an AutoHotkey script to create an Invoke'able object. This object can then be passed to JavaScript, allowing it to call functions defined in AutoHotkey.
obj := DispatchObj_Create("func")
MsgBox % COM_Invoke(obj, "func")
func() {
  return 1337
}
At the time I wrote it, I wasn't satisfied with how parameters are handled. It was not possible to call functions dynamically with string parameters (without extra coercion code in each function), so parameters must be extracted using DispatchObj_Param().

I'm still not satisfied with the script's current state, but it has been collecting dust for a while. Since it may be of some use in its current state, I thought I should release it.

Functions take the following style:
func(this, params, result)   ; all parameters are optional
{
    ; Get the value and type of the first parameter.
    value := DispatchObj_Param(params, 1, type)
    if type = 9 ; VT_DISPATCH (an object)
        COM_Invoke(value, "someMethod")

    ; Return an integer (zero):
    ;   optional since VT_I4 (int) is the default:
    NumPut(3, result+0,0,"ushort") ; result.vt=VT_I4;
    ;   method 1
    NumPut(123, result+8,0,"int") ; result.lVal=123;
    ;   method 2, overrides method 1 if non-zero. only works with int.
    return 123
}
An advantage of the DispatchObj_Param method is that it allows the parameters to be validated, making the function more robust. For instance, if JavaScript passes an integer when an object is expected, the script can return an error code rather than crashing as a result of invoking on an invalid pointer.
; Builds an object from a list of methods. MethodList takes the following form:
;   script_function_1,object_method_alias=script_function_2,...
DispatchObj_Create( MethodList )
{
    id_count = 0
    fail_count = 0
    
    method_names =
    
    Loop, Parse, MethodList, `,, %A_Space%%A_Tab%
    {
        ; method: member name of method in object.
        ; function: name of actual script function.
        if (pos := InStr(A_LoopField,"=")) {
            method := SubStr(A_LoopField,1,pos-1)
            function := SubStr(A_LoopField,pos+1)
        } else
            method := function := A_LoopField
        
        ; "CDecl" so we can pass more parameters than it expects.
        if (method="") or !(cb := RegisterCallback(function,"CDecl"))
        {
            fail_count += 1
            continue
        }
        id_count += 1
        cb%id_count% := cb
        ; Store zero-based byte-offset from start of method_names.
        method%id_count% := StrLen(method_names)
        method_names .= method ","
    }

    if id_count = 0
        return 0,  ErrorLevel:="NO VALID METHODS"
    
    ; Memory organisation:
    ;   *IDispatch ref_count reserved reserved
    ;   id_count ( *name *callback )* ( name )*
    
    ; If a method is executing, cur_params is a pointer to a DISPPARAMS
    ;   structure and cur_var_result is a pointer to a VARIANT structure
    ;   where the result of the method call is stored.
    
    pObj := COM_CoTaskMemAlloc(20+8*id_count+StrLen(method_names)+1)
    pNames := pObj+20+8*id_count
    
    NumPut(DispatchObj_CreateIDispatch(), pObj+0)   ; &IDispatch
    , NumPut(1,         pObj+4)     ; ref_count
    , NumPut(0,         pObj+8)     ; reserved
    , NumPut(0,         pObj+12)    ; reserved
    , NumPut(id_count,  pObj+16)    ; id_count
 
    DllCall("lstrcpy","uint",pNames,"str",method_names)
    ; null-terminate each name.
    Loop, Parse, method_names
        if (A_LoopField=",")
            NumPut(0,pNames-1,A_Index,"char")

    Loop %id_count%
        NumPut(cb%A_Index%, NumPut(pNames+method%A_Index%, pObj+20, 8*(A_Index-1)))

    return pObj
}

; Gets a parameter from a DISPPARAMS structure.
; params:   Address of DISPPARAMS structure.
; index:    ONE-based parameter index.
; type:     [out] The *original* VARTYPE of the parameter VARIANT.
; returns:  Parameter value, coerced to string where applicable.
DispatchObj_Param(params, index, ByRef type="")
{
    if !params or index<1 or index>NumGet(params+8)
        return
    VarSetCapacity(var,16), DllCall("RtlMoveMemory","uint",&var
        ,"uint",NumGet(params+0)+(NumGet(params+8)-index)*16,"uint",16)
    type := NumGet(var,0,"ushort")
    if type in 0,4,5,6,7,14
        DllCall("oleaut32\VariantChangeType","uint",&var,"uint",&var,"ushort",0,"ushort",8)
    return NumGet(var,0,"ushort")=8 ? COM_Ansi4Unicode(NumGet(var,8)) : NumGet(var,8)
}


; Based on COM_DispInterface()
DispatchObj_IDispatch(this, prm1=0, prm2=0, prm3=0, prm4=0, prm5=0, prm6=0, prm7=0, prm8=0)
{
    Critical
    If	A_EventInfo = 6
    {
        if ((prm4 & 1) && prm1>=1 && prm1<=NumGet(this+16)) {
            NumPut(3,prm6+0,0,"ushort") ; default to Int return type.
            NumPut(0,prm6+8,0,"int")    ; default to 0 return value.
            i:=DllCall(NumGet(this+24+8*(prm1-1)),"uint",this,"uint",prm5,"uint",prm6,"CDecl")
            if i != 0                   ; if non-zero, set new return value.
                NumPut(i,prm6+8,0,"int")
            hResult := 0
        } else
            hResult := 0x80020003
    }
    Else If	A_EventInfo = 5
    {
        Loop, %prm3%
            NumPut(-1, prm5+4*(A_Index-1))
        name := COM_Ansi4Unicode(NumGet(prm2+0))
        Loop % NumGet(this+16)
            if !DllCall("lstrcmpi","str",name,"uint",NumGet(this+20+8*(A_Index-1)))
            {
                NumPut(A_Index, prm5+0)
                break
            }
        hResult := (prm3>1 or -1=NumGet(prm5+0,0,"int")) ? 0x80020006 : 0
    }
    Else If	A_EventInfo = 4
        NumPut(0,prm3+0), hResult:=0x80004001
    Else If	A_EventInfo = 3
        NumPut(0,prm1+0), hResult:=0
    Else If	A_EventInfo = 2
    {
        NumPut(hResult:=NumGet(this+4)-1,this+4)
        if !hResult {
            Loop % NumGet(this+16)
                DllCall("GlobalFree","uint",NumGet(this+24+8*(A_Index-1)))
            COM_CoTaskMemFree(this)
        }
    }
    Else If	A_EventInfo = 1
        NumPut(hResult:=NumGet(this+4)+1,this+4)
    Else If	A_EventInfo = 0
        InStr("{00020400-0000-0000-C000-000000000046}{00000000-0000-0000-C000-000000000046}",COM_String4GUID(prm1)) ? NumPut(this,prm2+0) . NumPut(NumGet(this+4)+1,this+4) . (hResult:=0) : NumPut(0,prm2+0) . (hResult:=0x80004002)
    Return	hResult
}
; Based on COM_CreateIDispatch()
DispatchObj_CreateIDispatch()
{
	Static	IDispatch
	If	!VarSetCapacity(IDispatch)
	{
		VarSetCapacity(IDispatch,28,0),   nParams=3112469
		Loop,   Parse,   nParams
		NumPut(RegisterCallback("DispatchObj_IDispatch","",A_LoopField,A_Index-1),IDispatch,4*(A_Index-1))
	}
	Return &IDispatch
}
Planned Features ...for the distant future
[*:6x5lismz]A better example script, demonstrating multiple methods, parameters, parameter/return types and interaction with JavaScript.
[*:6x5lismz]Implement LowLevel to allow parameters to be passed directly to the script function as strings.
[*:6x5lismz]Provide access to the parameter list (including parameter types) and return type for the current function.
[*:6x5lismz]Provide an option to pass parameters as (type1, prm1, type2, prm2, etc.)
[*:6x5lismz]Provide an option to keep the current style, which supports variable number of parameters.
(Thanks to majkinetor for the idea of "a flag to choose between different approaches.")
[*:6x5lismz]Automatic ByRef support, if possible.[*:6x5lismz]Support for properties.
[*:6x5lismz]Get/set accessor functions.
[*:6x5lismz]Get/set an AutoHotkey variable.
[*:6x5lismz]Get/set any value, possibly from either AutoHotkey or external code.
[*:6x5lismz]Support for default property (which I think would allow array[index]-style syntax to be used.)[*:6x5lismz]Support for default method, so a DispatchObj can be used to handle an event. (This would make Handling Individual Events obsolete.)[/list]
Advanced Example
Requires ShowHTMLDialog.
obj := DispatchObj_Create("getHtml, getDefaultText, okay=onOkay")

html =
(
<head><title>Example Dialog</title></head>
<body>
    <input type=text id="textbox"><br>
    <button id="okButton" onclick="dialogArguments.okay(window)">Okay</button>
    <script for="window" event="onload">
        textbox.value = dialogArguments.getDefaultText();
        textbox.select();
    </script>
</body>
)

text := ShowHTMLDialog("javascript:dialogArguments.getHtml()"
                        , "+" obj  ; pass 'obj' as an object pointer
                        , "dialogWidth:155px;dialogHeight:50px")
if text !=
    MsgBox %text%

getHtml(this, params, result)
{
    global html
    NumPut(8, result+0) ; VT_BSTR
    return COM_SysAllocString(html)
}

getDefaultText(this, params, result)
{
    NumPut(8, result+0) ; VT_BSTR
    return COM_SysAllocString("default text!")
}

; This would be easier in JavaScript...
onOkay(this, params, result)
{
    window := DispatchObj_Param(params, 1, type)
    if type != 9 ; VT_DISPATCH
        return
    
    if textbox := COM_Invoke(window, "textbox")
    {
        returnValue := COM_Invoke(textbox, "value")
        COM_Release(textbox)
    }
    COM_Invoke(window, "returnValue=", returnValue)
    COM_Invoke(window, "close")
}
Also note that given a pointer to a window object 'ieWin', the following can be used to pass a DispatchObj to JavaScript:
; Create 'ahk' variable. (var=''; also works)
COM_Invoke(ieWin,"execScript","var ahk;","JScript")
; Assign dispatch object to ahk variable.
COM_Invoke(ieWin,"ahk=","+" pObj)

Sean deserves much credit for demonstrating how to implement a COM interface in AutoHotkey. DispatchObj_IDispatch and DispatchObj_CreateIDispatch are based on COM_DispInterface and COM_CreateIDispatch, respectively.

All code in this post is covered by Lexikos' default copyright license.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Ok, so I will have object in ahk variable in jscode.
But how do I execute it from JS code and how do I pass it params ?

window.ahk(...) ? :)
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
See the new Advanced Example. :)

DispatchObj is usable like any other object on the JavaScript side. Until I implement LowLevel, the AutoHotkey side will require DispatchObj_Param() to retrieve parameters and NumPut & COM_SysAllocString to return strings.

Writing the example has shown me just how useful/convenient it would be for DispatchObj to support properties...

COMTTS
  • Guests
  • Last active:
  • Joined: --
Thanks for the script, Lexikos.

God, I cannot even understand what's going on in the code.
Maybe I have to dig into the showHtmlDialog, and then get back here.
:lol:

And even though I didn't mention about your previous scripts,
they were really practical and useful. Thanks.

paulwarr
  • Members
  • 32 posts
  • Last active: Dec 04 2009 02:50 AM
  • Joined: 21 Sep 2006
@ lexikos:

I downloaded the advanced example above, together with ShowHTMLDialog and DispatchObj, and #include'd these in the advanced example.

On launching the advanced example, I get "Error" dialog box with the following text:

Line: 5
Error: Object doesn't support this property or method

When I dismiss the error message, I get the small Example Dialog window with a text box and an "Okay" button. But whether I type anything or not into the text box, when I click "Okay" I get another "Error" dialog box with the following text:

Line: 3
Error: Object doesn't support this property or method.

To be sure, I reinstalled the latest version of COM.ahk. Same errors. :(

Any suggestions on what to do next?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

I downloaded the advanced example above, together with ShowHTMLDialog and DispatchObj, and #include'd these in the advanced example.

Though it shouldn't matter, ShowHTMLDialog and DispatchObj are intended to be auto-included via the function library mechanism.

Line: 5
Error: Object doesn't support this property or method

I suppose it doesn't say which object or which property/method? Lines 3 and 5 have no code on them... did you change the HTML at all?

When I dismiss the error message, I get the small Example Dialog window with a text box and an "Okay" button.

Since the HTML for the dialog is retrieved via dialogArguments.getHtml(), DispatchObj must be working.

But whether I type anything or not into the text box, when I click "Okay" I get another "Error" dialog box with the following text:

Line: 3
Error: Object doesn't support this property or method.

dialogArguments (the DispatchObj) is used on lines 4 and 6. Those errors make some sense if these are lines 3 and 5 on your system, but that would mean dialogArguments is not valid after loading the HTML.

Try saving the example HTML to a file and loading that. (Replace "javascript:dialogArguments.getHtml()" with the full path of the file.)

Which version of IE are you using? Have you tried on other computers?

I just tested on IE 6.0.2900.2180 (XP SP2 on Virtual PC), and it worked, except that the window was only large enough for the caption and scrollbar. MSDN explains why:

For Windows Internet Explorer 7, dialogHeight and dialogWidth return the height and width of the content area and no longer includes the height and width of the frame.

(The example's dialogWidth/dialogHeight were based on IE7.)

paulwarr
  • Members
  • 32 posts
  • Last active: Dec 04 2009 02:50 AM
  • Joined: 21 Sep 2006
Further experiments - still no joy:

My earlier tests were run on a machine with VStudio, hence popped up the debug window with the earlier unhelpful messages. So I switched to the following platform:

XP SP2, running in VMWare Workstation v6
Unmodified from original CD installation, save for all critical security updates and McAfee VirusScan 8.5i
Internet Explorer v. 6.0.2900.2180.xpsp_sp2_gdr.0702227-2254
AutoHotKey v

To avoid any unintended side-effects, I moved ShowHTMLDialog.ahk and DispatchObj.ahk to my /Lib folder and removed the #include lines.

Per your suggestions, I modified the "Advanced Example" in two ways:
1) changed the dialogWidth and dialogHeight parameters (so that I could actually see ShowHTMLDialog window).
2) Saved the html code to a separate file.

Both modifications were in the ShowHTMLDialog function call, like so:

text := ShowHTMLDialog("file:///" A_ScriptDir "\DispatchObj_Example2.htm"
, "+" obj ; pass 'obj' as an object pointer
, "dialogWidth:255px;dialogHeight:150px")

Now, on launch of the advanced example, I get the following:

Posted Image

Posted Image

Notice that the "Default Text!" entry is missing.

If I click "yes" on the script error window, then type something into the textbox and click the "Okay" button, I get the following:

Posted Image

Posted Image

I swear, I didn't touch anything else!

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Well, it seems I forgot to update the script when I updated the example. :oops: There was one important change:
Loop, Parse, MethodList, `,[color=red], %A_Space%%A_Tab%[/color]
The method list is:
obj := DispatchObj_Create("getHtml, getDefaultText, okay=onOkay")
Notice the spaces... :x

paulwarr
  • Members
  • 32 posts
  • Last active: Dec 04 2009 02:50 AM
  • Joined: 21 Sep 2006
Works! Thanks!

I note that DispatchObj's example requires ShowHTMLDialog, which passes the object as a parameter (to showhtmldialogex?). But I also note that there is no corresponding parameter-pass option in the Webbrowser or IE "Navigate" or "Navigate2" methods. So do you have any suggestions on how to create and pass a DispatchObj invoke-able object to a standard IE or Webbrowser control?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

So do you have any suggestions on how to create and pass a DispatchObj invoke-able object to a standard IE or Webbrowser control?

Also note that given a pointer to a window object 'ieWin', the following can be used to pass a DispatchObj to JavaScript:

; Create 'ahk' variable. (var=''; also works)
COM_Invoke(ieWin,"execScript","var ahk;","JScript")
; Assign dispatch object to ahk variable.
COM_Invoke(ieWin,"ahk=","+" pObj)

To get a pointer to a window object ("ieWin"):
ControlGet, hIESvr, Hwnd,, Internet Explorer_Server1, Google ; (WinTitle)

ACC_Init() ; Call this once only, or call ACC_Term() for each ACC_Init().
           ; Note ACC_Init also calls COM_Init(), so you don't need both.
ieAcc := ACC_AccessibleObjectFromWindow(hIESvr)
IID_IHTMLWindow2 := "{332C4427-26CB-11D0-B483-00C04FD90119}"
ieWin := COM_QueryService(ieAcc,IID_IHTMLWindow2,IID_IHTMLWindow2)
COM_Release(ieAcc)
Requires Accessibility Standard Library.

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
Wow, I just found this.
Thanks, Lexikos.

lexikos*
  • Guests
  • Last active:
  • Joined: --
I haven't used this script since writing it. If you find it useful, perhaps you'd like to try implementing some of the "Planned Features". LowLevel would not be necessary now that we have the built-in capability to dynamically call functions. Some of the other items overlap with capabilities now present in AutoHotkey_L/AutoHotkeyU objects, so writing an IDispatch wrapper for those could be useful.

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007
I am able to replace javascript functions in a webpage loaded in IE through execscript. I am even able to put a dispatch obj into a variable on the webpage, and invoke it from ahk. Unfortunately, I can't figure out how to invoke an ahk function from the javascript / webpage side. (I know events can be connected, but can you connect a non event object / javascript function to an ahk function ? )
@Lexikos: could you provide an example using IE instead of showHTMLdialog ?

Edit:
Thanks Lexikos. I figured it out, see post below.
The following now works:
my ahk script:
onexit, cleanup
iewin := getiewin("ahk Plug-In")
obj := DispatchObj_Create("ahkMessage")
cobj := ComObjEnwrap(obj)
cobj.ahkMessage("cobj.ahkMessage works") ; works
ieWin.execScript("var ahk;","JScript")
ieWin.ahk := cobj
ieWin.ahk.ahkMessage("ieWin.ahk.ahkMessage now works") 

script2 = 
(
function testMessage()
{
alert("test message!" + window.status);   // works
ahk.ahkMessage("from javascript ahk.ahkMessage");   //works too :)
}
)
ieWin.execScript(script2,"JScript")

return
;; testMessage(this, params, result)   ; all parameters are optional
testMessage(this, params, result)   ; all parameters are optional
{
    ; Get the value and type of the first parameter.
    value := DispatchObj_Param(params, 1, type)
    if type = 9 ; VT_DISPATCH (an object)
        COM_Invoke(value, "someMethod")

    ; Return an integer (zero):
    ;   optional since VT_I4 (int) is the default:
    NumPut(3, result+0,0,"ushort") ; result.vt=VT_I4;
    ;   method 1
    NumPut(123, result+8,0,"int") ; result.lVal=123;
    ;   method 2, overrides method 1 if non-zero. only works with int.
  msgbox testmessage %value%
    return 123
}
;; ahkMessage(this, params, result)   ; all parameters are optional
ahkMessage(this, params, result)   ; all parameters are optional
{
    ; Get the value and type of the first parameter.
    value := DispatchObj_Param(params, 1, type)
    if type = 9 ; VT_DISPATCH (an object)
        COM_Invoke(value, "someMethod")

    ; Return an integer (zero):
    ;   optional since VT_I4 (int) is the default:
    NumPut(3, result+0,0,"ushort") ; result.vt=VT_I4;
    ;   method 1
    NumPut(123, result+8,0,"int") ; result.lVal=123;
    ;   method 2, overrides method 1 if non-zero. only works with int.
  msgbox ahkMessage %value%
    return 123
}
;; cleanup:
cleanup:
ExitApp

;; includes
#include com.ahk
#include dispatchobj.ahk
#include acc.ahk
#include expando.ahk
;; ui
!r::reload
!q::exitapp

;; getiewin(title)
getiewin(title)
{
ControlGet, hIESvr, Hwnd,, Internet Explorer_Server1, %title%
ACC_Init() ; Call this once only, or call ACC_Term() for each ACC_Init().
           ; Note ACC_Init also calls COM_Init(), so you don't need both.
ieAcc := ACC_AccessibleObjectFromWindow(hIESvr)
IID_IHTMLWindow2 := "{332C4427-26CB-11D0-B483-00C04FD90119}"
ieWin := COM_QueryService2(ieAcc,IID_IHTMLWindow2,IID_IHTMLWindow2)
COM_Release(ieAcc)
return ieWin
}

COM_QueryService2(ppv, SID, IID = "")
{
	If	DllCall(NumGet(NumGet(1*ppv:=COM_Unwrap(ppv))), "Uint", ppv, "Uint", COM_GUID4String(IID_IServiceProvider,"{6D5140C1-7436-11CE-8034-00AA006009FA}"), "UintP", psp)=0
	&&	DllCall(NumGet(NumGet(1*psp)+12), "Uint", psp, "Uint", COM_GUID4String(SID,SID), "Uint", IID ? COM_GUID4String(IID,IID):&SID, "UintP", ppv:=0)+DllCall(NumGet(NumGet(1*psp)+8), "Uint", psp)*0=0
	Return	ComObjEnwrap(ppv)
}
my webpage:
<!-- saved from url=(0013)about:internet -->
<HTML>
<HEAD>
<TITLE>ahk Plug-In</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<script type="text/javascript">
function testMessage()
{
alert("old message!");
}
</script>
</head>

<body>
<form>
<input type="button" value="testMessage" onclick="testMessage()" /> before launching ahk script, says "old Message", after says, "test mesaage" and calls an ahk message
</form><br>
<form>
<input type="button" name="ahkbutton" value="testahk" onclick="ahk.ahkMessage()" /> call ahk from javascript (after launching ahk script)
</form>

</body>
</HTML>


Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

obj := DispatchObj_Create("ahkMessage")
...
ieWin.ahk := obj

DispatchObj_Create() returns an integer/pointer, not an object. Use ComObjEnwrap().

tinku99
  • Members
  • 560 posts
  • Last active: Feb 08 2015 12:54 AM
  • Joined: 03 Aug 2007

obj := DispatchObj_Create("ahkMessage")
...
ieWin.ahk := obj

DispatchObj_Create() returns an integer/pointer, not an object. Use ComObjEnwrap().

Ok, I got it work. The problem was with iewin. COM_QueryService() uses COM_Enwrap instead of ComObjEnwrap. so
iewin.ahk := ComObjEnwrap(DispatchObj_Create("ahkMessage")) ; was failing
I used a modified COM_QueryService2()
COM_QueryService2(ppv, SID, IID = "")
{
	If	DllCall(NumGet(NumGet(1*ppv:=COM_Unwrap(ppv))), "Uint", ppv, "Uint", COM_GUID4String(IID_IServiceProvider,"{6D5140C1-7436-11CE-8034-00AA006009FA}"), "UintP", psp)=0
	&&	DllCall(NumGet(NumGet(1*psp)+12), "Uint", psp, "Uint", COM_GUID4String(SID,SID), "Uint", IID ? COM_GUID4String(IID,IID):&SID, "UintP", ppv:=0)+DllCall(NumGet(NumGet(1*psp)+8), "Uint", psp)*0=0
	Return	ComObjEnwrap(ppv)  ; was COM_Enwrap(ppv)
}
Now, I am able to call ahk function from javascript. I will update my post above with working code.

By the way, what's the difference between ComObjEnwrap() and COM_Enwrap() ? Why does com_L.ahk use the later in COM_queryservice() ?