ActiveScript - Host VBScript and JScript in-process

Post your working scripts, libraries and tools for AHK v1.1 and older
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

ActiveScript - Host VBScript and JScript in-process

13 Sep 2014, 04:09

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.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

13 Sep 2014, 05:16

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.
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

13 Sep 2014, 05:36

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.
User avatar
fincs
Posts: 527
Joined: 30 Sep 2013, 14:17
Location: Seville, Spain
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

13 Sep 2014, 05:52

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?
fincs
Windows 11 Pro (Version 22H2) | AMD Ryzen 7 3700X with 32 GB of RAM | AutoHotkey v2.0.0 + v1.1.36.02
Get SciTE4AutoHotkey v3.1.0 - [My project list]
User avatar
joedf
Posts: 8937
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

13 Sep 2014, 08:30

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
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

13 Sep 2014, 08:53

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.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

11 Oct 2014, 07:45

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.) ;)
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

11 Oct 2014, 18:56

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.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

12 Oct 2014, 02:31

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?
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

12 Oct 2014, 19:40

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.
User avatar
Relayer
Posts: 160
Joined: 30 Sep 2013, 13:09
Location: Delaware, USA

Re: ActiveScript - Host VBScript and JScript in-process

16 Jan 2015, 17:25

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
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

17 Jan 2015, 23:09

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.
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

23 Jan 2015, 05:15

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+.
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

12 Feb 2016, 06:38

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
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

12 Feb 2016, 06:59

Looks great. Thank you lexikos.
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

20 Mar 2016, 04:02

@lexikos, not sure if you've seen it already, I submitted a PR - mainly to provide v2.0-a compatibility.
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Re: ActiveScript - Host VBScript and JScript in-process

17 Apr 2017, 12:30

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
Last edited by F4Jonatas on 29 Apr 2017, 12:33, edited 2 times in total.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 80 guests