Inter-process communication

BlitzMax Forums/BlitzMax Beginners Area/Inter-process communication

It has come time for me to get into inter-process communication. I want to have one program, "parent", that is managing a bunch of others running as seperate "child" processes. From the freeprocess module, as I understand it, I can read and write to a child process by attaching a TProcess to it and treating the TProcess like a stream. My question is how does one prepare the child program to be communicated with in the first place? As far as I can tell, I am supposed to open a TPipeStream in the child and read from/write to that, and if another program is listening/writing to the pipe then that is how communication takes place. Is this correct?

TProcess is not attached to the child process ... it actually is the child process or better executes it and takes care that it isn't not just freed again.

From the host, you use TPipeStream to communicate (TProcess holds one in process.pipe ...)
In the child you use StdInStream / StdOutStream

Okay, thanks for the clarification. One other thing, if I may; I noticed in FreeProcess that there is a std pipe and an err pipe to communicate through. On the child side how do you direct which pipe to write to? If I have an error in the child program then I definitely want to be able to control the stream message out so it goes to err pipe not std pipe, for example.

Never checked the error pipe. Perhaps that are system signals (you would need to check that) which BM does not allow you to send. You would need to write some own functionality for this purpose.

Or you are right in the way that it is possible to create a TPipeStream in the client without having a valid TProcess to use those 2 streams ...
So far I've always used the StdIn and Out so I can't say, sorry.

Alrighty, after some messing around I have arrived at this, but can someone tell me where I am screwing it up? Messages should be exchanged through pipes and appear in the complimentary text fields upon entry.

Child exe

SuperStrict

Framework pub.freeprocess
Import brl.standardio
Import brl.win32maxgui
Import brl.eventqueue

Local labtext1:String = "Enter a message here :"
Local labtext2:String = "Message from parent :"
Local window:TGadget = CreateWindow("Child Process", 310, 100, 300, 55, Null, 1|32)
ActivateGadget(window)
Local canvas:TGadget = CreateCanvas(0, 0, GadgetWidth(window), GadgetHeight(window), window)
Local sg1:TGadget = CreateTextField(130, 5, GadgetWidth(window) - 135, 16, canvas)
Local clab1:TGadget = CreateLabel(labtext1, 5, 5, Len(labtext1)*16, 16, canvas)
Local sg2:TGadget = CreateTextField(130, 30, GadgetWidth(window) - 135, 16, canvas)
Local clab2:TGadget = CreateLabel(labtext2, 5, 30, Len(labtext2)*16, 16, canvas)
Local button:TGadget = CreateButton("", 0, 0, 0, 0, canvas ,BUTTON_OK)
HideGadget button

Local instrm:Int, outstrm:Int
Global pipe:TPipeStream = TPipeStream.Create(instrm, outstrm)

Repeat
PollEvent()
	Select EventID()
		Case EVENT_GADGETACTION
			Select EventSource()
				Case button
					If TextFieldText(sg1) <> "" Then
					WriteStdIO(TextFieldText(sg1))
					SetGadgetText(sg1,"")
					End If
					ActivateGadget(sg1)
			End Select
	End Select

Local incoming:String = ReadStdIO()
If incoming <> "" Then SetGadgetText(sg2, incoming)

Until EventID() = EVENT_WINDOWCLOSE And EventSource() = window
End

Function WriteStdIO(text:String)
	pipe.WriteLine(text)
End Function

Function ReadStdIO:String()
	Local inc:String = pipe.ReadLine()
	If inc Then Return inc Else Return ""
End Function


Parent exe

SuperStrict

Framework pub.freeprocess
Import brl.standardio
Import brl.win32maxgui
Import brl.eventqueue

Local labtext1:String = "Enter a message here :"
Local labtext2:String = "Message from child:"
Local window:TGadget = CreateWindow("Parent Process", 0, 100, 300, 55, Null, 1|32)
ActivateGadget(window)
Local canvas:TGadget = CreateCanvas(0, 0, GadgetWidth(window), GadgetHeight(window), window)
Local sg1:TGadget = CreateTextField(130, 5, GadgetWidth(window) - 135, 16, canvas)
Local clab1:TGadget = CreateLabel(labtext1, 5, 5, Len(labtext1)*16, 16, canvas)
Local sg2:TGadget = CreateTextField(130, 30, GadgetWidth(window) - 135, 16, canvas)
Local clab2:TGadget = CreateLabel(labtext2, 5, 30, Len(labtext2)*16, 16, canvas)
Local button:TGadget = CreateButton("", 0, 0, 0, 0, canvas ,BUTTON_OK)
HideGadget button

Local instrm:Int, outstrm:Int
Global process:TProcess = CreateProcess("childtest", 0)

Repeat
PollEvent()
	Select EventID()
		Case EVENT_GADGETACTION
			Select EventSource()
				Case button
					If TextFieldText(sg1) <> "" Then
					WriteStdIO(TextFieldText(sg1))
					SetGadgetText(sg1,"")
					End If
					ActivateGadget(sg1)
			End Select
	End Select

Local incoming:String = ReadStdIO()
If incoming <> "" Then SetGadgetText(sg2, incoming)

Until EventID() = EVENT_WINDOWCLOSE And EventSource() = window
terminateprocess(process)
End

Function WriteStdIO(text:String)
	process.pipe.WriteLine(text)
End Function

Function ReadStdIO:String()
	Local inc:String = process.pipe.ReadLine()
	If inc Then Return inc Else Return ""
End Function


Obviously the child.exe has to be compiled first and then wait in the same folder as the parent.exe so parent.exe will be able to find and run it.

I'm playing with inter-process comunication and I'm blocked with ReadStdIN() function.

Have a look at the following example (master+client)
Master
Import maxgui.drivers
Local window:tgadget , buttonstart:tgadget , buttonStop:tgadget,buttonSend:tgadget
window = CreateWindow("Test Master" , 100 , 100 , 300 , 200) 
buttonStart = CreateButton("Start" , 20 , 20 , 70 , 30 , window) 
buttonStop = CreateButton("Stop" , 20 , 60 , 70 , 30 , window)
buttonSend = CreateButton("Send" , 20 , 100 , 70 , 30 , window)
DisableGadget buttonStop
DisableGadget buttonSend
Local pro:tprocess
Local listeN:Int=0

While True
	WaitEvent
	
	If listen = 1
		If pro
			rec$ = pro.pipe.ReadLine() 
			If rec 
				Print "Received: ["+ rec+"]"
				
				If Instr(rec,"EXIT")>0
					Print "Terminate..."
					pro.terminate() 
					pro = Null
					End
				End If
				
			End If 
			rec = ""
		End If
	
	End If
		
	Select EventID() 
		
		Case event_gadgetAction
			Select EventSource() 
			
				Case buttonSend
					If pro
						pro.pipe.WriteLine "Hallo"						End If
					
				Case buttonStart
					pro=CreateProcess("Proc_child2.debug.exe") 
					DisableGadget buttonStart
					EnableGadget buttonStop
					EnableGadget buttonSend
					listen=1
				
				Case buttonStop
					DisableGadget buttonStop
					EnableGadget buttonStart
					
					If pro
						pro.pipe.WriteLine "EXIT"	
					End If
			End Select
	
		Case EVENT_WINDOWCLOSE
			End
	End Select
Wend
End


Client 1 (proc_client) - this work perfectly

Local msg$ 
While Not AppTerminate() 
msg$ = ReadStdin()  

If msg 
	Notify "MESSAGE:[" + msg+"]"
	
	Print "RECEIVED"
	If msg = "EXIT" Print "EXIT"
	msg="" 
End If
Wend
End


client 2 (proc_client with MaxGUI)

Import maxgui.drivers

Local window:tgadget , buttonstart:tgadget , buttonStop:tgadget , buttonSend:tgadget
Local txt:tgadget
Local msg$

window = CreateWindow("Test Client" , 100 , 100 , 300 , 200) 
buttonStop = CreateButton("Stop" , 20 , 20 , 70 , 30 , window)
buttonSend = CreateButton("Send" , 20 , 60 , 70 , 30 , window) 
txt = CreateTextField(20 , 110 , 100 , 20 , window) 

CreateTimer 5

While True	
	WaitEvent
	Select EventID()
	
		Case event_timerTick
			msg$ = ReadStdin() 
		
			If msg 
				Notify "MESSAGE:" + msg
				
				Print "RECEIVED"
				
				If msg = "EXIT"
						Print "EXIT"
						Exit
						End If
				msg="" 
			End If
		

		
		Case event_gadgetAction
			Select EventSource() 
			
				Case buttonSend

					Print TextFieldText(txt)
					
				Case buttonStop
					Print "EXIT"	
				
			End Select
			
	
	
		Case EVENT_WINDOWCLOSE
			Print "EXIT"
			End
	End Select
Wend
End


The problem is in the second client that uses MaxGUI - I think ReadStdIN() locks the program (and so MaxGUI processes) to wait some inputs: is there a workaround?

degac, when trying out your examples, for me both clients block at attempting ReadStdin(). I found an explanation about the blocking here: Link

That behaviour is exactly why I want to use non-blocking pipes instead...

you should use plain text to comunicate processes. Just use readline and writeline. convert everything to strings. Standard IO streams work in a 4096 kbytes buffer and has a sort of interruption on CR characters. Also, I think non blocking pipes requiere multiple threading...

ok. thanks, I will try.

You can send update values to the client(s).

master.bmx
SuperStrict

Framework pub.freeprocess

Import brl.eventqueue
Import maxgui.drivers
Import brl.timer

Local Wnd_Main:TGadget = CreateWindow("Host", 100, 100, 300, 250, Null, WINDOW_TITLEBAR | WINDOW_STATUS) 

Local Btn_Start:TGadget = CreateButton("Start", 20, 20, 70, 30, Wnd_Main) 
Local Btn_Stop:TGadget = CreateButton("Stop", 20, 60, 70, 30, Wnd_Main) 
	DisableGadget Btn_Stop
	
Local Btn_Send:TGadget = CreateButton("Send", 20, 100, 70, 30, Wnd_Main) 
	DisableGadget Btn_Send
	
Local Txf_SendMessage:TGadget = CreateTextField(20, 140, 200, 20, Wnd_Main) 

Local proc:TProcess, Listen:Int = 0, procmsg:String

CreateTimer 60
Repeat
	Select WaitEvent() 
		Case EVENT_TIMERTICK
			If Listen = 1
				If proc.Status() And proc.pipe.ReadAvail() 
				
				 procmsg = proc.pipe.ReadLine().Replace(Chr(13), "").Replace(Chr(0), "") 
					
					If procmsg
					   SetStatusText Wnd_Main, CurrentTime() + "[" + procmsg + "]"
						
						If procmsg = "EXIT"
							Print "Terminate..."
							
							proc.Terminate() 
							proc = Null
						   End
						End If
						
					End If
					
				End If
				
			   proc.pipe.WriteLine("~~") 
			   
			End If
			
		Case EVENT_GADGETACTION
			Select EventSource() 
			
				Case Btn_Send
				 If proc Then proc.pipe.WriteLine(TextFieldText(Txf_SendMessage)) 
					
				Case Btn_Start
				  proc = CreateProcess("proc.exe") 
					DisableGadget Btn_Start
					EnableGadget Btn_Stop
					EnableGadget Btn_Send
					SetStatusText Wnd_Main, "Process Started"
					
				   Listen = 1
				
				Case Btn_Stop
					DisableGadget Btn_Send
					DisableGadget Btn_Stop
					EnableGadget Btn_Start
					SetStatusText Wnd_Main, "Process Stopped"
					
					Listen = 0
					proc.Terminate() 
					proc = Null
					
			End Select
			
		Case EVENT_WINDOWCLOSE
			End
			
	End Select
	
Forever
End


proc.bmx
SuperStrict

Framework brl.standardio

Import brl.eventqueue
Import maxgui.drivers
import brl.timer

Local Wnd_Main:TGadget = CreateWindow("Client", 400, 100, 300, 250, Null, WINDOW_TITLEBAR | WINDOW_STATUS) 

Local Btn_Stop:TGadget = CreateButton("Stop", 20, 20, 70, 30, Wnd_Main) 
Local Btn_Send:TGadget = CreateButton("Send", 20, 60, 70, 30, Wnd_Main) 

Local Txf_SendMessage:TGadget = CreateTextField(20, 140, 200, 20, Wnd_Main)  

CreateTimer 60
Repeat
	Select WaitEvent() 
		Case EVENT_TIMERTICK
		  Local stdmsg:String = StandardIOStream.ReadLine() 'ReadStdin() 
			
			If stdmsg
				If stdmsg <> "~~"
					SetStatusText Wnd_Main, CurrentTime() + "[" + stdmsg + "]"
					
					'Print "RECEIVED"
				EndIf
				
				If stdmsg = "EXIT"
					Print "EXIT"
					
				   Exit
				End If
				
			End If
			
		Case EVENT_GADGETACTION
			Select EventSource() 
				Case Btn_Send
					Print TextFieldText(Txf_SendMessage) 
					
				Case Btn_Stop
					Print "EXIT"	
				
			End Select
			
		Case EVENT_WINDOWCLOSE
			Print "EXIT"
		   End
			
	End Select
	
Forever
End