(adapted from Geekdudes RemoteObj.ahk)
Access and use objects remotely from another computer across LAN or internet - or share them between scripts on the same computer via "localhost" IP.
Handle as many objects as you need all on the same port.
AhkSock
https://autohotkey.com/board/topic/53827-ahksock-a-simple-ahk-implementation-of-winsock-tcpip/page-1
Jxon.ahk
https://www.autohotkey.com/boards/viewtopic.php?t=627
RemoteObj_AHKsock.ahk
Code: Select all
/* By RazorHalo
RemoteObj_AHKsock - Using Remote Objects with AHKsock
Version 1.0 - August 8, 2020
*** Required Libraries ***
AHKsock.ahk by TheGood
https://autohotkey.com/board/topic/53827-ahksock-a-simple-ahk-implementation-of-winsock-tcpip/page-1
Jxon.ahk by Coco
https://www.autohotkey.com/boards/viewtopic.php?t=627
Adapted for AHKsock from the original RemoteObj.ahk by GeekDude
https://www.autohotkey.com/boards/viewtopic.php?f=6&t=35242
*/
;##############################################################################################
; REMOTE OBJECT SERVER
;##############################################################################################
Global rObj
class RemoteObjServer {
;Create a listening port
__New(Port) {
If (i := AHKsock_Listen(Port, "rObjServer")) {
OutputDebug, % "AHKsock_Listen() failed with return value = " i " and ErrorLevel = " ErrorLevel
}
rObj := this
}
;Add Object to the server
Add(Obj, ObjID) {
this[ObjID] := Obj
}
;Remove Object from the server
Remove(ObjID) {
this.Delete(ObjID)
}
}
rObjServer(sEvent, iSocket = 0, sName = 0, sAddr = 0, sPort = 0, ByRef bRecvData = 0, bRecvDataLength = 0) {
If (sEvent = "ACCEPTED") {
OutputDebug, % "Server - A client connected!"
} Else If (sEvent = "DISCONNECTED") {
OutputDebug, % "Server - The client disconnected. Going back to listening..."
bConnected := False
} Else If (sEvent = "RECEIVED") {
OutputDebug, % "Server - We received " bRecvDataLength " bytes."
OutputDebug, % "Server - Data: " bRecvData
;We received data.
Query := Jxon_Load(bRecvData)
if (Query.Action == "__Get")
RetVal := rObj[Query.Object][Query.Key]
else if (Query.Action == "__Set")
RetVal := rObj[Query.Object][Query.Key] := Query.Value
else if (Query.Action == "__Call")
RetVal := rObj[Query.Object][Query.Name].Call(rObj[Query.Object], Query.Params*)
bData := Jxon_Dump({"RetVal": RetVal})
bDataLength := StrLen(bData) * 2
;Send the actual data now
If ((i := AHKsock_Send(iSocket, &bData, bDataLength)) < 0 ) {
OutputDebug, % "Server AHKsock_Send failed with return value = " i " and error code = " ErrorLevel " at line " A_LineNumber
} Else OutputDebug, % "Server - Sent " i " bytes!"
}
}
;##############################################################################################
; REMOTE OBJECT CLIENT
;##############################################################################################
class RemoteObjClient {
__New(Addr, Port) {
ObjRawSet(this, "__Addr", Addr)
ObjRawSet(this, "__Port", Port)
}
;Add Object to the server
Add(ObjID) {
this[ObjID] := new this.rObject
ObjRawSet(this[ObjID], "__ObjID", ObjID)
ObjRawSet(this[ObjID], "__Addr", this.__Addr)
ObjRawSet(this[ObjID], "__Port", this.__Port)
}
;Remove Object from the client
Remove(ObjID) {
this.Delete(ObjID)
}
Class rObject {
__Get(Key) {
return rObjSend(this.__Addr, this.__Port, {"Object": this.__ObjID, "Action": "__Get", "Key": Key})
}
__Set(Key, Value) {
return rObjSend(this.__Addr, this.__Port, {"Object": this.__ObjID, "Action": "__Set", "Key": Key, "Value": Value})
}
__Call(Name, Params*) {
return rObjSend(this.__Addr, this.__Port, {"Object": this.__ObjID, "Action": "__Call", "Name": Name, "Params": Params})
}
}
}
rObjSend(Addr, Port, Obj) {
Global oData, oDataLength
, RetVal := ""
, WaitForResponse := True
;Prepare the data for sending
oData := Jxon_Dump(Obj)
;Get text length
oDataLength := StrLen(oData) * 2
;Connect to the server and initiate the transaction of data
If (i := AHKsock_Connect(Addr, Port, "rObjClient")) {
OutputDebug, % "AHKsock_Connect() failed with return value = " i " and ErrorLevel = " ErrorLevel
WaitForResponse := False
Return
}
OutputDebug % "WAITING for Response from Server"
;Wait for the server to process the request and respond
While (WaitForResponse) {
Sleep 50
}
Return RetVal
}
rObjClient(sEvent, iSocket = 0, sName = 0, sAddr = 0, sPort = 0, ByRef bData = 0, iLength = 0) {
Global oData, oDataLength
, RetVal
, WaitForResponse
If (sEvent = "CONNECTED") {
;Check if the connection attempt was succesful
If (iSocket = -1) {
OutputDebug, % "Client - AHKsock_Connect() failed."
WaitForResponse := False
} Else OutputDebug, % "Client - AHKsock_Connect() successfully connected!"
} Else If (sEvent = "DISCONNECTED") {
OutputDebug, % "Client - The server closed the connection."
} Else If (sEvent = "RECEIVED") {
OutputDebug, % "Client - We received " iLength " bytes."
OutputDebug, % "Client - Data: " bData
;Process the returned value
RetVal := Jxon_Load(bData).RetVal
;Exchange is over, close the socket and notify rObjSend()
AHKsock_Close(iSocket)
WaitForResponse := False
} Else If (sEvent = "SEND") {
If ((i := AHKsock_Send(iSocket, &oData, oDataLength)) < 0) {
OutputDebug, % "Client AHKsock_Send failed with return value = " i " and error code = " ErrorLevel " at line " A_LineNumber
} Else OutputDebug, % "Client - Sent " i " bytes!"
}
}
Code: Select all
/* By RazorHalo
RemoteObj_AHKsock Server Example - Using Remote Objects with AHKsock
Version 1.0 - August 8, 2020
*** Required Libraries ***
AHKsock.ahk by TheGood
https://autohotkey.com/board/topic/53827-ahksock-a-simple-ahk-implementation-of-winsock-tcpip/page-1
Jxon.ahk by Coco
https://www.autohotkey.com/boards/viewtopic.php?t=627
*/
#NoEnv
SetBatchLines, -1
#Include AHKsock.ahk
#Include Jxon.ahk
#Include RemoteObj_AHKsock.ahk
;Register OnExit subroutine so that AHKsock_Close is called before exit
OnExit, CloseAHKsock
;Add menu item for exiting gracefully (see comment block in CloseAHKsock)
Menu, Tray, Add
Menu, Tray, Add, Exit Gracefully, CloseAHKsock
;Set up an error handler (this is optional)
AHKsock_ErrorHandler("AHKsockErrors")
;Create instance of the Server
;RemoteObjServer(Port)
ObjServer := new RemoteObjServer(27015)
;Add the object to the server
;Pass the object and quoted identifier - these can be different. The identifier will
;be used on the client side to know ;which object will be called
MyClass := new ExampleClass()
ObjServer.Add(MyClass, "MyClass")
MyTest := new Test()
ObjServer.Add(MyTest, "MyTest")
return
class ExampleClass
{
__New()
{
Gui, New, +hWndhWnd +AlwaysOnTop
this.hWnd := hWnd
Gui, Add, Text, w100 Center, Remote!
Gui, Show
}
AddButton(ButtonText, Action, Params*)
{
hWnd := this.hWnd
Gui, %hWnd%: Default
Gui, Add, Button, xm y+m w100 hWndhButton, %ButtonText%
BoundFunc := this[Action].Bind(this, Params*)
GuiControl, +g, %hButton%, %BoundFunc%
Gui, Show, AutoSize
}
Run(Target)
{
Run, %Target%
}
MsgBox(Text)
{
MsgBox, 4096,, %Text%
}
}
class Test {
InputBox(Prompt) {
InputBox, Out, % this.Title, %Prompt%
return Out
}
}
GuiClose:
CloseAHKsock:
/*! If the user selects the "Exit" menu item from the tray menu, this sub will execute once, i.e. as the OnExit sub. In
this situation, if we're still connected to the server, we will have no way of gracefully shutting down the connection.
But if the user selects the "Exit Gracefully" menu item that we added at startup, this sub will execute twice: once as
the label of the menu item, and once more right after as the OnExit sub (since ExitApp is called at the end of the sub).
Therefore, during the first execution of those two, since the thread will be interruptible, calling AHKsock_Close will
gracefully shutdown the connection. Note that the second call to AHKsock_Close during the OnExit sub then becomes
useless by redundancy, but harmless (so there's no need to ensure that it is only called during the first of the two
executions).
If this application had a GUI, we could instead execute a graceful shutdown on the GuiClose event, as done in other
AHKsock examples. Here, we have to rely on the tray menu because it is the only way for the user to exit while still
being able to gracefully shutdown.
Note however that in this example, the server shuts down the client as soon as it is done sending data to it (see the
server's SEND event of the Send() function). Therefore, the conversation between server and client is very short. This
means that when the user decides to exit the application, chances are, we are no longer connected to the server, and on
our way to exiting anyway.
However, if we want to guarantee a graceful shutdown, we must still be safe and consider the slim possibility that the
user wants to exit before we are done receiving all the data from the server. This is why we are doing this here. This
possibility can be much larger in applications that have longer conversations (or applications that stay connected until
the user exits, like in AHKsock Example 3).
See the section "NOTES ON CLOSING SOCKETS AND AHKsock_Close" in the documentation for more information on performing
graceful shutdown.
*/
AHKsock_Close()
ExitApp
;We're not actually handling errors here. This is here just to make us aware of errors if any do come up.
AHKsockErrors(iError, iSocket) {
OutputDebug, % "Client - Error " iError " with error code = " ErrorLevel ((iSocket <> -1) ? " on socket " iSocket : "")
}
Code: Select all
/* By RazorHalo
RemoteObj_AHKsock Client Example - Using Remote Objects with AHKsock
Version 1.0 - August 8, 2020
*** Required Libraries ***
AHKsock.ahk by TheGood
https://autohotkey.com/board/topic/53827-ahksock-a-simple-ahk-implementation-of-winsock-tcpip/page-1
Jxon.ahk by Coco
https://www.autohotkey.com/boards/viewtopic.php?t=627
*/
#NoEnv
SetBatchLines, -1
#Include AHKsock.ahk
#Include Jxon.ahk
#Include RemoteObj_AHKsock.ahk
;Register OnExit subroutine so that AHKsock_Close is called before exit
OnExit, CloseAHKsock
;Add menu item for exiting gracefully (see comment block in CloseAHKsock)
Menu, Tray, Add
Menu, Tray, Add, Exit Gracefully, CloseAHKsock
;Set up an error handler (this is optional)
AHKsock_ErrorHandler("AHKsockErrors")
;Create an instance of the RemoteObject Client
;Parameters (IP of RemoteObject Server, Port)
Remote := new RemoteObjClient("localhost", 27015)
;Add any remote objects from the server script
Remote.Add("MyTest")
Remote.Add("MyClass")
;Access the remote objects by referencing RemoteObject.ObjectToModify
Remote.MyTest.Title := "Hello World!"
Remote.MyTest.Answer := Remote.MyTest.InputBox("What is your favorite colour?")
Remote.MyClass.AddButton(Remote.MyClass.Index++ ". Run Notepad", "Run", "notepad")
Remote.MyClass.AddButton(Remote.MyClass.Index++ ". Show Answer", "MsgBox", Remote.MyTest.Answer)
CloseAHKsock:
/*! If the user selects the "Exit" menu item from the tray menu, this sub will execute once, i.e. as the OnExit sub. In
this situation, if we're still connected to the server, we will have no way of gracefully shutting down the connection.
But if the user selects the "Exit Gracefully" menu item that we added at startup, this sub will execute twice: once as
the label of the menu item, and once more right after as the OnExit sub (since ExitApp is called at the end of the sub).
Therefore, during the first execution of those two, since the thread will be interruptible, calling AHKsock_Close will
gracefully shutdown the connection. Note that the second call to AHKsock_Close during the OnExit sub then becomes
useless by redundancy, but harmless (so there's no need to ensure that it is only called during the first of the two
executions).
If this application had a GUI, we could instead execute a graceful shutdown on the GuiClose event, as done in other
AHKsock examples. Here, we have to rely on the tray menu because it is the only way for the user to exit while still
being able to gracefully shutdown.
Note however that in this example, the server shuts down the client as soon as it is done sending data to it (see the
server's SEND event of the Send() function). Therefore, the conversation between server and client is very short. This
means that when the user decides to exit the application, chances are, we are no longer connected to the server, and on
our way to exiting anyway.
However, if we want to guarantee a graceful shutdown, we must still be safe and consider the slim possibility that the
user wants to exit before we are done receiving all the data from the server. This is why we are doing this here. This
possibility can be much larger in applications that have longer conversations (or applications that stay connected until
the user exits, like in AHKsock Example 3).
See the section "NOTES ON CLOSING SOCKETS AND AHKsock_Close" in the documentation for more information on performing
graceful shutdown.
*/
AHKsock_Close()
ExitApp
;We're not actually handling errors here. This is here just to make us aware of errors if any do come up.
AHKsockErrors(iError, iSocket) {
OutputDebug, % "Client - Error " iError " with error code = " ErrorLevel ((iSocket <> -1) ? " on socket " iSocket : "")
}