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.
Code:
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:
Code:
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.
Code:
; 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
- A better example script, demonstrating multiple methods, parameters, parameter/return types and interaction with JavaScript.
- Implement LowLevel to allow parameters to be passed directly to the script function as strings.
- Provide access to the parameter list (including parameter types) and return type for the current function.
- Provide an option to pass parameters as (type1, prm1, type2, prm2, etc.)
- 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.") - Automatic ByRef support, if possible.
- Support for properties.
- Get/set accessor functions.
- Get/set an AutoHotkey variable.
- Get/set any value, possibly from either AutoHotkey or external code.
- Support for default property (which I think would allow array[index]-style syntax to be used.)
- Support for default method, so a DispatchObj can be used to handle an event. (This would make Handling Individual Events obsolete.)
Advanced ExampleRequires
ShowHTMLDialog.
Code:
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:
Code:
; 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.