Support for passing objects to COM APIs

Propose new features and changes
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Support for passing objects to COM APIs

13 Sep 2014, 05:32

I recently had cause to look into passing AutoHotkey objects to/from COM-based APIs, and found the current solutions to be a bit lacking.

ComDispatch by fincs (based on my DispatchObj) and modified by Coco:
  1. Requires defining which methods can be called, and it only supports methods, not properties.
  2. Objects need to be explicitly wrapped before passing them to a COM API.
  3. Objects need to be explicitly unwrapped when they are returned from a COM API.
So I modified it a little and produced ComDispatch0, which solves #1 and partially #2 - unwrapping objects which are passed as parameters from a COM client to the ComDispatch0 wrapper - but there are many other situations it doesn't cover, and mostly can't.

So I experimented with adding native support for passing AutoHotkey objects to/from COM APIs. The cost is about 1-2KB of added size to AutoHotkey.exe (32-bit), with the following features:
  • No wrapper objects; AutoHotkey objects implement IDispatch natively, though it is never used by AutoHotkey itself.
  • Supports calling methods and getting/setting properties.
  • Supports calling functions (by reference).
  • All types of AutoHotkey objects can be passed to COM APIs.
  • COM objects must still be wrapped, since they don't support the interface used internally by AutoHotkey.
  • AutoHotkey objects passed back from COM APIs are recognized automatically and not wrapped in a ComObject.
  • Enumeration is not supported.
  • ByRef is not supported (except via a ComObject wrapper).
Now I'm looking for convincing arguments that I should complete the feature and add it to the main branch.
User avatar
fincs
Posts: 504
Joined: 30 Sep 2013, 14:17
GitHub: fincs
Location: Seville, Spain
Contact:

Re: Support for passing objects to COM APIs

13 Sep 2014, 06:04

I have several questions:
  • Does ObjectBase implement IDispatch or just Object?
  • Are accesses to integer keys handled in a separate way (e.g. using DISPID_VALUE)?
  • Is the reason for not supporting enumerations that you didn't implement it yet (or would be planned for a future release)?
fincs
Windows 10 x64 Build 18362 | AMD Ryzen 7 3700X with 32 GB of RAM | AutoHotkey v1.1.31.01
Get SciTE4AutoHotkey v3.0.06.01 - [My project list]
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: Support for passing objects to COM APIs

13 Sep 2014, 06:11

Just curious, aside from Active Scripting languages (JScript, VBScript), what other COM objects/API is this(passing objects) applicable to? I definitely prefer native support - it'll make AutoHotkey more powerful.
Some cool stuff that I can think of:
  • Implement(easier) COM interface in your script (just like S4AHK)
  • Better/easier webpage control / automation (IE) - perhaps, I'm not sure about this, I haven't experimented with ComDispatch() and IE yet...
  • Easier to write richer HTML-based application(s) - think AutoHotkey's version of node-webkit
  • Others - I'm sure there are more uses
Well, if the only disadvantage is the code size, I don't think it's that much of a disadvantage at all.

Is it implemented like ComDispatch/ComDistpatch0?
Last edited by Coco on 13 Sep 2014, 06:27, edited 2 times in total.
User avatar
joedf
Posts: 7356
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: Support for passing objects to COM APIs

13 Sep 2014, 06:21

Are their any other disadvantages?
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500 @ 4.00 GHz, 2x8GB G.Skill RipJaws V - DDR4 3280 MHz, NVIDIA GTX 1060 6GB | [About Me] | [ASPDM - StdLib Distribution]
[Populate the AHK MiniCity!] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library] | [About the AHK Foundation]
Bruttosozialprodukt
Posts: 457
Joined: 24 Jan 2014, 22:28

Re: Support for passing objects to COM APIs

13 Sep 2014, 07:04

Well, I'd certainly like to see this implemented.
I don't really have good arguments though. All I can say is, that I'm currently implementing js based plug-in support into some of my scripts which I think is a pretty damn cool thing. But the fact that 3 libraries are required is kind of disappointing:

Code: Select all

#Include Libs\ComDispatch.ahk ; https://github.com/fincs/SciTE4AutoHotkey/blob/master/source/toolbar/Lib/ComDispatch.ahk
#Include Libs\ComDispTable.ahk ; https://github.com/fincs/SciTE4AutoHotkey/blob/master/source/toolbar/Lib/ComDispTable.ahk
#Include Libs\ComVar.ahk ; https://github.com/fincs/SciTE4AutoHotkey/blob/master/source/toolbar/Lib/ComVar.ahk

JScriptObj := ComObjCreate("ScriptControl")
JScriptObj.Language := "JScript"

If (PluginExist("test")) { ;if \Plugins\test.js does exist
    LoadPlugin("test",{"Plugin_MsgBox":"MsgBox","Plugin_Sleep":"Sleep","Plugin_Run":"Run"})
    JScriptObj.Run("main")
} Else
    MsgBox, \Plugins\test.js does not exist

PluginExist(name) {
    If FileExist(A_WorkingDir "\Plugins\" name ".js")
        Return True
    Return False
}
LoadPlugin(name,importFunctions) {
    Global JScriptObj
    JScriptObj.Reset()
    If (ErrorLevel)
        MsgBox, Failed to reset the JScript engine!
    FileRead, pluginCode, % A_WorkingDir "\Plugins\" name ".js"
    If (ErrorLevel)
        MsgBox, Failed to access the plug-in.
    For fName, jsfName in importFunctions {
        params := ""
        Loop % Func(fName).MaxParams-1
            params .= chr(96+A_Index) ","
        If (params)
            params := SubStr(params,1,StrLen(params)-1)
        pluginTemplate .= "function " jsfName "(" params ") {__ImportedFunctions." jsfName "(" params ");}`n"
        fList .= jsfName "=" fName ", "
    }
    fList := SubStr(fList,1,StrLen(fList)-2)
    MsgBox % pluginTemplate pluginCode
    JScriptObj.AddObject("__ImportedFunctions", ComDispatch("", fList))
    If (ErrorLevel)
        MsgBox, Failed to register the functions to the JScript engine!
    JScriptObj.AddCode(pluginTemplate pluginCode)
    If (ErrorLevel)
        MsgBox, Failed to load the plug-in into the engine!
    Return True
}
Plugin_MsgBox(this, msg:="") {
	MsgBox % msg
}
Plugin_Sleep(this, time) {
	Sleep % time
}
Plugin_Run(this, fileFullPath, parameters) {
	RunWait, % fileFullPath " " parameters
}

Code: Select all

MsgBox("Script loaded!");

function main() {
    MsgBox("You called the main function of the script!");
}
But I'm wondering if the added internal support for this would impact the runtime performance of scripts that don't make use of it.
Last edited by Bruttosozialprodukt on 13 Sep 2014, 07:23, edited 1 time in total.
User avatar
joedf
Posts: 7356
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: Support for passing objects to COM APIs

13 Sep 2014, 07:08

Cool, nice share!
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500 @ 4.00 GHz, 2x8GB G.Skill RipJaws V - DDR4 3280 MHz, NVIDIA GTX 1060 6GB | [About Me] | [ASPDM - StdLib Distribution]
[Populate the AHK MiniCity!] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library] | [About the AHK Foundation]
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Support for passing objects to COM APIs

13 Sep 2014, 08:36

Coco wrote:[1] ... what other COM objects/API is this(passing objects) applicable to?
[2] Better/easier webpage control / automation (IE)
[3] Easier to write richer HTML-based application(s)
[4] Is it implemented like ComDispatch/ComDistpatch0?
[1] Anything that takes an object implementing the IDispatch interface. C# with the dynamic type via CLR.ahk, for instance.
[2] I don't think there's much call for passing an AutoHotkey object to a webpage.
[3] Like AutoHotkey Setup, which currently "calls functions" by navigating to invalid URLs.
[4] All three work by implementing the IDispatch interface, by necessity. What are you really asking that I haven't answered in my first post?
joedf wrote:Are their any other disadvantages?
None that I'm aware of.
brutosozialprodukt wrote:But the fact that 3 libraries are required is kind of disappointing:
You can reduce that to just 1, ComDispatch0 - as mentioned in my first post.

Since you're using ScriptControl, I'll add that you may need to switch to ActiveScript.ahk if you want to support AutoHotkey 64-bit.

Side note: I was tentatively planning to add VT_BYREF support either way, in which case ComVar() would be fairly obsolete. comvar := ComVar() would be mostly equivalent to:

Code: Select all

VarSetCapacity(buf, 24), comvar := ComObject(0x400C, &buf)
The cost of this should be negligible, since it would use mostly the same code as SAFEARRAY support.
But I'm wondering if the added internal support for this would impact the runtime performance of scripts that don't make use of it.
I'll need to benchmark to answer that for sure. I expect that it won't impact performance in any meaningful way.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: Support for passing objects to COM APIs

14 Sep 2014, 00:14

Will you be providing test binaries or include this in the next release?
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Support for passing objects to COM APIs

14 Sep 2014, 07:27

fincs wrote:I have several questions:
  • Does ObjectBase implement IDispatch or just Object?
  • Are accesses to integer keys handled in a separate way (e.g. using DISPID_VALUE)?
  • Is the reason for not supporting enumerations that you didn't implement it yet (or would be planned for a future release)?
I overlooked this post. Sorry.
  • I believe I said "All types of AutoHotkey objects can be passed to COM APIs." Restricting it to Object would have meant keeping IObject and IDispatch separate, which means that every object needs two v-table pointers. Changing IObject to derive from IDispatch and implementing it in a common base class produced a smaller executable and a better result. Note: Func does not extend from either Object or ObjectBase, yet I considered it a requirement to be able to pass them to COM APIs.
  • We aren't calling IDispatch, we're implementing it. If someone invokes with DISPID_VALUE, the parameters are passed along to IObject::Invoke. But JScript and VBScript don't do that - they use numeric names. .NET's dynamic type uses DISPID_VALUE, but it also sets both DISPATCH_METHOD and DISPATCH_GET, so x[1] is indistinguishable from x(1). At the moment I interpret it as CALL if there are parameters and GET otherwise (since x.y in VBScript produces the same combination of flags but with no parameters).
  • I haven't implemented it and I have no plan to implement it.
Coco: probably, probably not.
User avatar
fincs
Posts: 504
Joined: 30 Sep 2013, 14:17
GitHub: fincs
Location: Seville, Spain
Contact:

Re: Support for passing objects to COM APIs

16 Sep 2014, 10:01

Some further thoughts about this:
  • A very nice consequence of this hypothetical new functionality is that it would be possible to publish AutoHotkey objects through COM and do (mostly) seamless inter-process communication between AutoHotkey scripts.
  • TBH I'm not too happy with converting IObject into a COM dispatch interface. An implementation using wrapper objects around AutoHotkey objects would have been cleaner IMO; no need to pollute the core AutoHotkey interpreter with COM concepts (remark: QueryInterface is not necessary to unwrap these objects since we can read and compare the VTable pointer). This will bite us back if somebody in the (hopefully not so but admittedly) far future wants to create a multiplatform version of AutoHotkey - even if this person rewrites everything, it would be necessary to apply ugly hacks to the Windows port in order to make objects inherit from IDispatch in that platform only. I don't know what are the code size implications for this alternate approach though.
  • The only downside to above is that it would not possible to directly pass AHK objects to raw COM methods (using DllCall). However some trickery could be used or added to force AHK to build a wrapper object (such as allowing ComObjValue(ahkObject), which would be consistent with real COM objects).
fincs
Windows 10 x64 Build 18362 | AMD Ryzen 7 3700X with 32 GB of RAM | AutoHotkey v1.1.31.01
Get SciTE4AutoHotkey v3.0.06.01 - [My project list]
User avatar
nnnik
Posts: 4328
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Support for passing objects to COM APIs

16 Sep 2014, 11:25

A very nice consequence of this hypothetical new functionality is that it would be possible to publish AutoHotkey objects through COM and do (mostly) seamless inter-process communication between AutoHotkey scripts.
:clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :clap: :superhappy: :superhappy: :superhappy: :superhappy: :superhappy:
Recommends AHK Studio
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Support for passing objects to COM APIs

16 Sep 2014, 17:04

fincs wrote:An implementation using wrapper objects around AutoHotkey objects would have been cleaner IMO;
I find that statement frankly quite ridiculous.
no need to pollute the core AutoHotkey interpreter with COM concepts
IDispatch is not used at all except with COM. The difference is:

Code: Select all

class IObject : public IDispatch
...
struct DECLSPEC_NOVTABLE IObjectComCompatible : public IObject
instead of

Code: Select all

class IObject
...
struct DECLSPEC_NOVTABLE IObjectComCompatible : public IObject, public IDispatch  // (Contains implementation)
... smaller code, and no need for dynamic type checks because everything implements IDispatch. Also AddRef/Release is not redefined in IObject.
even if this person rewrites everything, it would be necessary to apply ugly hacks to the Windows port in order to make objects inherit from IDispatch in that platform only.
It would be an extremely trivial "ugly hack" among thousands of other such hacks, if they were to make the same code base support both OSes. They would likely face a lot more difficulty and ugly hacks than this one, like for instance, most of the unique/useful functionality is entirely dependent on Windows APIs. Wrapper objects would be far "uglier" than a few lines in an #ifdef.
The only downside
It's a big one, and you're forgetting memory usage, performance and possibly problems with equality checks like wrapperObjectOne == wrapperObjectTwo.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: Support for passing objects to COM APIs

18 Sep 2014, 08:04

lexikos wrote:It's a big one, and you're forgetting memory usage, performance ...
How big are we talking about? 2x, 3x memory usage, performance? Is it an inevitable compromise?
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Support for passing objects to COM APIs

18 Sep 2014, 21:47

Please read what I said in the context of the part of fincs' post I was replying to. I was referring to the downsides of fincs' suggested approach (wrappers), not the approach that I've actually taken. In my opinion, there is no good reason to take the wrapper approach, so no need to quantify the cost to memory usage or performance (but at a guess, I'd say it would be negligible in real-world scripts).
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: Support for passing objects to COM APIs

19 Sep 2014, 10:38

My bad, I shouldn't have quoted you, hence, the confusion. Yep, my question is about your approach for which you have already answered. ;)
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Support for passing objects to COM APIs

19 Sep 2014, 18:39

Test Build

Notes about the test build:
  • Objects can be passed directly to COM methods (called via ComObject wrappers).
  • Objects can be passed to COM methods via DllCall. You should be able to pass &object as an IDispatch/IUnknown, but to be extra safe, store the object in an array or VARIANT and then NumGet the pointer out. This ensures that it points at the correct interface (IDispatch), and native COM support is present.
  • Each name requested by a COM client is mapped to an ID internally. COM rules require that the name map to the same ID for the lifetime of the object. For simplicity, we guarantee it for the lifetime of the program and for all objects. DISPID_VALUE is also supported; for GET|PUT, it simply passes all parameters to the object; for METHOD, it calls an empty-named method (compatible with %x%()).
  • Information for a thrown exception is not returned to the COM client; at the moment, we return E_FAIL for simplicity.
  • If a client passes VT_BYREF|VT_VARIANT, it is dereferenced. This is consistent with ComObjConnect, but mightn't be ideal.
  • Property/method names which are integer-strings are converted to pure integers, consistent with v1 objects. This is for JScript[1].
  • Only getting/setting/calling is supported. So no JScript for-loop, no JScript new operator, etc.
  • Ambiguity problems described in detail below.
Bonus features:
  • VT_BYREF|VT_VARIANT wrappers are supported. ComObject(0x400C, pvar)[] := 42 interprets pvar as a pointer to a VARIANT and stores the value 42 in it.
  • Stdin/stdout is supported as *, for all relevant commands/functions except FileRead, I think. Specify * as the name of the script to load a script from stdin. You can type multiple lines at a command prompt by running type CONIN$ | AutoHotkey *. See Add /RunStdIn by fincs for some related details.
Ambiguity
Some COM clients use a combination of flags, like DISPATCH_METHOD|DISPATCH_GET. I documented some specific issues in the source code, also copied below. This ambiguity is impossible to resolve properly, so just be aware that the behaviour in each language might not be exactly what you expect. At the moment, if DISPATCH_METHOD is present and the name corresponds to a Func in the object or its base, a method is called. Otherwise the value is returned (I just realized this isn't appropriate when COM clients pass multiple parameters; but note that the property feature added in v1.1.16.01 should work).

These rules likely need some tweaking to get the best (or least unfavourable) behaviour.

Code: Select all

	// PUT is probably never combined with GET/METHOD, so is handled first.  Some common problem
	// cases which combine GET and METHOD include:
	//  - Foo.Bar in VBScript, so we use GET if there are no parameters.
	//  - foo.bar() and foo.bar[] in C#.  There's no way to differentiate, so we just use METHOD
	//    when there are parameters to cover the most common cases.  Both will work with user-
	//    defined properties (v1.1.16+) since they can be "called", but won't work with __Get.
	//  - foo[bar] and foo(bar) in C# both use METHOD|GET with DISPID_VALUE.
	//  - foo(bar) (but not foo[bar]) in JScript uses METHOD|GET with DISPID_VALUE.
N.B. The notes about what "we" do are obsolete. Ignore them.


Download
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Support for passing objects to COM APIs

19 Sep 2014, 18:49

Simple example:

Code: Select all

sc := ComObjCreate("ScriptControl"), sc.Language := "JScript"
sc.AddObject("x", ["foo", "bar"])
MsgBox % sc.Eval("x[1]")
C# example (requires CLR.ahk):

Code: Select all

; 'dynamic' requires Visual C# 2010 / .NET Framework 4
code =
(
    public class Foo {
        public void Bar(dynamic x) {
            x(42);
        }
    }
)
asm := CLR_CompileC#(code, "System.Core.dll | System.Windows.Forms.dll | Microsoft.CSharp.dll")
foo := CLR_CreateObject(asm, "Foo")
foo.Bar(Func("callback"))

callback(a := "") {
    MsgBox Callback was called with parameter '%a%'.
}
GeekDude
Posts: 853
Joined: 02 Oct 2013, 22:13

Re: Support for passing objects to COM APIs

19 Sep 2014, 19:13

If AHK's objects will support IDispatch, does that mean I could pass the object as a command line parameter to a second script I launch and use that object to communicate with the host script through COM? Eg, from the second script I call Object.MessageBoxMethod() and in the host script have an object with that method that does MsgBox, % this.Text?
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
GitHub: cocobelgica

Re: Support for passing objects to COM APIs

19 Sep 2014, 23:13

Thanks lexikos.

Btw, I can't seem to get ComVar to work with Shell.Explorer (it works if I use VBScript, just like in the docs):

Code: Select all

html =
(
<!DOCTYPE html>
<html><head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<script type="text/javascript">
function test(v) {
	document.getElementById("test").innerText = v;
	v = "Goodbye World!";
}
</script></head>
<body>
<h1 id="test">ComVar Test</h1>
</body></html>
)

Gui New
Gui Margin, 0, 0
Gui Add, ActiveX, w400 h200 vwb, Shell.Explorer
wb.Navigate("about:blank")
while (wb.ReadyState != 4)
    Sleep 10
wb.Document.open()
wb.Document.write(html)
wb.Document.close()
Gui Show

Sleep 2000
;// ComVar test
VarSetCapacity(v, 24)
comvar := ComObject(0x400C, &v)
comvar[] := "Hello World"
wb.Document.parentWindow.test(comvar)
MsgBox % comvar[]
return
GuiClose:
ExitApp
Re: AutoHotkey *, an alternative to type CONIN$ is to use PowerShell(just type it in the command line), not included in XP though.

Code: Select all

C:\>powershell
Windows PowerShell
Copyright (C) 2012 Microsoft Corporation. All rights reserved.

PS C:\>@"
>> for k, v in ["Auto", "Hot", "Key"]
>>     MsgBox %k% = %v%
>> "@ | AutoHotkey.exe *
lexikos
Posts: 6668
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Support for passing objects to COM APIs

21 Sep 2014, 02:52

GeekDude wrote:If AHK's objects will support IDispatch, does that mean I could pass the object as a command line parameter to a second script I launch and use that object to communicate with the host script through COM?
No. The object only exists within the address space of the process which created it. IDispatch is no different to the existing interface in this respect. The difference is that an object which implements IDispatch can be registered with OLE. fincs has already done this with SciTE; the difference would only be that you would not need to manually implement the IDispatch interface.
Coco wrote:Btw, I can't seem to get ComVar to work with Shell.Explorer (it works if I use VBScript, just like in the docs):
I believe you can use VBScript in a WebBrowser control. Parameters are ByRef by default in VBScript, but there's no such thing as a ByRef parameter in JScript. You can return an array from JScript, or pass an AutoHotkey object as a parameter and have JScript store a value in it.

Return to “Wish List”

Who is online

Users browsing this forum: No registered users and 10 guests