ObjRegisterActive

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

ObjRegisterActive

29 Jan 2015, 03:22

Code: Select all

/*
    ObjRegisterActive(Object, CLSID, Flags:=0)
    
        Registers an object as the active object for a given class ID.
        Requires AutoHotkey v1.1.17+; may crash earlier versions.
    
    Object:
            Any AutoHotkey object.
    CLSID:
            A GUID or ProgID of your own making.
            Pass an empty string to revoke (unregister) the object.
    Flags:
            One of the following values:
              0 (ACTIVEOBJECT_STRONG)
              1 (ACTIVEOBJECT_WEAK)
            Defaults to 0.
    
    Related:
        http://goo.gl/KJS4Dp - RegisterActiveObject
        http://goo.gl/no6XAS - ProgID
        http://goo.gl/obfmDc - CreateGUID()
*/
ObjRegisterActive(Object, CLSID, Flags:=0) {
    static cookieJar := {}
    if (!CLSID) {
        if (cookie := cookieJar.Remove(Object)) != ""
            DllCall("oleaut32\RevokeActiveObject", "uint", cookie, "ptr", 0)
        return
    }
    if cookieJar[Object]
        throw Exception("Object is already registered", -1)
    VarSetCapacity(_clsid, 16, 0)
    if (hr := DllCall("ole32\CLSIDFromString", "wstr", CLSID, "ptr", &_clsid)) < 0
        throw Exception("Invalid CLSID", -1, CLSID)
    hr := DllCall("oleaut32\RegisterActiveObject"
        , "ptr", &Object, "ptr", &_clsid, "uint", Flags, "uint*", cookie
        , "uint")
    if hr < 0
        throw Exception(format("Error 0x{:x}", hr), -1)
    cookieJar[Object] := cookie
}
Known limitations:
  • If the script quits while running a method called by a remote script, the remote script will receive an error.
  • There are also limitations related to how AutoHotkey locally interacts with COM objects, and how AutoHotkey objects respond to requests via COM interfaces.
    • For-loops don't support remote objects.
    • Variable can't be passed ByRef to a remote object.
    • Some invocations are ambiguous; for example, foo.bar triggers foo.__Call and then foo.__Get.
To try, run both of the following files:

Code: Select all

; Register our object so that other scripts can get to it.  The second
; parameter is a GUID which I generated.  You should generate one unique
; to your script.  You can use [CreateGUID](http://goo.gl/obfmDc).
ObjRegisterActive(ActiveObject, "{6B39CAA1-A320-4CB0-8DB4-352AA81E460E}")

#Persistent
OnExit Revoke
return

Revoke:
; This "revokes" the object, preventing any new clients from connecting
; to it, but doesn't disconnect any clients that are already connected.
; In practice, it's quite unnecessary to do this on exit.
ObjRegisterActive(ActiveObject, "")
ExitApp

; This is a simple class (object) that clients will interact with.
; You can register any object; it doesn't have to be a class.
class ActiveObject {
    ; Simple message-passing example.
    Message(Data) {
        MsgBox Received message: %Data%
        return 42
    }
    ; "Worker thread" example.
    static WorkQueue := []
    BeginWork(WorkOrder) {
        this.WorkQueue.Insert(WorkOrder)
        SetTimer Work, -100
        return
        Work:
        ActiveObject.Work()
        return
    }
    Work() {
        work := this.WorkQueue.Remove(1)
        ; Pretend we're working.
        Sleep 5000
        ; Tell the boss we're finished.
        work.Complete(this)
    }
    Quit() {
        MsgBox Quit was called.
        DetectHiddenWindows On  ; WM_CLOSE=0x10
        PostMessage 0x10,,,, ahk_id %A_ScriptHwnd%
        ; Now return, so the client's call to Quit() succeeds.
    }
}

Code: Select all

; Get an active something.
x := ComObjActive("{6B39CAA1-A320-4CB0-8DB4-352AA81E460E}")

; Make up a property.  It's an AutoHotkey object, after all.
x.Name := "Bob"

; Call a method.
x.Message("Hello, world!")

; Queue some work to be done. We'll be notified when it's finished.
x.BeginWork({Complete: Func("Completed")})

#Persistent

Completed(work, worker) {
    MsgBox % worker.Name " finished work."
    worker.Quit()  ; Fire?
    ; PostMessage allows us to return before closing.
    DetectHiddenWindows On  ; WM_CLOSE=0x10
    PostMessage 0x10,,,, ahk_id %A_ScriptHwnd%
}
Related: GetActiveObjects()
User avatar
boiler
Posts: 16768
Joined: 21 Dec 2014, 02:44

Re: ObjRegisterActive

29 Jan 2015, 07:54

Great. Thanks for this.
User avatar
cyruz
Posts: 346
Joined: 30 Sep 2013, 13:31

Re: ObjRegisterActive

29 Jan 2015, 08:47

Nice. Aside from IPC can you elaborate on possible use cases?
Is your "Worker thread" example. comment a way to say that with this code we can multithread safely?
ABCza on the old forum.
My GitHub.
min

Re: ObjRegisterActive

29 Jan 2015, 09:00

Nice! Btw, Host.ahk gives error:

Code: Select all

Error:  0x800706BA - The RPC server is unavailable.
Specifically: Complete
---> work.Complete(this)
User avatar
joedf
Posts: 8940
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: ObjRegisterActive

29 Jan 2015, 09:17

@lexikos so this is the multithreading you were talking about... :)
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]
min

Re: ObjRegisterActive

29 Jan 2015, 09:27

Does using ObjRegisterActive mean that now we can write AHK apps which can be controlled by COM by other programs written in other languages?
e.g. some C++ app uses COM and calls a method or sets/gets property in our AHK script? If so --> that's awesome! :D
User avatar
fincs
Posts: 527
Joined: 30 Sep 2013, 14:17
Location: Seville, Spain
Contact:

Re: ObjRegisterActive

29 Jan 2015, 10:57

Nice script and example. This is exactly the same technique SciTE4AutoHotkey uses to expose its COM object.
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
Learning one
Posts: 173
Joined: 04 Oct 2013, 13:59
Location: Croatia
Contact:

Re: ObjRegisterActive

29 Jan 2015, 15:25

Lexikos, this is fantastic! 8-)
Thank you so much! :)
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: ObjRegisterActive

29 Jan 2015, 17:14

cyruz wrote:Aside from IPC can you elaborate on possible use cases?
IPC is effectively all that it is. It's just a question of what you use IPC for.
Is your "Worker thread" example. comment a way to say that with this code we can multithread safely?
Multiple processes means multiple threads, so yes. Any IPC method could be used to coordinate multiple processes. This just makes it a bit easier in some cases. For simple tasks you can just run another script with (or without) command line parameters.
min wrote:Host.ahk gives error:
I've updated the examples. I think there was a race condition between the host handling the WM_CLOSE message and the client exiting. The limitation here is that if the script exits while the remote end is waiting for a method to return, they get an error. In one script I used PostMessage to work around that, but in the other script I forgot and used ExitApp. A proper COM server might implement the IExternalConnection interface to detect when all external connections have been released, then exit. However, in the case of just passing objects back and forward in method calls, that wouldn't be very practical.
joedf wrote:@lexikos so this is the multithreading you were talking about... :)
I don't recall making any specific reference to this in regard to multi-threading, only IPC. This is just one of many alternatives to direct multi-threading.
min wrote:Does using ObjRegisterActive mean that now we can write AHK apps which can be controlled by COM by other programs written in other languages?
Yes. It's easy to do from VBScript. However, it seems GetObject() requires a ProgID rather than a CLSID, so you need to register it in the registry.

Code: Select all

' First set HKCR\testy\CLSID (default value) to the GUID
set x = GetObject(,"testy")
x.Message("Hello, world!")
fincs wrote:This is exactly the same technique SciTE4AutoHotkey uses to expose its COM object.
It differs in a number of ways. For instance, this function uses a strong reference by default and doesn't bother with CoLockObjectExternal. Where ComRemote closes connections and revokes the object automatically when the server end releases its "ComRemote" object, this function keeps the object alive and accessible until it is revoked. The function also accepts a normal object, not a ComObject wrapper. An object can be revoked without disconnecting clients, so for instance, a worker process could revoke its object when in use to allow the objects of other (idle) worker processes to be accessed.
Guest

Re: ObjRegisterActive

29 Jan 2015, 19:53

Lexikos,

I tried this on as simple object in script #1:

Code: Select all

AnObject := {one:1, two:2, three:3}
ObjRegisterActive(AnObject, "{6B39CAA1-A320-4CB0-8DB4-352AA81E460E}")

#Persistent
I then accessed it in script #2:

Code: Select all

x := ComObjActive("{6B39CAA1-A320-4CB0-8DB4-352AA81E460E}")
Msgbox % x.one . "`n" . x.two . "`n" . x.three
for key, value in x
	Msgbox % key . "`n" . value
The first Msgbox works but the for loop complains it cannot find a member. It looks as if x cannot be accessed the same way as it can in the script that created it.

Relayer
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: ObjRegisterActive

29 Jan 2015, 20:35

@fincs why is S4AHK's code for this so much shorter? Also, do you register a ProgID in the registry?
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: ObjRegisterActive

29 Jan 2015, 21:21

Huh? ComRemote.ahk is not shorter, unless you're including comments. That's even without considering the dependency (Str2GUID) and that it has less error checking. ComInterface.ahk registers a ProgID and the CLSID in the registry; look for RegisterIDs().
Relayer wrote:The first Msgbox works but the for loop complains it cannot find a member.
I haven't tested yet but I presume it's because the ComObject wrapper (for the remote object) tries to use an IEnumVARIANT-based enumerator instead of just calling object._NewEnum(). You should be able to get around it by calling _NewEnum and enum.Next() directly instead of using a for-loop.
[Edit: Wrong. See below.]
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: ObjRegisterActive

29 Jan 2015, 21:32

You're completely right, of course. Thanks for pointing out ComInterface.ahk, also. HKCU is the one you can write to without admin permissions, right?

How exactly does the inter-process calling of Func()tion references work? I would've expected some kind of RegisterCallback to be required. Was that part of "Added support for passing AutoHotkey objects to COM APIs as IDispatch."?
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: ObjRegisterActive

29 Jan 2015, 22:17

"Added support for passing AutoHotkey objects to COM APIs as IDispatch." is the whole reason that this works at all. Without it, we would have to implement our own IDispatch interface in script, as SciTE4AutoHotkey does. %fn%() calls fn[""](), which usually fails with COM objects but works okay with AutoHotkey objects via COM. However, work.Complete() isn't calling a Func reference; it's calling a method of the work object.
User avatar
maestrith
Posts: 825
Joined: 16 Oct 2013, 13:52

Re: ObjRegisterActive

30 Jan 2015, 05:00

Thank you so much lexikos! Studio will have this soon.
John H Wilson III 05/29/51 - 03/01/2020. You will be missed.AHK Studio OSDGUI Creator
Donations
Discord
All code is done on a 64 bit Windows 10 PC Running AutoHotkey x32
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: ObjRegisterActive

31 Jan 2015, 07:45

Guest wrote:The first Msgbox works but the for loop complains it cannot find a member.
There are actually two problems:
  1. "Member not found" comes from requesting the member DISPID_NEWENUM instead of attempting to resolve the name "_NewEnum". This works well with COM objects (since some don't actually resolve the name "_NewEnum"), but AutoHotkey objects don't respond to that ID.
  2. Even if you call _NewEnum() directly, you can't use an AutoHotkey enumerator object remotely because it expects variable references to put the items into. The for-loop passes a normal variable reference, not Variant reference (a ComObject with VT_BYREF|VT_VARIANT). You can pass a Variant reference, but the AutoHotkey object at the other end will just dereference it and pass a useless value to the enumerator.
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: ObjRegisterActive

31 Jan 2015, 21:01

I have posted another example in the form of a new function: LoadFile - Load script file as a separate process.

It demonstrates temporary registration of an object using a unique ID to prevent conflicts.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: ObjRegisterActive

31 Jan 2015, 21:45

I want so much to have a use for this. GeekBot comes very close, but he already has a socket based system that works great under WINE, allowing non-WINE programs such as php to interact.
min

Re: ObjRegisterActive

02 Feb 2015, 02:46

Lexikos, what are the terms of use for your ObjRegisterActive(), LoadFile(), LoadLib() ?
lexikos
Posts: 9553
Joined: 30 Sep 2013, 04:07
Contact:

Re: ObjRegisterActive

02 Feb 2015, 03:25

WTFPL or equivalent.

If use is allowed unconditionally, does that mean there are no terms of use?

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: JoeWinograd and 130 guests