Handle an external program via stdin/out/error

Share your advanced PureBasic knowledge/code with the community.
THCM
Enthusiast
Enthusiast
Posts: 276
Joined: Fri Apr 25, 2003 5:06 pm
Location: Gummersbach - Germany
Contact:

Re: Handle an external program via stdin/out/error

Post 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?
The Human Code Machine / Masters' Design Group
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Handle an external program via stdin/out/error

Post by infratec »

You have to check if the command prompt is 'printed' and then you can use

Code: Select all

echo %errorlevel%
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.
THCM
Enthusiast
Enthusiast
Posts: 276
Joined: Fri Apr 25, 2003 5:06 pm
Location: Gummersbach - Germany
Contact:

Re: Handle an external program via stdin/out/error

Post 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
The Human Code Machine / Masters' Design Group
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Handle an external program via stdin/out/error

Post by Kwai chang caine »

Hello at all

I use this nice code of INFRATEC, and it work perfectly 8)
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 :oops:
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
ImageThe happiness is a road...
Not a destination
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Handle an external program via stdin/out/error

Post 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!
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Handle an external program via stdin/out/error

Post by Kwai chang caine »

Hello INFRATEC :D

Thanks for your example, i try it immediately and say to you the result 8)
Last edited by Kwai chang caine on Sun Jan 09, 2022 9:11 pm, edited 1 time in total.
ImageThe happiness is a road...
Not a destination
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Handle an external program via stdin/out/error

Post by Kwai chang caine »

That works very nice !!! :D
Thanks a lot MASTER for your like usualy golden help 8)

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 ???
ImageThe happiness is a road...
Not a destination
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Handle an external program via stdin/out/error

Post by infratec »

Why not using a List() and send the next if the answer arrived.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Handle an external program via stdin/out/error

Post 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)
Last edited by infratec on Mon Jan 10, 2022 2:28 pm, edited 3 times in total.
Joris
Addict
Addict
Posts: 885
Joined: Fri Oct 16, 2009 10:12 am
Location: BE

Re: Handle an external program via stdin/out/error

Post 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.
Yeah I know, but keep in mind ... Leonardo da Vinci was also an autodidact.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Handle an external program via stdin/out/error

Post 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.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Handle an external program via stdin/out/error

Post by Kwai chang caine »

Your new code with List() works perfectly too 8)
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() ? :oops:
Surely not need to increment an index of array, you have right it's more simple :idea:

Your code is smart, you send the new order only if the answer is received
The line

Code: Select all

If Right(Help$, 1) = ">"
is an additional security, because i have see that works without her :wink:

Again thanks for your merveillous help 8)
ImageThe happiness is a road...
Not a destination
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Handle an external program via stdin/out/error

Post 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.
Joris
Addict
Addict
Posts: 885
Joined: Fri Oct 16, 2009 10:12 am
Location: BE

Re: Handle an external program via stdin/out/error

Post 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...
Yeah I know, but keep in mind ... Leonardo da Vinci was also an autodidact.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Handle an external program via stdin/out/error

Post 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 8) , faster you die (French expression) :lol:
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 ? :oops:
ImageThe happiness is a road...
Not a destination
Post Reply