Page 1 of 3

ActiveScript - Host VBScript and JScript in-process

Posted: 13 Sep 2014, 04:09
by lexikos
ActiveScript for AutoHotkey v1.1

Provides an interface to Active Scripting languages like VBScript and JScript, without relying on Microsoft's ScriptControl, which is not available to 64-bit programs.

License: Use, modify and redistribute without limitation, but at your own risk.

More information, examples and downloads


ComObject := ComDispatch0(Object) - not needed in AutoHotkey v1.1.17+

Wraps an AutoHotkey object in an IDispatch interface, allowing COM clients such as the VBScript or JScript engines to call methods and set and get values. See Example_Dispatch0.ahk for examples. ComDispatch0 is based on fincs' ComDispatch, modified by Coco.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 13 Sep 2014, 05:16
by Coco
Very cool, thanks lexikos. How much tweaking does it need to make it work for v2? I noticed a couple of parsing loops and a .Remove() somewhere. Other than that most parts are v2 compatible... unless I missed something.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 13 Sep 2014, 05:36
by lexikos
I would have to review the code closely and test to answer that. I didn't bother since there are some things it uses that I'm planning on changing soon, like meta-functions.


If you're interested in this topic, you should read Support for passing objects to COM APIs - Wish List.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 13 Sep 2014, 05:52
by fincs
This is very nice & much more elegant than my previous ScriptControl marshaling workaround. I'm also glad to see that my old ComDispatch lib is deemed useful by others and is updated/enhanced.
lexikos wrote:there are some things it uses that I'm planning on changing soon, like meta-functions.
Huh? I thought the new property syntax was introduced in order to minimize the need to use meta-functions and such avoid having to change how they work?

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 13 Sep 2014, 08:30
by joedf
Very Nice! :D Just one thing tho... Is it just me or is this file not supposed to be on github? ??
https://github.com/Lexikos/ActiveScript ... properties

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 13 Sep 2014, 08:53
by lexikos
fincs: The idea was to minimize the cost in terms of any inconvenience that might be caused by making the meta-functions less quirky. It's still true that (as I said) "the rules for how meta-functions work are complicated and error-prone," and that needs to be changed even if meta-functions aren't needed as much. By eliminating properties from the things meta-functions are commonly used for, there's less need to have them tied into the inheritance chain as they are now.

joedf: It's just you.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 13 Sep 2014, 09:54
by joedf
:0 ok

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 11 Oct 2014, 07:45
by Coco
Edit: Ok, I got it, the same interface pointer is used for subsequent instance(s). So, say for SetWBClientSite(), can I recycle the same IOleClientSite, IServiceProvider and IInternetSecurityManager interface pointers(previously used) for subsequent WebBrowser control(s).

@lexikos: I noticed that when you called .vftable() you used the class object itself (ActiveScriptSite), hence, the buffer is stored in the class object's member(s). Is this OK, since it gets overridden every time a new instance of ActiveScriptSite is created? Or it's OK since IActiveScript::SetScriptSite is only needed one-time? I'm currently writing a mod for your SetWBClientSite() routine and would like to base it on your ActiveScriptSite class, e.g.: the way you stored a weak reference to the instance of ActiveScript etc..

PS: It would be nice if you(or anyone knowledgeable) can do a tutorial on how to implement COM interfaces. I did learn/understand a few things, (while updating ComDispatch, etc.) ;)

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 11 Oct 2014, 18:56
by lexikos
Coco wrote:Edit: Ok, I got it, the same interface pointer is used for subsequent instance(s).
No.

It's not an interface pointer, but a virtual function table. Normally in compiled languages, there is one table per class (per super-class/interface if it inherits multiple), and this is constructed at compile time, since the functions don't change at run-time. An interface pointer is a pointer to a pointer to the virtual function table. This is why you can compare NumGet(&obj) between objects to identify whether they're the same type of object. The interface pointer either points at the beginning of the object, or math can be used to locate the beginning of the object (the compiler handles this). For example, the second field of my ActiveScriptSite is a pointer to the IActiveScriptSiteWindow vtable, hence this line:

Code: Select all

this -= A_PtrSize     ; Cast to IActiveScriptSite
NumGet(&this) returns the address of the vftable.
Is this OK, since it gets overridden every time a new instance of ActiveScriptSite is created?
It doesn't get overridden. Each vftable (_vft and _vft_w) is constructed only once; the first two lines of _vftable() ensure this.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 12 Oct 2014, 02:31
by Coco
lexikos wrote:An interface pointer is a pointer to a pointer to the virtual function table.
I see that's why: func_addr := NumGet(NumGet(interface_ptr + 0), zero_based_index * A_PtrSize)

Edit: Ok, after carefully reviewing it, I think I got it. So given the code below, is it OK to implement AddRef and Release? You do have a comment about avoiding circular references, does it apply in this case?:

Code: Select all

class WBClientSite
{
	__New(self)
	{
		ObjSetCapacity(this, "__Site", 3*A_PtrSize)
		NumPut(&self
		, NumPut(this.base._vftable("_vft_IInternetSecurityManager", "11348733")
		, NumPut(this.base._vftable("_vft_IServiceProvider", "3")
		, NumPut(this.base._vftable("_vft_IOleClientSite", "031010")
		    , this.__Ptr := ObjGetAddress(this, "__Site") ) ) ) )
	}

	_vftable(name, args)
	{
		if ( ptr := ObjGetAddress(this, name) )
			return ptr
		
		static IUnknown
		if !IUnknown
		{
			IUnknown := {
			(Join, Q C
				"QueryInterface": RegisterCallback(this._QueryInterface, "Fast")
				"AddRef":         RegisterCallback(this._AddRef, "Fast")
				"Release":        RegisterCallback(this._Release, "Fast")
			)}
		}

		sizeof_VFTABLE := (3 + StrLen(args)) * A_PtrSize
		ObjSetCapacity(this, name, sizeof_VFTABLE)
		ptr := ObjGetAddress(this, name)

		NumPut(IUnknown.QueryInterface, ptr + 0*A_PtrSize)
		NumPut(IUnknown.AddRef,         ptr + 1*A_PtrSize)
		NumPut(IUnknown.Release,        ptr + 2*A_PtrSize)

		for i, argc in StrSplit(args)
		{
			vfunc := RegisterCallback(this[SubStr(name, 6)], "Fast", argc+1, i)
			NumPut(vfunc, ptr + (3+i-1)*A_PtrSize)
		}

		return ptr
	}

	_QueryInterface(riid, ppvObject)
	{
		static IID_IUnknown := "{00000000-0000-0000-C000-000000000046}"
		     , IID_IOleClientSite := "{00000118-0000-0000-C000-000000000046}"
		     , IID_IServiceProvider := "{6d5140c1-7436-11ce-8034-00aa006009fa}"
		
		iid := WBClientSite._GUID2String(riid)
		if (iid = IID_IOleClientSite || iid = IID_IUnknown)
		{
			NumPut(this, ppvObject+0) ;// IOleClientSite
			return 0 ;// S_OK
		}
		if (iid = IID_IServiceProvider)
		{
			NumPut(this + A_PtrSize, ppvObject+0) ;// IServiceProvider
			return 0 ;// S_OK
		}
		NumPut(0, ppvObject+0)
		return 0x80004002 ;// E_NOINTERFACE
	}

	_AddRef()
	{
		return 1
	}

	_Release()
	{
		return 1
	}

	IOleClientSite(p1:="", p2:="", p3:="")
	{
		if (A_EventInfo == 3) ;// GetContainer
		{
			NumPut(0, p1+0) ;// *ppContainer := NULL
			return 0x80004002 ;// E_NOINTERFACE
		}
		return 0x80004001 ;// E_NOTIMPL
	}

	IServiceProvider(guidService, riid, ppv) ;// QueryService
	{
		static IID_IUnknown := "{00000000-0000-0000-C000-000000000046}"
		     , IID_IInternetSecurityManager := "{79eac9ee-baf9-11ce-8c82-00aa004ba90b}"
		
		if (WBClientSite._GUID2String(guidService) = IID_IInternetSecurityManager)
		{
			iid := WBClientSite._GUID2String(riid)
			if (iid = IID_IInternetSecurityManager || iid = IID_IUnknown)
			{
				NumPut(this + A_PtrSize, ppv+0) ;// IInternetSecurityManager
				return 0 ;// S_OK
			}
			NumPut(0, ppv+0)
			return 0x80004002 ;// E_NOINTERFACE
		}
		NumPut(0, ppv+0)
		return 0x80004001 ;// E_NOTIMPL
	}

	IInternetSecurityManager(p1:="", p2:="", p3:="", p4:="", p5:="", p6:="", p7:="", p8:="")
	{
		if (A_EventInfo == 5) ;// ProcessUrlAction
		{
		 	if (p2 == 0x1400) ;// dwAction = URLACTION_SCRIPT_RUN
		 	{
		 		NumPut(0x00, p3+0) ;// *pPolicy := URLPOLICY_ALLOW = 0x00
		 		return 0 ;// S_OK
		 	}
		}
		return 0x800C0011 ;// INET_E_DEFAULT_ACTION
	}

	_GUID2String(pGUID)
	{
		VarSetCapacity(string, 38*2)
		DllCall("ole32\StringFromGUID2", "ptr", pGUID, "str", string, "int", 39)
		return string
	}
}
Another thing when you store a reference to the instance of ActiveScript, how come you were able to retrieve it using Object(NumGet(this + A_PtrSize*2)) , I believe &Script is the third field, however it's stored in ObjGetAddress(this, "_site"). The pointers to vftable are also stored in ObjGetAddress(this, "_site"). So if I understand correctly, ObjGetAddress(this, "_site") is IActiveScriptSite, right?

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 12 Oct 2014, 19:40
by lexikos
The comment was:

Code: Select all

    ; AddRef and Release don't do anything because we want to avoid circular references.
    ; The site and IActiveScript are both released when the AHK script releases its last
    ; reference to the ActiveScript object.
The references are: your ahk script -> ActiveScript -> IActiveScript -> site -> ActiveScript -> IActiveScript ... If the references were counted, there would be a circular reference.

If your interface might be called after the ahk script releases all references to its WBClientSite instance, you need to implement reference counting to keep the interface alive until the WebBrowser no longer needs it. Otherwise, you don't need to implement reference counting.
Another thing
I guess you figured out this is the interface pointer, not the object.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 16 Jan 2015, 17:25
by Relayer
lexikos,

I see your vbscript example... is there a way to pass parameters into the vbscript. You show how to access variables to get them out but what if the vbscript function accepts and requires parameters? I tried:

vb.variable := "blah" before executing vb.exec(code) but that didn't work

Relayer

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 17 Jan 2015, 23:09
by lexikos
Relayer wrote:vb.variable := "blah" before executing vb.exec(code) but that didn't work
That's not passing a parameter...

The example uses vb.GetAnswer() to call the GetAnswer function which was defined via Exec. If you want to pass a parameter, just do it.

Code: Select all

vb.GetAnswer(42)
You can only set a VBScript variable if it's already been declared:
Example.ahk wrote:vb.Answer := 42 ; Set a previously-declared global variable.
However, in AutoHotkey v1.1.18+ this requirement doesn't apply to JScript.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 23 Jan 2015, 05:15
by lexikos
I've updated the script:
  • JScript 5.8 (IE8) features such as JSON.parse() and JSON.stringify() are enabled automatically when using JScript.
  • AddObject() now supports normal AutoHotkey objects on AutoHotkey v1.1.17+.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 12 Feb 2016, 06:38
by lexikos
I've added a method for enabling the use of WinRT (the framework used by Windows 10 apps) with JsRT.Edge, and an example for displaying a toast notification.

Image

There's a list of WinRT APIs which can be used from desktop apps:
https://msdn.microsoft.com/en-au/library/dn554295

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 12 Feb 2016, 06:59
by jNizM
Looks great. Thank you lexikos.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 12 Feb 2016, 08:42
by joedf
Ohhh fancy! :D

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 20 Mar 2016, 04:02
by Coco
@lexikos, not sure if you've seen it already, I submitted a PR - mainly to provide v2.0-a compatibility.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 20 Mar 2016, 04:20
by lexikos
Merged, thanks.

Re: ActiveScript - Host VBScript and JScript in-process

Posted: 17 Apr 2017, 12:30
by F4Jonatas
Hello!
I want to know why this is not right!?

Code: Select all

#NoEnv
#Include ActiveScript.ahk
#Include JsRT.ahk


Gui, +Resize MinSize671x300
Gui, Show, W671 H300, JsRT
Gui, Color, Gray
Gui, Add, ActiveX, X0 Y0 W671 H300 VIE, "Shell.Explorer"



; Toast notifications from desktop apps can only use local image files.
if !FileExist("sample.png")
    URLDownloadToFile https://autohotkey.com/boards/styles/simplicity/theme/images/announce_unread.png
        , % A_ScriptDir "\sample.png"
; The templates are described here:
;  http://msdn.com/library/windows/apps/windows.ui.notifications.toasttemplatetype.aspx
toast_template := "toastImageAndText02"
; Image path/URL must be absolute, not relative.
toast_image := A_ScriptDir "\sample.png"
; Text is an array because some templates have multiple text elements.
toast_text := ["Hello, world!", "This is the sub-text."]

; Only the Edge version of JsRT supports WinRT.
js := new JsRT.Edge
js.AddObject("yesno", Func("yesno"))
yesno(s) {
    MsgBox 4,, %s%
    IfMsgBox Yes
        return true
}

; Enable use of WinRT.  "Windows.UI" or "Windows" would also work.
js.ProjectWinRTNamespace("Windows.UI.Notifications")
code =
(
    function toast(template, image, text, app) {
        // Alias for convenience.
        var N = Windows.UI.Notifications;
        // Get the template XML as an XmlDocument.
        var toastXml = N.ToastNotificationManager
            .getTemplateContent(N.ToastTemplateType[template]);
        // Insert our content.
        var i = 0;
        for (let el of toastXml.getElementsByTagName("text")) {
            if (typeof text == 'string') {
                el.innerText = text;
                break;
            }
            el.innerText = text[++i];
        }
        toastXml.getElementsByTagName("image")[0]
            .setAttribute("src", image);
        // Show the notification.
        var toastNotifier = N.ToastNotificationManager
            .createToastNotifier(app || "AutoHotkey");
        var notification = new N.ToastNotification(toastXml);
        toastNotifier.show(notification);
        // Unlike TrayTip, this API lets us hide the notification:
        if (yesno("Hide the notification?")) {
            toastNotifier.hide(notification);
        }
    }
)
try {
    ; Define the toast function.
    js.Exec(code)
    ; Show a toast notification.
    js.toast(toast_template, toast_image, toast_text)
}
catch ex {
    try errmsg := ex.stack
    if !errmsg
        errmsg := "Error: " ex.message
    MsgBox % errmsg
}
; Note: If the notification wasn't hidden, it will remain after we exit.
; ExitApp