Page 3 of 4
Re: Handle an external program via stdin/out/error
Posted: Fri Dec 25, 2020 9:14 pm
by THCM
Hi infratec,
nice code example, but how can I check in the window event loop if a program called from cmd has ended and exited with error code: 0?
Re: Handle an external program via stdin/out/error
Posted: Fri Dec 25, 2020 10:25 pm
by infratec
You have to check if the command prompt is 'printed' and then you can use
To find out the exitcode of the program.
Since you run cmd, this is needed, because else you get only the exit code of cmd.exe
Or you can write a batch file which includes the echo command.
Then you can also use cmd /c to execute it.
Re: Handle an external program via stdin/out/error
Posted: Sun Dec 27, 2020 6:37 pm
by THCM
Thx a lot for your help! Using your various code examples and hints I got my java tool running from a hidden cmd console now. Here's an excerpt:
Code: Select all
DOS = RunProgram(ComSpec.s,"","",#PB_Program_Open | #PB_Program_Read | #PB_Program_Write | #PB_Program_Error )
If DOS
Repeat
Delay(10)
ReadLen = AvailableProgramOutput(DOS)
Debug ReadLen
Until ReadLen
If ReadLen > MemorySize(*Buffer)
*Buffer = ReAllocateMemory(*Buffer, ReadLen, #PB_Memory_NoClear)
EndIf
ReadLen = ReadProgramData(DOS, *Buffer, ReadLen)
Output.s = ExternalProgram_CP850ToUnicode(*Buffer, ReadLen)
Output.s = RemoveString(Output.s, #CR$)
Output.s = RTrim(Output.s, #LF$)
i = CountString(Output.s, #LF$)
Prompt.s=StringField(Output.s, i + 1, #LF$)
Debug "Prompt: " + Prompt.s
StartTime.q = ElapsedMilliseconds()
WriteProgramStringN(DOS, JavaParameter.s)
ExitCode.i=0
Output.s=""
Repeat
ReadLen = AvailableProgramOutput(DOS)
If ReadLen > MemorySize(*Buffer)
*Buffer = ReAllocateMemory(*Buffer, ReadLen, #PB_Memory_NoClear)
EndIf
If ReadLen
ReadLen = ReadProgramData(DOS, *Buffer, ReadLen)
Output.s = Output.s + ExternalProgram_CP850ToUnicode(*Buffer, ReadLen)
If FindString(Output.s, Prompt.s)
ExitCode.i = #True
EndIf
EndIf
Until ExitCode.i
Re: Handle an external program via stdin/out/error
Posted: Sun Jan 09, 2022 7:19 pm
by Kwai chang caine
Hello at all
I use this nice code of INFRATEC, and it work perfectly
So i try now to send automaticaly a list of commands named "TabloCommandes()"
When the console have answering, the next command is sended
I understand nothing to SEMAPHORE and i have not found how i can test if the SEMAPHORE is locked or not

So i had the idea to adding a timer, but apparently there are when even a problem of synchronisation
If someone can help me
Have a good day
Code: Select all
CompilerIf Not #PB_Compiler_Thread
MessageRequester("Info", "Enable Thread-Safe in compiler options!")
End
CompilerEndIf
Enumeration
#Form0
#EditorCommande
#StringCommande
#BoutonLancer
#BoutonEffacer
#Timer
EndEnumeration
Enumeration #PB_Event_FirstCustomValue
#Own_Event_FromProg
#Own_Event_Exit
EndEnumeration
Structure ThreadParameterStructure
Semaphore.i
Thread.i
ProgramID.i
FromProg$
ToProg$
Exit.i
EndStructure
Global Dim TabloCommandes.s(10)
xx+1:TabloCommandes(xx) = "HELP"
xx+1:TabloCommandes(xx) = "DIR"
xx+1:TabloCommandes(xx) = "CD D:\"
xx+1:TabloCommandes(xx) = "D:"
xx+1:TabloCommandes(xx) = "DIR"
ReDim TabloCommandes(xx)
Procedure HandleIO(*ThreadParameter.ThreadParameterStructure)
Protected ReadLen.i, WriteLen.i, Error$
*Buffer = AllocateMemory(10000)
If *Buffer
*ThreadParameter\ProgramID = RunProgram("cmd.exe", "", "", #PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|#PB_Program_UTF8|#PB_Program_Hide)
Repeat
If *ThreadParameter\ProgramID
ReadLen = AvailableProgramOutput(*ThreadParameter\ProgramID)
If ReadLen
ReadLen = ReadProgramData(*ThreadParameter\ProgramID, *Buffer, ReadLen)
If ReadLen
*ThreadParameter\FromProg$ = PeekS(*Buffer, ReadLen, #PB_UTF8)
PostEvent(#Own_Event_FromProg)
WaitSemaphore(*ThreadParameter\Semaphore)
EndIf
EndIf
Error$ = ReadProgramError(*ThreadParameter\ProgramID, #PB_UTF8)
If Len(Error$)
*ThreadParameter\FromProg$ = Error$
PostEvent(#Own_Event_FromProg)
WaitSemaphore(*ThreadParameter\Semaphore)
EndIf
If Len(*ThreadParameter\ToProg$)
*ThreadParameter\ToProg$ + #CRLF$
WriteLen = PokeS(*Buffer, *ThreadParameter\ToProg$, -1, #PB_UTF8)
*ThreadParameter\ToProg$ = ""
WriteLen = WriteProgramData(*ThreadParameter\ProgramID, *Buffer, WriteLen)
SignalSemaphore(*ThreadParameter\Semaphore)
EndIf
Delay(10)
FillMemory(*Buffer, 10000)
EndIf
Until *ThreadParameter\Exit ;Or Not ProgramRunning(*ThreadParameter\ProgramID)
CloseProgram(*ThreadParameter\ProgramID)
FreeMemory(*Buffer)
EndIf
PostEvent(#Own_Event_Exit)
EndProcedure
Define Event.i, Exit.i
Define ThreadParameter.ThreadParameterStructure
OpenWindow(#Form0, 379, 176, 608, 542, "Remote console", #PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_TitleBar)
EditorGadget(#EditorCommande, 5, 15, 593, 447)
StringGadget(#StringCommande, 6, 469, 594, 23, "")
ButtonGadget(#BoutonLancer, 11, 503, 201, 33, "Envoyer commande")
ButtonGadget(#BoutonEffacer, 211, 503, 201, 33, "Effacer")
AddWindowTimer(#Form0, #Timer, 1000)
ThreadParameter\Semaphore = CreateSemaphore()
ThreadParameter\Thread = CreateThread(@HandleIO(), @ThreadParameter)
Repeat
Event = WaitWindowEvent()
Select Event
Case #Own_Event_FromProg
AddGadgetItem(#EditorCommande, -1, ThreadParameter\FromProg$)
SignalSemaphore(ThreadParameter\Semaphore)
Case #Own_Event_Exit
Exit = #True
Case #PB_Event_Timer
If EventTimer() = #Timer
If LigneCommande <= ArraySize(TabloCommandes())
LigneCommande + 1
ThreadParameter\ToProg$ = TabloCommandes(LigneCommande)
WaitSemaphore(ThreadParameter\Semaphore)
EndIf
EndIf
Case #PB_Event_Gadget
Select EventGadget()
Case #BoutonLancer
LigneCommande = 0
Case #BoutonEffacer
SetGadgetText(#EditorCommande, "")
;
EndSelect
Case #PB_Event_CloseWindow
If IsThread(ThreadParameter\Thread)
CloseProgram(ThreadParameter\ProgramID)
ThreadParameter\ProgramID = 0
EndIf
FreeSemaphore(ThreadParameter\Semaphore)
Exit = #True
EndSelect
Until Exit
Re: Handle an external program via stdin/out/error
Posted: Sun Jan 09, 2022 8:21 pm
by infratec
You need an additional Mutex.
Code: Select all
If LigneCommande <= ArraySize(TabloCommandes())
LigneCommande + 1
ThreadParameter\ToProg$ = TabloCommandes(LigneCommande)
WaitSemaphore(ThreadParameter\Semaphore)
EndIf
Is not the right way, because you try to access this variable inside of the thread.
So you need to block the concurrent access.
Code: Select all
If EventTimer() = #Timer
If TryLockMutex(ThreadParameter\Mutex)
If LigneCommande <= ArraySize(TabloCommandes())
LigneCommande + 1
ThreadParameter\ToProg$ = TabloCommandes(LigneCommande)
UnlockMutex(ThreadParameter\Mutex)
EndIf
Else
; try it again and don't bölock the main loop
PostEvent(#PB_Event_Timer, #Form0, #Timer)
EndIf
EndIf
And
Code: Select all
If TryLockMutex(*ThreadParameter\Mutex)
If Len(*ThreadParameter\ToProg$)
*ThreadParameter\ToProg$ + #CRLF$
WriteLen = PokeS(*Buffer, *ThreadParameter\ToProg$, -1, #PB_UTF8)
*ThreadParameter\ToProg$ = ""
UnlockMutex(*ThreadParameter\Mutex)
WriteLen = WriteProgramData(*ThreadParameter\ProgramID, *Buffer, WriteLen)
SignalSemaphore(*ThreadParameter\Semaphore)
Else
UnlockMutex(*ThreadParameter\Mutex)
EndIf
EndIf
Not tested!
Re: Handle an external program via stdin/out/error
Posted: Sun Jan 09, 2022 8:36 pm
by Kwai chang caine
Hello INFRATEC
Thanks for your example, i try it immediately and say to you the result

Re: Handle an external program via stdin/out/error
Posted: Sun Jan 09, 2022 9:10 pm
by Kwai chang caine
That works very nice !!!
Thanks a lot MASTER for your like usualy golden help
The timer is the best solution for send commands one by one automatically, or it's possible and perhaps even more simple to do that only with the mutex, Semaphore, etc ???
Re: Handle an external program via stdin/out/error
Posted: Sun Jan 09, 2022 9:41 pm
by infratec
Why not using a List() and send the next if the answer arrived.
Re: Handle an external program via stdin/out/error
Posted: Mon Jan 10, 2022 9:25 am
by infratec
Since you had some bugs inside, the list version wa not running as it should.
Here is a working version:
Code: Select all
CompilerIf Not #PB_Compiler_Thread
MessageRequester("Info", "Enable Thread-Safe in compiler options!")
End
CompilerEndIf
EnableExplicit
Enumeration
#Form0
#EditorCommande
#StringCommande
#BoutonLancer
#BoutonEffacer
#Timer
EndEnumeration
Enumeration #PB_Event_FirstCustomValue
#Own_Event_FromProg
#Own_Event_Exit
EndEnumeration
Structure ThreadParameterStructure
Mutex.i
Semaphore.i
Thread.i
ProgramID.i
FromProg$
ToProg$
Exit.i
EndStructure
Procedure HandleIO(*ThreadParameter.ThreadParameterStructure)
Protected ReadLen.i, WriteLen.i, Error$
Protected *Buffer
*Buffer = AllocateMemory(10000)
If *Buffer
*ThreadParameter\ProgramID = RunProgram("cmd.exe", "", "", #PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|#PB_Program_UTF8|#PB_Program_Hide)
If *ThreadParameter\ProgramID
Repeat
ReadLen = AvailableProgramOutput(*ThreadParameter\ProgramID)
If ReadLen
ReadLen = ReadProgramData(*ThreadParameter\ProgramID, *Buffer, ReadLen)
If ReadLen
;Debug ReadLen
*ThreadParameter\FromProg$ = PeekS(*Buffer, ReadLen, #PB_UTF8|#PB_ByteLength)
PostEvent(#Own_Event_FromProg)
WaitSemaphore(*ThreadParameter\Semaphore)
Debug "FromProg printed"
EndIf
EndIf
Error$ = ReadProgramError(*ThreadParameter\ProgramID, #PB_UTF8)
If Len(Error$)
*ThreadParameter\FromProg$ = Error$
PostEvent(#Own_Event_FromProg)
WaitSemaphore(*ThreadParameter\Semaphore)
Debug "Error printed"
EndIf
If TryLockMutex(*ThreadParameter\Mutex)
If Len(*ThreadParameter\ToProg$)
Debug "ToProg found: " + *ThreadParameter\ToProg$
*ThreadParameter\ToProg$ + #CRLF$
WriteLen = PokeS(*Buffer, *ThreadParameter\ToProg$, -1, #PB_UTF8)
*ThreadParameter\ToProg$ = ""
SignalSemaphore(*ThreadParameter\Semaphore)
UnlockMutex(*ThreadParameter\Mutex)
WriteLen = WriteProgramData(*ThreadParameter\ProgramID, *Buffer, WriteLen)
Else
UnlockMutex(*ThreadParameter\Mutex)
EndIf
EndIf
Delay(10)
Until *ThreadParameter\Exit
CloseProgram(*ThreadParameter\ProgramID)
EndIf
FreeMemory(*Buffer)
EndIf
PostEvent(#Own_Event_Exit)
EndProcedure
Define.i Event, Exit
Define Help$
Define ThreadParameter.ThreadParameterStructure
NewList TabloCommandeList.s()
OpenWindow(#Form0, 379, 176, 608, 542, "Remote console", #PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_TitleBar)
EditorGadget(#EditorCommande, 5, 15, 593, 447)
StringGadget(#StringCommande, 6, 469, 594, 23, "")
ButtonGadget(#BoutonLancer, 11, 503, 201, 33, "Envoyer commande")
ButtonGadget(#BoutonEffacer, 211, 503, 201, 33, "Effacer")
AddElement(TabloCommandeList())
TabloCommandeList() = "HELP"
AddElement(TabloCommandeList())
TabloCommandeList() = "DIR"
AddElement(TabloCommandeList())
TabloCommandeList() = "CD H:\"
AddElement(TabloCommandeList())
TabloCommandeList() = "H:"
AddElement(TabloCommandeList())
TabloCommandeList() = "DIR"
ResetList(TabloCommandeList())
ThreadParameter\Mutex = CreateMutex()
ThreadParameter\Semaphore = CreateSemaphore()
ThreadParameter\Thread = CreateThread(@HandleIO(), @ThreadParameter)
Repeat
Event = WaitWindowEvent()
Select Event
Case #Own_Event_FromProg
Help$ = ThreadParameter\FromProg$
SignalSemaphore(ThreadParameter\Semaphore)
AddGadgetItem(#EditorCommande, -1, Help$)
If Right(Help$, 1) = ">"
Debug "prompt found"
If NextElement(TabloCommandeList())
LockMutex(ThreadParameter\Mutex)
ThreadParameter\ToProg$ = TabloCommandeList()
UnlockMutex(ThreadParameter\Mutex)
WaitSemaphore(ThreadParameter\Semaphore)
EndIf
EndIf
Case #Own_Event_Exit
Exit = #True
Case #PB_Event_Gadget
Select EventGadget()
Case #BoutonLancer
LastElement(TabloCommandeList())
Case #BoutonEffacer
SetGadgetText(#EditorCommande, "")
EndSelect
Case #PB_Event_CloseWindow
Exit = #True
EndSelect
Until Exit
If IsThread(ThreadParameter\Thread)
ThreadParameter\Exit = #True
If WaitThread(ThreadParameter\Thread, 3000) = 0
KillThread(ThreadParameter\Thread) ; should never happen
EndIf
EndIf
FreeSemaphore(ThreadParameter\Semaphore)
FreeMutex(ThreadParameter\Mutex)
The main fault was a missing #PB_ByteLength:
Code: Select all
*ThreadParameter\FromProg$ = PeekS(*Buffer, ReadLen, #PB_UTF8|#PB_ByteLength)
Re: Handle an external program via stdin/out/error
Posted: Mon Jan 10, 2022 12:09 pm
by Joris
This topic title really appeals to me, but I have no idea how to work with this source or what can be done with it.
Can someone give a simple outline of how to use this code ?
Thanks.
Re: Handle an external program via stdin/out/error
Posted: Mon Jan 10, 2022 2:41 pm
by infratec
In general it should allow to receive and send data to a console program.
In the code of the first topic is also an example how to use it. (Inside of the CompilerIf)
It uses cmd.exe as console program and it is able to receive and send data to it.
Also a codepage translation is included.
Re: Handle an external program via stdin/out/error
Posted: Mon Jan 10, 2022 4:07 pm
by Kwai chang caine
Your new code with List() works perfectly too
infratec wrote: Sun Jan 09, 2022 9:41 pm
Why not using a List() and send the next if the answer arrived.
I don't understand what is the benefit to use a list() rather than an array() ?

Surely not need to increment an index of array, you have right it's more simple
Your code is smart, you send the new order only if the answer is received
The line
is an additional security, because i have see that works without her
Again thanks for your merveillous help

Re: Handle an external program via stdin/out/error
Posted: Mon Jan 10, 2022 11:58 pm
by infratec
No, it is not an additional security.
The timer is removed.
So you need to know when you can send the next command.
You can send a command if the prompt is shown.
With this version you don't loose any time if the list is executed.
Re: Handle an external program via stdin/out/error
Posted: Tue Jan 11, 2022 9:32 am
by Joris
infratec wrote: Mon Jan 10, 2022 2:41 pm
In general it should allow to receive and send data to a console program.
...
infratec thank you.
Just tried it under Win-XP and it works with that included example. Nice.
As the terminal is also a console program (I suppose, yeah my level of coding... low)... yeah, I remember you didn't recommend it (on using alsa library), but now I'm just curious...
Re: Handle an external program via stdin/out/error
Posted: Wed Jan 12, 2022 4:38 pm
by Kwai chang caine
infratec wrote: Mon Jan 10, 2022 11:58 pm
With this version you don't loose any time if the list is executed.
It's exactely what i want

, faster you die (French expression)
infratec wrote: Mon Jan 10, 2022 11:58 pm
No, it is not an additional security.
The timer is removed.
So you need to know when you can send the next command.
You can send a command if the prompt is shown.
Ok but is it impossible the answer writing a ">" the loop see it at a certain time and this is not the prompt ?
