![]() |
ExtA small library for organizing interprocess communication (IPC) between software components implemented in different programming languages. The main purpose of Ext is to provide the ability to call functions implemented in individual executable processes with minimal infrastructure requirements and without the need for network protocols or complex IPC mechanisms. |
The solution is based on the concept of dividing the application functionality into client and server processes, where the client initiates the execution of operations, and the server processes requests and returns results. Communication between processes is carried out through two text files used as channels for transmitting commands and responses. Messages are transmitted in JSON format.
The main process (we will call it the client process) starts, when necessary, another process (the server process) and establishes a connection with it, which is carried out through two text files. The client can call functions in the server process and get the results.
- The client process is the main application that runs and manages the server process.
- A server process is an autonomous executable module that implements certain functionality (for example, data processing, integration with external libraries, etc.).
- If necessary, the client starts the server (if it is not active) and establishes a communication channel with it.
Interaction can be carried out both synchronously and asynchronously
For a better understanding of the Ext concept, let's recall one of the similar solutions — UCI (Universal Chess Interface), a protocol for the interaction of chess GUIs with chess engines. Here
- The graphical interface (client) runs the chess engine (server) as a separate process.
- The exchange takes place through standard input/output streams (stdin/stdout), where the GUI transmits commands (position, go, stop), and the engine returns moves and analysis.
- The protocol is text-based, simple, and does not require a network infrastructure.
This concept can be useful in the following cases::
- if it is difficult or impossible to implement some functionality in the language you are writing in the main (client) application. For example, using Python libraries (Pillow, NumPy) from an application on Harbour;
- if you want to separate some functionality into a another process, so as not to "burden" the main program;
- to allocate resource-intensive operations. Transfer of big data processing, report generation, and work with graphics, etc. in a separate process;
- if you want to use some functions in several applications, the server process can be used with any of them;
- When migrating legacy systems. Gradual replacement of components without a complete redesign of the architecture.
Usage examples
Simplest client programs:
in Harbour
FUNCTION Main
LOCAL hExt
// LOCAL cExe := "python test_srv_1.py" /* if the server process is implemented in Python */
LOCAL cExe := "test_srv_1.exe"
// Run the server process
hExt := ecli_run( cExe )
IF Empty( hExt )
? "Can't launch " + cExe
RETURN Nil
ENDIF
/* Send a command to server process to launch "fu1" function with given parameters
and print the result */
? ecli_RunFunc( hExt, "fu1",{"test","10"} )
?
RETURN Nil
in Python
import sys
sys.path.append('../source/python')
from extcli import ecli_Run, ecli_RunFunc, ecli_Close
def main():
hExt = None
exe_name = "test_srv_1.exe"
# Run the server process
hExt = ecli_Run(exe_name)
if hExt == None:
print(f"Can't launch {exe_name}")
input("Press any key to continue...")
return None
# Send a command to server process to launch "fu1" function with given parameters
# and print the result
print( ecli_RunFunc(hExt, "fu1", ("test","10")) )
return None
if __name__ == "__main__":
main()
in Go
package main
import (
"fmt"
ecli "tests/go/extcli"
)
func main() {
var exeName = "test_srv_1.exe"
// Run the server process
hExt := ecli.Init(exeName, 1, "", "")
if hExt == nil {
fmt.Println( "Can't launch program" )
return
}
// Send a command to server process to launch "fu1" function with given parameters
// and print the result
fmt.Println( ecli.RunFunc( hExt, "fu1", []string{"test","10"}, false ) )
ecli.Close(hExt)
}
Appropriate server programs:
in Harbour
FUNCTION Main( ... )
LOCAL hExt
// At first, we call esrv_Init() to initialize the connection with a calling process
hExt := esrv_Init( hb_arrayToParams( hb_aParams() ) )
IF Empty( hExt )
RETURN Nil
ENDIF
// Now we wait for commands from caling process
esrv_Wait( hExt )
RETURN Nil
// A function to be called from client
FUNCTION FU1( aParams )
RETURN "fu1: " + Time() + " " + aParams[1] + " " + aParams[2]
in Python
import sys
sys.path.append('../source/python')
from extsrv import esrv_Init, esrv_Wait
# A function to be called from client
def fu1( aparams ):
return "fu1: " + aparams[0] + " " + aparams[1]
def main( aparams ):
# At first, we call esrv_Init() to initialize the connection with a calling process
hExt = esrv_Init(aparams)
if hExt == None:
return None
# Now we wait for commands from caling process
esrv_Wait(hExt)
return None
if __name__ == "__main__":
main( sys.argv[1:] )
in Go
package main
import "os"
import esrv "tests/go/extsrv"
func main() {
var sParam = ""
// Registration of functions, which may be launched on request of a calling process
esrv.RegFunc("fu1", fu1)
// We need to create a string from arguments to pass it to esrv.Init()
for i := 1; i < len(os.Args); i++ {
sParam = sParam + "\n" + os.Args[i]
}
// Init() function to initialize connection with calling process
if esrv.Init(sParam) != 0 {
esrv.WriteLog( "Init failed" )
return
}
// Now we wait for commands from caling process
esrv.Wait()
// And, finally, we need to close connection - close handles, etc.
esrv.Exit()
}
// A function to be called from client
func fu1(p []string) string {
return "fu1 is launched " + p[0] + " " + p[1]
}
Documentation
Detailed instructions and code examples are available in project repository.
Please also pay attention to the article in the notes.
Your name:
Email address:
(not be shown publicly)
|   |
