Best practice ? Read variable from other scripts

Get help with using AutoHotkey and its commands and hotkeys
User avatar
SL5
Posts: 497
Joined: 12 May 2015, 02:10
GitHub: sl5net
Contact:

Best practice ? Read variable from other scripts

10 Feb 2018, 10:06

hi I am looking for the best way to read from another AHK script a variable.

In my particular case, a dynamic AHK (Script 2) is created (with dynamically created include addresses) and then its run (all from Script 1).

Currently Script 1 waits for a return file, which then contains the string.
Is this better about environment variables? Or registry?
Guest

Re: Best practice ? Read variable from other scripts

10 Feb 2018, 10:33

There are various methods, "built-in" method would be
OnMessage() - last example in the docs https://autohotkey.com/docs/commands/OnMessage.htm

Alternatives (there are many more, but these are easy to use):
Use a Hidden Gui https://autohotkey.com/boards/viewtopic.php?t=3275
Talk() https://autohotkey.com/board/topic/9432 ... -and-more/
User avatar
SL5
Posts: 497
Joined: 12 May 2015, 02:10
GitHub: sl5net
Contact:

Re: Best practice ? Read variable from other scripts

14 Feb 2018, 12:42

Thank you all ! :) I will remember all of it :) (y)
I have implemented one of your suggestions here (OnMessage() - last example in the docs https://autohotkey.com/docs/commands/OnMessage.htm):
https://github.com/sl5net/global-Intell ... nc.ahk#L13
User avatar
eventhorizon
Posts: 158
Joined: 27 Oct 2016, 14:22

Re: Best practice ? Read variable from other scripts

14 Feb 2018, 14:30

if you know which variables you will be looking for then one of the simplest ways is to have the script with the variables write them off to an ini or text file and have your search script read them from there. more complex would be to scan the text of the variable source script looking for the variable name like parsing a source file for a compiler or an interpreter and recovering the variable and it's value from there. if the value can vary from run to run then i'd recommend using the ini file method. it's cleaner.
A computer lets you make more mistakes faster than any invention in human history – with the possible exceptions of handguns and tequila.
cocobanana
Posts: 19
Joined: 31 Oct 2017, 05:43

Re: Best practice ? Read variable from other scripts

14 Feb 2018, 15:52

AhkSelf() is also a good solution....

https://github.com/HotKeyIt/ahkdll-v2-r ... hkSelf.ahk

or do you want AHK_L only code?

here is an example

Code: Select all

;Main_Thread
global main            := AhkSelf() ; Create new Critical object
;Create Sub_Thread
file                   := "C:\your_script.ahk"
fileObj                := FileOpen(file, "r", UTF-8-RAW)
str                    := fileObj.Read()
str_thread             := "main:=CriticalObject(" (&main) ")`n" . str
fileObj.Close()

t_sub:= AhkThread(str_thread)

return
;Now you can communicate between threads per global Object ---> here called main.
User avatar
Masonjar13
Posts: 1405
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: Best practice ? Read variable from other scripts

14 Feb 2018, 17:53

Similar to AhkSelf(), you could directly use AHK_H.dll to multi-thread. By multi-threading, the main script is able to access the content of and execute functions, subs, and code the child thread(s). I have a class here that does that (v1.1). In terms of best practice, I would certainly recommend multi-threading, much less messy than having to separate processes (imo). In terms of simplicity, definitely an ini/txt file.
cocobanana
Posts: 19
Joined: 31 Oct 2017, 05:43

Re: Best practice ? Read variable from other scripts

14 Feb 2018, 18:59

Masonjar13 wrote:Similar to AhkSelf(), you could directly use AHK_H.dll to multi-thread. By multi-threading, the main script is able to access the content of and execute functions, subs, and code the child thread(s). I have a class here that does that (v1.1). In terms of best practice, I would certainly recommend multi-threading, much less messy than having to separate processes (imo). In terms of simplicity, definitely an ini/txt file.
thumps up for your threadMan.ahk :thumbup:
User avatar
SL5
Posts: 497
Joined: 12 May 2015, 02:10
GitHub: sl5net
Contact:

Re: Best practice ? Read variable from other scripts

14 Feb 2018, 20:40

cocobanana wrote:
Masonjar13 wrote:Similar to AhkSelf(), you could directly use AHK_H.dll to multi-thread. By multi-threading, the main script is able to access the content of and execute functions, subs, and code the child thread(s). I have a class here that does that (v1.1). In terms of best practice, I would certainly recommend multi-threading, much less messy than having to separate processes (imo). In terms of simplicity, definitely an ini/txt file.
thumps up for your threadMan.ahk :thumbup:
interesting.
i have problems with Receive_WM_COPYDATA .
seems like the script often could not find hidden scipts. sometimes it find it sometimes not. i dont understand. it drives me crazy
from https://github.com/sl5net/global-Intell ... id.ahk#L25

Code: Select all

Receive_WM_COPYDATA(wParam, lParam)
{
    StringAddress := NumGet(lParam + 2*A_PtrSize)  ; Retrieves the CopyDataStruct's lpData member.
CopyOfData := StrGet(StringAddress)  ; Copy the string out of the structure.
; Show it with ToolTip vs. MsgBox so we can return in a timely fashion:
;msgbox, %A_ScriptName%`nReceived:`n%CopyOfData%
;RegRead, wordlistActive, HKEY_CURRENT_USER, SOFTWARE\sl5net, CopyOfData

global wordlistOLD
global wordlist
;    wordlistNEWarchivePath := CopyOfData
;    wordlistActivePath  := CopyOfData
;    wordlistNEW := CopyOfData
wordlist := CopyOfData
tooltip,Receive_WM_COPYDATA: `n`n''%wordlist%' = wordlist `n ( %A_ScriptName%(inc)~%A_LineNumber% ) `n

;msgbox,Receive_WM_COPYDATA: `n`n'%wordlist%' = wordlist `n ( %A_ScriptName%(inc)~%A_LineNumber% ) `n
ToolTip4sec(wordlistOLD . "<??>" . wordlist . "´n´n" . A_LineNumber . " " . A_ScriptName . " " . Last_A_This)
Sleep,1000

if( 1 && wordlistOLD <> wordlist){
    feedbackMsgBox("wordlistOLD <> wordlist",wordlistOLD . " <> " . wordlist . "`n" . A_ScriptName . "(inc)~" . A_LineNumber)
    ;setGlobalWordlist(wordlistActive)
    ;InitializeListBox()
    ;BlockInput, Send ; Send:  The user's keyboard and mouse input is ignored while a Send or SendRaw is in progress
    InitializeHotKeys()
    DisableKeyboardHotKeys()
    SetBatchLines, -1 ;Change the Running performance speed (Priority changed to High in GetIncludedActiveWindow)
    ;feedbackMsgBox("ReadInTheWordList()",wordlist . "`n" . activeTitle . " = activeTitle  `n " .  A_ScriptName . "(inc)~" . A_LineNumber)
    ReadInTheWordList()
    ;prefs_Length := setLength(ParseWordsCount, maxLinesOfCode4length1)
    wordlistOLD:=wordlist
}
MainLoop()
}
and sending from here: https://github.com/sl5net/global-Intell ... e.ahk#L345

Code: Select all

temp =
(
TargetScriptTitle = TypingAid ahk_class AutoHotkey
TargetScriptTitle = TypingAid.ahk ahk_class AutoHotkey
;TargetScriptTitle = TypingAid.ahk
stringToSend := (InStr(wordlistNEW,"\")) ? wordlistNEW : wordlistDir . "\" . wordlistNEW
result := Send_WM_COPYDATA`(stringToSend, TargetScriptTitle`)
Send_WM_COPYDATA(ByRef StringToSend, ByRef TargetScriptTitle){  ; ByRef saves a little memory in this case.
    ; This function sends the specified string to the specified window and returns the reply.
    ; The reply is 1 if the target window processed the message, or 0 if it ignored it.
    VarSetCapacity(CopyDataStruct, 3*A_PtrSize, 0)  ; Set up the structure's memory area. \
    ; First set the structure's cbData member to the size of the string, including its zero terminator:
    SizeInBytes := (StrLen(StringToSend) + 1) * (A_IsUnicode ? 2 : 1)
    NumPut(SizeInBytes, CopyDataStruct, A_PtrSize)  ; OS requires that this be done.
    NumPut(&StringToSend, CopyDataStruct, 2*A_PtrSize)  ; Set lpData to point to the string itself.
    Prev_DetectHiddenWindows := A_DetectHiddenWindows
    Prev_TitleMatchMode := A_TitleMatchMode
    DetectHiddenWindows On
    SetTitleMatchMode 2
    IfWinNotExist,`% TargetScriptTitle
{
    tooltip,`% TargetScriptTitle . "=TargetScriptTitle  :( NOT found "
    sleep,4000
    return
}
    TimeOutTime = 2400  ; Optional. Milliseconds to wait for response from receiver.ahk. Default is 5000
    ; Must use SendMessage not PostMessage.
    ; see Receive_WM_COPYDATA
    ; SendMessage, 0x4a, 0, &CopyDataStruct,, `%TargetScriptTitle`%,,,, `%TimeOutTime`% ; 0x4a is WM_COPYDATA.
    ;SendMessage, 0x4a, 0, &CopyDataStruct,, TypingAid,,,, `%TimeOutTime`% ; 0x4a is WM_COPYDATA.
    ;SendMessage, 0x4a, 0, &CopyDataStruct,, TypingAid,,,, `%TimeOutTime`% ; 0x4a is WM_COPYDATA.
    ;PostMessage, 0x4a, 0, &CopyDataStruct,, `%TargetScriptTitle`%,,,,  ; 0x4a is WM_COPYDATA.
    SendMessage, 0x4a, 0, &CopyDataStruct,, TypingAid ahk_class AutoHotkey,,,, `%TimeOutTime`% ; 0x4a is WM_COPYDATA.
    ;PostMessage, 0x4a, 0, &CopyDataStruct,, TypingAid,,,,  ; 0x4a is WM_COPYDATA.
    ;msg:="#" . StringToSend  . "# ``n" . A_ScriptName . "~" . A_LineNumber
    ;tooltip,`% "SendMessage: " . msg
    ;sleep,2000
    ;feedbackMsgBox("SendMessage",msg)
    DetectHiddenWindows `%Prev_DetectHiddenWindows`%  ; Restore original setting for the caller.
    SetTitleMatchMode `%Prev_TitleMatchMode`%         ; Same.
    return ErrorLevel  ; Return SendMessage's reply back to our caller.
}
; RegWrite, REG_SZ, HKEY_CURRENT_USER, SOFTWARE\sl5net, wordlistDir, `%wordlistDir`%
; RegWrite, REG_SZ, HKEY_CURRENT_USER, SOFTWARE\sl5net, wordlistActive, `%wordlistActive`%
; RegWrite, REG_SZ, HKEY_CURRENT_USER, SOFTWARE\sl5net, wordlistNEW, `%wordlistNEW`%
; RegWrite, REG_SZ, HKEY_CURRENT_USER, SOFTWARE\sl5net, wordlistNEWarchivePath, `%wordlistNEWarchivePath`%
)
;feedbackMsgBox("SendMessage",temp . "`n#" . wordlistNEW  . "#`n`n" . A_ScriptName . "~" . A_LineNumber)
ahkSource .= temp
it seems for me that the script sometimes cant find each other.

i hope its safer, more reliable with AHK_H.dll
cocobanana
Posts: 19
Joined: 31 Oct 2017, 05:43

Re: Best practice ? Read variable from other scripts

14 Feb 2018, 20:55

it seems for me that the script sometimes cant find each other.

i hope its safer, more reliable with AHK_H.dll
In my opinion multi-threading works more accurately and it is much simpler to use.
FanaticGuru
Posts: 1238
Joined: 30 Sep 2013, 22:25

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 00:37

After I discovered Lexikos' ObjRegisterActive, everything else including my own hidden GUI solution seems like a hack.

COM was specifically designed for applications to communicate with each other and ObjRegisterActive provides a way to tap into that.

Example:

Code: Select all

;ObjReg1
#Persistent

;  Create an Object
x := {}, x.Name :=  "Bob"

; Register Object
ObjRegisterActive(x, "{93C04B39-0465-4460-8CA0-7BFFF481FF98}")

Code: Select all

; ObjReg2

; Connect to Registered Object
y := ComObjActive("{93C04B39-0465-4460-8CA0-7BFFF481FF98}")

MsgBox % y.Name
Run the first script then run the second script and it will display "Bob".

This is a simple object, The object can be very complex with tons of data in it.

And it is not just accessible from one script to another, it is accessible to pretty much every application running that can access COM objects. Excel for example could access my COM Object created by AHK. Every program can read the object and every program can change the object.

You have to have ObjRegisterActive but it is a relatively small function:

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
}
FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts

AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon

[Function] Timer - Create and Manage Timers
User avatar
SL5
Posts: 497
Joined: 12 May 2015, 02:10
GitHub: sl5net
Contact:

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 03:24

FanaticGuru wrote:After ... ObjRegisterActive, everything else .... seems like a hack.
COM was specifically designed for applications to communicate with each other and ObjRegisterActive provides a way to tap into that.

Example: ...
What's really handy for this script is calling a function from the other script. So I want to try it for a while, with this technique or something similar:
https://github.com/sl5net/global-Intell ... e.ahk#L345

Code: Select all

TargetScriptTitle = TypingAid ahk_class AutoHotkey
TargetScriptTitle = TypingAid.ahk ahk_class AutoHotkey
;TargetScriptTitle = TypingAid.ahk
stringToSend := (InStr(wordlistNEW,"\")) ? wordlistNEW : wordlistDir . "\" . wordlistNEW
result := Send_WM_COPYDATA`(stringToSend, TargetScriptTitle`)
Send_WM_COPYDATA(ByRef StringToSend, ByRef TargetScriptTitle){
...
DetectHiddenWindows On
SetTitleMatchMode 2
PostMessage, 0x4a, 0, &CopyDataStruct,, TypingAid ahk_class AutoHotkey,,,,  ; 0x4a is WM_COPYDATA.
...
Sometimes it works. And then I find it really, very fast and very practical. If it was somehow just a mistake of mine (that would be nice). Really handy calling a function in a foreign script.
User avatar
nnnik
Posts: 3563
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 03:37

Yeah OnMessage is nothing more but a bad hack compared to ObjRegisterActive. You can share an object between scripts and an Object is Data + Functions.
And you could introduce a method to the object that lets you call another scripts function.

Code: Select all

#Persistent
ObjRegisterActive(Stuff, "{93C04B39-0465-4460-8CA0-7BFFF481FF98}")

function( text ) {
	Msgbox % text
}
class Stuff{
	static abc := 1
	callFunction( name, p* ) { ;allows you to call any function in this script
		abc := func( name )
		%abc%( p* )
	}
}

Code: Select all

y := ComObjActive("{93C04B39-0465-4460-8CA0-7BFFF481FF98}")
y.callFunction( "function", "Hello World!" ) ;will call the function Hello World of the other script
Compared to COM the SendMessage method is nothing but a bad hack. There are no advantages to it.
Recommends AHK Studio
User avatar
SL5
Posts: 497
Joined: 12 May 2015, 02:10
GitHub: sl5net
Contact:

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 04:40

nnnik wrote:Yeah OnMessage is nothing more but a bad hack compared to ObjRegisterActive. You can share an object between scripts and an Object is Data + Functions.
...
y := ComObjActive("{93C04B39-0465-4460-8CA0-7BFFF481FF98}")
y.callFunction( "function", "Hello World!" ) ;will call the function Hello World of the other script ...
:bravo: :dance: yes it works. And pretty fast. :D
( https://github.com/sl5net/global-Intell ... nc.ahk#L12 )
Will it be even faster, if not access to all functions is possible, only to my string-transfer function?
User avatar
nnnik
Posts: 3563
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 08:00

Yes indeed you can do that. In fact I created a method named callFunction in the object Stuff.
Any method you create in this object is available to the other script like the method callFunction.
You could remove the method callfunction and add another method that only calls a specific function.
That way you already limit which functions can be called.

Of course most of the time everything is possible - you just need to find out how to make it possible.
Recommends AHK Studio
User avatar
SL5
Posts: 497
Joined: 12 May 2015, 02:10
GitHub: sl5net
Contact:

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 08:47

nnnik wrote:...
You could remove the method callfunction and add another method that only calls a specific function.
That way you already limit which functions can be called...
great thanks, and here it is:

Code: Select all

fnReceive_wordlistAddress := Func("Receive_wordlistAddress").Bind(1)
ObjRegisterActive(fnReceive_wordlistAddress, "{93C04B39-0465-4460-8CA0-7BFFF481FF98}")  ; Receive_wordlistAddress(CopyOfData){
this seems working. if its faster? i dont know.
function need to be a object. therfore i created fnReceive_wordlistAddress

By the way. this long number.
After some research, I suspect it is CLSID. unique to an application, not to your computer.
But i didnt find this number by web-searchengine. hmmm
User avatar
nnnik
Posts: 3563
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 13:12

You can set it to any other CLSID.
Actually I think it is better to share 1 object and put all the functions inside there as methods.
II don't know if it is faster.
Recommends AHK Studio
FanaticGuru
Posts: 1238
Joined: 30 Sep 2013, 22:25

Re: Best practice ? Read variable from other scripts

15 Feb 2018, 13:36

SL5 wrote:
nnnik wrote:...
You could remove the method callfunction and add another method that only calls a specific function.
That way you already limit which functions can be called...
great thanks, and here it is:

Code: Select all

fnReceive_wordlistAddress := Func("Receive_wordlistAddress").Bind(1)
ObjRegisterActive(fnReceive_wordlistAddress, "{93C04B39-0465-4460-8CA0-7BFFF481FF98}")  ; Receive_wordlistAddress(CopyOfData){
this seems working. if its faster? i dont know.
function need to be a object. therfore i created fnReceive_wordlistAddress

By the way. this long number.
After some research, I suspect it is CLSID. unique to an application, not to your computer.
But i didnt find this number by web-searchengine. hmmm
A Class is really the way to go. A Class is basically a structure for storing Functions. They are called methods when they are in a class but they still work like functions. This is much more "best practices".

Script to create class:

Code: Select all

;ObjReg3

class Math
{
	Add5(n)
	{
		return n+5
	}
	Mult3(n)
	{
		return n*3
	}
	CircleArea(r)
	{
		static Pi := 3.14159265359
		Area := Pi * r**2
		return Area
	}
}

ObjRegisterActive(Math, "{93C04B39-0465-4460-8CA0-7BFFF481FF98}")

Esc::ExitApp
Script to use class:

Code: Select all

; ObjReg4

Math := ComObjActive("{93C04B39-0465-4460-8CA0-7BFFF481FF98}")

MsgBox % Math.Add5(6)
MsgBox % Math.Mult3(6)
MsgBox % Math.CircleArea(6)
By doing it this way you are also entering the world of conformity and doing things the same way as most other Windows applications.

Speed wise it should be pretty quick. There is some overhead to register the object and to connect to the object but that only needs to be done once in a script, after that actually using the object should be fast.

As for the long random looking string of characters, that is pretty much what it is: a random id string. It is in the proper form to be an GUID. Every application should have its own unique GUID so that if two developers publish applications, users will know what type application object they are connecting to by its GUID.

Every COM program has one, Excel, Outlook, AutoCAD, etc. Now those programs modify the Windows Registry to create an alias for that long random string that is more user friendly like ComObjActive("Excel.Application") but that "Excel.Application" is just an alias for a GUID string.

Here is a link to CreateGUID to make your own but really you can just use the one in any of these examples if you are not going to publish your script for wide distribution.
http://goo.gl/obfmDc

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts

AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon

[Function] Timer - Create and Manage Timers
User avatar
SL5
Posts: 497
Joined: 12 May 2015, 02:10
GitHub: sl5net
Contact:

Re: y.callFunction(

28 Feb 2018, 10:51

script hangs at y.callFunction .
it sends START y. ... as messagebox (special type), but nothing more. and it not ends. stays freezed.
I seems its waiting for return value. surprized

Code: Select all

try{
    y := ComObjActive("{93C04B39-0465-4460-8CA0-7BFFF481FF98}")
    feedbackMsgBox(A_ScriptName . ">" . A_LineNumber, "START y.callFunction( Receive_wordlistAddress, " . stringToSend )
    y.callFunction( "Receive_wordlistAddress", stringToSend ) ;will call the function of the other script , ObjRegisterActive , shuttle
    feedbackMsgBox(A_ScriptName . ">" . A_LineNumber, "END y.callFunction( Receive_wordlistAddress, " . stringToSend )

} catch e{
    tip:="Exception:``n" e.What "``n" e.Message "``n" e.File "@" e.Line
    lll(A_LineNumber, A_LineFile, tip)
    tooltip, `% tip
}
FanaticGuru
Posts: 1238
Joined: 30 Sep 2013, 22:25

Re: y.callFunction(

28 Feb 2018, 14:11

SL5 wrote:script hangs at y.callFunction .
I seems its waiting for return value. surprized
Without seeing the callFunction it is hard to say but I imagine that is exactly what is happening. Whether it is a function in the script or a function in a COM object, the thread of execution is not going to proceed beyond the function call until the thread returns from the function.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts

AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon

[Function] Timer - Create and Manage Timers

Return to “Ask For Help”

Who is online

Users browsing this forum: au6, Bing [Bot], swagfag, vvhitevvizard and 37 guests