Remote Objects for AHKsock

Post your working scripts, libraries and tools
User avatar
RazorHalo
Posts: 34
Joined: 21 Dec 2015, 21:23

Remote Objects for AHKsock

08 Aug 2020, 21:23

Remote Objects for AHKsock
(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!"
	}
}
Example - SERVER

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 : "")
}
Example - CLIENT

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 : "")
}

Return to “Scripts and Functions”

Who is online

Users browsing this forum: Bing [Bot], Byran Alan and 24 guests