Handle an external program via stdin/out/error

Share your advanced PureBasic knowledge/code with the community.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Handle an external program via stdin/out/error

Post by infratec »

Hi,

since KCC had some problems and he want to use the solution as a 'standard',
I rewrote it a bit.

Save it as ExternalProgram.pbi

Code: Select all

;
; ExternalProgram.pbi
;
; https://www.purebasic.fr/english/viewtopic.php?t=64876
;
; V 1.01
;
; - added a list of commands to send
; - fixed the output, command should be printed behind the prompt and not below
;
; V 1.00
;
; by infratec
;

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf

CompilerIf Not #PB_Compiler_Thread
  MessageRequester("Info", "Enable Thread-Safe in compiler options!")
  End
CompilerEndIf

CompilerIf Not Defined(Event_CustomValue, #PB_Enumeration)
  
  Enumeration Event_CustomValue #PB_Event_FirstCustomValue
    #ExternalProgram_Event_StdOut
    #ExternalProgram_Event_Error
    #ExternalProgram_Event_Exit
  EndEnumeration
  
CompilerElse
  
  Enumeration Event_CustomValue
    #ExternalProgram_Event_StdOut
    #ExternalProgram_Event_Error
    #ExternalProgram_Event_Exit
  EndEnumeration
  
CompilerEndIf

Prototype.s ExternalProgram_CPxxxToUnicode(*Buffer, Length.i)

Structure ExternalProgram_ParameterStructure
  Program$
  ProgramParameter$
  ProgramWorkingDirectory$
  ProgramReadWriteMode.i
  ProgramExit$
  Semaphore.i
  Mutex.i
  Thread.i
  ProgramID.i
  StdOut$
  StdIn$
  StdErr$
  CodePageConverter.ExternalProgram_CPxxxToUnicode
  Exit.i
EndStructure




Procedure.s ExternalProgram_CP437ToUnicode(*Buffer, Length.i)
  
  DataSection
    ExternalProgram_CP437Data:
    Data.u $00C7, $00FC, $00E9, $00E2, $00E4, $00E0, $00E5, $00E7
    Data.u $00EA, $00EB, $00E8, $00EF, $00EE, $00EC, $00C4, $00C5
    Data.u $00C9, $00E6, $00C6, $00F4, $00F6, $00F2, $00FB, $00F9
    Data.u $00FF, $00D6, $00DC, $00A2, $00A3, $00A5, $20A7, $0192
    Data.u $00E1, $00ED, $00F3, $00FA, $00F1, $00D1, $00AA, $00BA
    Data.u $00BF, $2310, $00AC, $00BD, $00BC, $00A1, $00AB, $00BB
    Data.u $2591, $2592, $2593, $2502, $2524, $2561, $2562, $2556
    Data.u $2555, $2563, $2551, $2557, $255D, $255C, $255B, $2510
    Data.u $2514, $2534, $252C, $251C, $2500, $253C, $255E, $255F
    Data.u $255A, $2554, $2569, $2566, $2560, $2550, $256C, $2567
    Data.u $2568, $2564, $2565, $2559, $2558, $2552, $2553, $256B
    Data.u $256A, $2518, $250C, $2588, $2584, $258C, $2590, $2580
    Data.u $03B1, $00DF, $0393, $03C0, $03A3, $03C3, $00B5, $03C4
    Data.u $03A6, $0398, $03A9, $03B4, $221E, $03C6, $03B5, $2229
    Data.u $2261, $00B1, $2265, $2264, $2320, $2321, $00F7, $2248
    Data.u $00B0, $2219, $00B7, $221A, $207F, $00B2, $25A0, $00A0
  EndDataSection
  
  Protected Result$, i.i, Byte.a
  
  
  Length - 1
  
  For i = 0 To Length
    Byte = PeekA(*Buffer + i)
    If Byte < $80
      Result$ + Chr(Byte)
    Else
      Result$ + Chr(PeekU(?ExternalProgram_CP437Data + (Byte & $7F) << 1))
    EndIf
  Next i
  
  ProcedureReturn Result$
  
EndProcedure




Procedure.s ExternalProgram_CP850ToUnicode(*Buffer, Length.i)
  
  DataSection
    ExternalProgram_CP850Data:
    Data.u $00c7, $00fc, $00e9, $00e2, $00e4, $00e0, $00e5, $00e7
    Data.u $00ea, $00eb, $00e8, $00ef, $00ee, $00ec, $00c4, $00c5
    Data.u $00c9, $00e6, $00c6, $00f4, $00f6, $00f2, $00fb, $00f9
    Data.u $00ff, $00d6, $00dc, $00f8, $00a3, $00d8, $00d7, $0192
    Data.u $00e1, $00ed, $00f3, $00fa, $00f1, $00d1, $00aa, $00ba
    Data.u $00bf, $00ae, $00ac, $00bd, $00bc, $00a1, $00ab, $00bb
    Data.u $2591, $2592, $2593, $2502, $2524, $00c1, $00c2, $00c0
    Data.u $00a9, $2563, $2551, $2557, $255d, $00a2, $00a5, $2510
    Data.u $2514, $2534, $252c, $251c, $2500, $253c, $00e3, $00c3
    Data.u $255a, $2554, $2569, $2566, $2560, $2550, $256c, $00a4
    Data.u $00f0, $00d0, $00ca, $00cb, $00c8, $0131, $00cd, $00ce
    Data.u $00cf, $2518, $250c, $2588, $2584, $00a6, $00cc, $2580
    Data.u $00d3, $00df, $00d4, $00d2, $00f5, $00d5, $00b5, $00fe
    Data.u $00de, $00da, $00db, $00d9, $00fd, $00dd, $00af, $00b4
    Data.u $00ad, $00b1, $2017, $00be, $00b6, $00a7, $00f7, $00b8
    Data.u $00b0, $00a8, $00b7, $00b9, $00b3, $00b2, $25a0, $00a0
  EndDataSection
  
  Protected Result$, i.i, Byte.a
  
  
  Length - 1
  
  For i = 0 To Length
    Byte = PeekA(*Buffer + i)
    If Byte < $80
      Result$ + Chr(Byte)
    Else
      Result$ + Chr(PeekU(?ExternalProgram_CP850Data + (Byte & $7F) << 1))
    EndIf
  Next i
  
  ProcedureReturn Result$
  
EndProcedure



Procedure.s ExternalProgram_CP852ToUnicode(*Buffer, Length.i)
  
  DataSection
    ExternalProgram_CP852Data:
    Data.u $00C7, $00FC, $00E9, $00E2, $00E4, $016F, $0107, $00E7
    Data.u $0142, $00EB, $0150, $0151, $00EE, $0179, $00C4, $0106
    Data.u $00C9, $0139, $013A, $00F4, $00F6, $013D, $013E, $015A
    Data.u $015B, $00D6, $00DC, $0164, $0165, $0141, $00D7, $010D
    Data.u $00E1, $00ED, $00F3, $00FA, $0104, $0105, $017D, $017E
    Data.u $0118, $0119, $00AC, $017A, $010C, $015F, $00AB, $00BB
    Data.u $2591, $2592, $2593, $2502, $2524, $00C1, $00C2, $011A
    Data.u $015E, $2563, $2551, $2557, $255D, $017B, $017C, $2510
    Data.u $2514, $2534, $252C, $251C, $2500, $253C, $0102, $0103
    Data.u $255A, $2554, $2569, $2566, $2560, $2550, $256C, $00A4
    Data.u $0111, $0110, $010E, $00CB, $010F, $0147, $00CD, $00CE
    Data.u $011B, $2518, $250C, $2588, $2584, $0162, $016E, $2580
    Data.u $00D3, $00DF, $00D4, $0143, $0144, $0148, $0160, $0161
    Data.u $0154, $00DA, $0155, $0170, $00FD, $00DD, $0163, $00B4
    Data.u $00AD, $02DD, $02DB, $02C7, $02D8, $00A7, $00F7, $00B8
    Data.u $00B0, $00A8, $02D9, $0171, $0158, $0159, $25A0, $00A0
  EndDataSection
  
  Protected Result$, i.i, Byte.a
  
  
  Length - 1
  
  For i = 0 To Length
    Byte = PeekA(*Buffer + i)
    If Byte < $80
      Result$ + Chr(Byte)
    Else
      Result$ + Chr(PeekU(?ExternalProgram_CP852Data + (Byte & $7F) << 1))
    EndIf
  Next i
  
  ProcedureReturn Result$
  
EndProcedure




Procedure.s ExternalProgram_CP863ToUnicode(*Buffer, Length.i)
  
  DataSection
    ExternalProgram_CP863Data:
    Data.u $00c7, $00fc, $00e9, $00e2, $00c2, $00e0, $00b6, $00e7
    Data.u $00ea, $00eb, $00e8, $00ef, $00ee, $2017, $00c0, $00a7
    Data.u $00c9, $00c8, $00ca, $00f4, $00cb, $00cf, $00fb, $00f9
    Data.u $00a4, $00d4, $00dc, $00a2, $00a3, $00d9, $00db, $0192
    Data.u $00a6, $00b4, $00f3, $00fa, $00a8, $00b8, $00b3, $00af
    Data.u $00ce, $2310, $00ac, $00bd, $00bc, $00be, $00ab, $00bb
    Data.u $2591, $2592, $2593, $2502, $2524, $2561, $2562, $2556
    Data.u $2555, $2563, $2551, $2557, $255d, $255c, $255b, $2510
    Data.u $2514, $2534, $252c, $251c, $2500, $253c, $255e, $255f
    Data.u $255a, $2554, $2569, $2566, $2560, $2550, $256c, $2567
    Data.u $2568, $2564, $2565, $2559, $2558, $2552, $2553, $256b
    Data.u $256a, $2518, $250c, $2588, $2584, $258c, $2590, $2580
    Data.u $03b1, $00df, $0393, $03c0, $03a3, $03c3, $00b5, $03c4
    Data.u $03a6, $0398, $03a9, $03b4, $221e, $03c6, $03b5, $2229
    Data.u $2261, $00b1, $2265, $2264, $2320, $2321, $00f7, $2248
    Data.u $00b0, $2219, $00b7, $221a, $207f, $00b2, $25a0, $00a0
  EndDataSection
  
  Protected Result$, i.i, Byte.a
  
  
  Length - 1
  
  For i = 0 To Length
    Byte = PeekA(*Buffer + i)
    If Byte < $80
      Result$ + Chr(Byte)
    Else
      Result$ + Chr(PeekU(?ExternalProgram_CP863Data + (Byte & $7F) << 1))
    EndIf
  Next i
  
  ProcedureReturn Result$
  
EndProcedure




Procedure.s ExternalProgram_CP1252ToUnicode(*Buffer, Length.i)
  
  DataSection
    ExternalProgram_CP1252Data:
    Data.u $20AC, $0000, $201A, $0192, $201E, $2026, $2020, $2021
    Data.u $02C6, $2030, $0160, $2039, $0152, $0000, $017D, $0000
    Data.u $0000, $2018, $2019, $201C, $201D, $2022, $2013, $2014
    Data.u $02DC, $2122, $0161, $203A, $0153, $0000, $017E, $0178
  EndDataSection
  
  Protected Result$, i.i, Byte.a
  
  
  Length - 1
  
  For i = 0 To Length
    Byte = PeekA(*Buffer + i)
    If Byte < $80 Or Byte > $9F
      Result$ + Chr(Byte)
    Else
      Result$ + Chr(PeekU(?ExternalProgram_CP1252Data + (Byte & $7F) << 1))
    EndIf
  Next i
  
  ProcedureReturn Result$
  
EndProcedure




Procedure ExternalProgram_Thread(*Parameter.ExternalProgram_ParameterStructure)
  
  Protected *Buffer, ReadLen.i, WriteLen.i, Error$, Timeout.i, StringMode.i, PeekStringMode.i
  
  *Buffer = AllocateMemory(1024, #PB_Memory_NoClear)
  
  If *Buffer
    
    Select *Parameter\ProgramReadWriteMode
      Case #PB_Program_Ascii
        StringMode = #PB_Ascii
        PeekStringMode = #PB_Ascii
      Case #PB_Program_Unicode
        StringMode = #PB_Unicode
        PeekStringMode = #PB_Unicode
      Case #PB_Program_UTF8
        StringMode = #PB_UTF8
        PeekStringMode = #PB_UTF8|#PB_ByteLength
    EndSelect
    
    *Parameter\ProgramID = RunProgram(*Parameter\Program$, *Parameter\ProgramParameter$, *Parameter\ProgramWorkingDirectory$, #PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|#PB_Program_Hide|*Parameter\ProgramReadWriteMode)
    
    If *Parameter\ProgramID
      
      Repeat
        
        While AvailableProgramOutput(*Parameter\ProgramID)
          ReadLen = AvailableProgramOutput(*Parameter\ProgramID)
          
          If ReadLen > MemorySize(*Buffer)
            *Buffer = ReAllocateMemory(*Buffer, ReadLen, #PB_Memory_NoClear)
          EndIf
          
          ReadLen = ReadProgramData(*Parameter\ProgramID, *Buffer, ReadLen)
          If ReadLen
            If *Parameter\CodePageConverter
              *Parameter\StdOut$ = *Parameter\CodePageConverter(*Buffer, ReadLen)
            Else
              *Parameter\StdOut$ = PeekS(*Buffer, ReadLen, PeekStringMode)
            EndIf
            PostEvent(#ExternalProgram_Event_StdOut)
            WaitSemaphore(*Parameter\Semaphore)
          EndIf
        Wend
        
        Error$ = ReadProgramError(*Parameter\ProgramID, StringMode)
        If Len(Error$)
          *Parameter\StdErr$ = Error$
          PostEvent(#ExternalProgram_Event_Error)
          WaitSemaphore(*Parameter\Semaphore)
        EndIf
        
        If TryLockMutex(*Parameter\Mutex)
          If Len(*Parameter\StdIn$)
            *Parameter\StdIn$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\StdIn$, -1, StringMode|#PB_String_NoZero)
            *Parameter\StdIn$ = ""
            SignalSemaphore(*Parameter\Semaphore)
            UnlockMutex(*Parameter\Mutex)
            WriteLen = WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
            Delay(50)
          Else
            UnlockMutex(*Parameter\Mutex)
          EndIf
        EndIf
        
        Delay(10)
        
      Until *Parameter\Exit Or Not ProgramRunning(*Parameter\ProgramID)
      
      If *Parameter\Exit
        If ProgramRunning(*Parameter\ProgramID)
          If Len(*Parameter\ProgramExit$)
            *Parameter\ProgramExit$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\ProgramExit$, -1, StringMode|#PB_String_NoZero)
            WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
          Else
            WriteProgramData(*Parameter\ProgramID, #PB_Program_Eof, 0)
          EndIf
          Timeout = 300
          Repeat
            Delay(10)
            Timeout - 1
            While AvailableProgramOutput(*Parameter\ProgramID)
              ReadProgramData(*Parameter\ProgramID, *Buffer, 1)
            Wend
          Until Timeout = 0 Or Not ProgramRunning(*Parameter\ProgramID)
          If Timeout = 0
            Debug "Kill"
            KillProgram(*Parameter\ProgramID)
          EndIf
        EndIf
      EndIf
      
      CloseProgram(*Parameter\ProgramID)
      
    EndIf
    
    FreeMemory(*Buffer)
  EndIf
  
  PostEvent(#ExternalProgram_Event_Exit)
  
EndProcedure




;- Example
CompilerIf #PB_Compiler_IsMainFile
  
  Enumeration
    #MainWindow
    #EditorGadget
    #StringGadget
    #ButtonGadget
  EndEnumeration
  
  Define.i Event, Exit, i, j, LastLine
  Define Help$
  Define ThreadParameter.ExternalProgram_ParameterStructure
  NewList CommandList$()
  
  LoadFont(0, "Consolas", 10)
  
  OpenWindow(#MainWindow, 0, 0, 600, 500, "Remote console", #PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)
  WindowBounds(#MainWindow, 600, 500, #PB_Ignore, #PB_Ignore)
  EditorGadget(#EditorGadget, 10, 10, 580, 410)
  SetGadgetFont(#EditorGadget, FontID(0))
  StringGadget(#StringGadget, 10, 430, 580, 20, "")
  ButtonGadget(#ButtonGadget, 200, 460, 200, 30, "Send to console")
  
  ThreadParameter\Program$ = "cmd.exe"
  ThreadParameter\ProgramReadWriteMode = #PB_Program_Ascii
  ThreadParameter\ProgramExit$ = "exit"
  ThreadParameter\CodePageConverter = @ExternalProgram_CP850ToUnicode()
  ThreadParameter\Semaphore = CreateSemaphore()
  ThreadParameter\Mutex = CreateMutex()
  ThreadParameter\Thread = CreateThread(@ExternalProgram_Thread(), @ThreadParameter)
  
  AddElement(CommandList$())
  CommandList$() = "help"
  
  AddElement(CommandList$())
  CommandList$() = "dir"
  
  ResetList(CommandList$())
  
  Repeat
    
    Event = WaitWindowEvent()
    
    Select Event
      Case #ExternalProgram_Event_StdOut
        Help$ = ThreadParameter\StdOut$
        SignalSemaphore(ThreadParameter\Semaphore)
        
        If Right(Help$, 1) = ">"
          If NextElement(CommandList$())
            LockMutex(ThreadParameter\Mutex)
            ThreadParameter\StdIn$ = CommandList$()
            UnlockMutex(ThreadParameter\Mutex)
            WaitSemaphore(ThreadParameter\Semaphore)
          EndIf
        EndIf
        
        Help$ = RemoveString(Help$, #CR$)
        Help$ = RTrim(Help$, #LF$)
                
        i = CountString(Help$, #LF$)
        For j = 0 To i
          If j = 0
            LastLine = CountGadgetItems(#EditorGadget) - 1
            SetGadgetItemText(#EditorGadget, LastLine, GetGadgetItemText(#EditorGadget, LastLine) + StringField(Help$, j + 1, #LF$))
          Else
            AddGadgetItem(#EditorGadget, -1, StringField(Help$, j + 1, #LF$))
          EndIf
        Next j
        
        
      Case #ExternalProgram_Event_Error
        Debug ThreadParameter\StdErr$
        SignalSemaphore(ThreadParameter\Semaphore)
        
      Case #ExternalProgram_Event_Exit
        Exit = #True
        
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #ButtonGadget
            LockMutex(ThreadParameter\Mutex)
            ThreadParameter\StdIn$ = GetGadgetText(#StringGadget)
            UnlockMutex(ThreadParameter\Mutex)
            WaitSemaphore(ThreadParameter\Semaphore)
        EndSelect
        
      Case #PB_Event_SizeWindow
        ResizeGadget(#ButtonGadget, WindowWidth(#MainWindow) / 2 - 100, WindowHeight(#MainWindow) - 40, #PB_Ignore, #PB_Ignore)
        ResizeGadget(#StringGadget, #PB_Ignore, WindowHeight(#MainWindow) - 70, WindowWidth(#MainWindow) - 20, #PB_Ignore)
        ResizeGadget(#EditorGadget, #PB_Ignore, #PB_Ignore, WindowWidth(#MainWindow) - 20, WindowHeight(#MainWindow) - 90)
        
      Case #PB_Event_CloseWindow
        If IsThread(ThreadParameter\Thread)
          ThreadParameter\Exit = #True
          WaitThread(ThreadParameter\Thread)
        EndIf
        FreeMutex(ThreadParameter\Mutex)
        FreeSemaphore(ThreadParameter\Semaphore)
        Exit = #True
    EndSelect
    
  Until Exit
  
CompilerEndIf
I hope it is 'universal' enough to handle all possibilities.
Maybe it is useful for others too.

Bernd
Last edited by infratec on Mon Jan 10, 2022 2:34 pm, edited 13 times in total.
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

I have tried your splendid code, and it works very well, for Phantom, but also with CMD.

Your code is very usefull, for remote apparently all console applications by PB
I have searched the same in all the forum and not found a full remote console (read/write) for all exe, just remote of CMD

Again thanks 8)
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 »

Hello INFRATEC

I need another time your code, and i see now, it works only if the lengh of the return of console is not too long :|

Then, this time, i have another time try with console, but also with ADB of androidSdk and i have just a part of return
I have also try to modify the two delay(), that change a little the lengh of return, but it's nor perfect :(

Have you an idea for fix that ?

Code: Select all

CompilerIf Not #PB_Compiler_Thread
  MessageRequester("Info", "Enable Thread-Safe in compiler options!")
  End
CompilerEndIf

Enumeration #PB_Event_FirstCustomValue
  #ExternalProgram_Event_StdOut
  #ExternalProgram_Event_Error
  #ExternalProgram_Event_Exit
EndEnumeration

Structure ExternalProgramParameterStructure
  Program$
  ProgramParameter$
  ProgramWorkingDirectory$
  ProgramReadWriteMode.i
  ProgramExit$
  Semaphore.i
  Mutex.i
  Thread.i
  ProgramID.i
  StdOut$
  StdIn$
  StdErr$
  Exit.i
EndStructure

Procedure ExternalProgramThread(*Parameter.ExternalProgramParameterStructure)
 
  Protected *Buffer, ReadLen.i, WriteLen.i, Error$, Timeout.i
 
  *Buffer = AllocateMemory(1024)
 
  If *Buffer
   
    *Parameter\ProgramID = RunProgram(*Parameter\Program$, *Parameter\ProgramParameter$, GetPathPart(*Parameter\Program$), #PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|#PB_Program_Hide|*Parameter\ProgramReadWriteMode)
   
    If *Parameter\ProgramID
     
      Repeat
       
        ReadLen = AvailableProgramOutput(*Parameter\ProgramID)
        If ReadLen
          ReadLen = ReadProgramData(*Parameter\ProgramID, *Buffer, ReadLen)
          If ReadLen
            *Parameter\StdOut$ = PeekS(*Buffer, ReadLen, *Parameter\ProgramReadWriteMode|#PB_ByteLength)
            PostEvent(#ExternalProgram_Event_StdOut)
            WaitSemaphore(*Parameter\Semaphore)
          EndIf
        EndIf
       
        Error$ = ReadProgramError(*Parameter\ProgramID, *Parameter\ProgramReadWriteMode)
        If Len(Error$)
          *Parameter\StdErr$ = Error$
          PostEvent(#ExternalProgram_Event_Error)
          WaitSemaphore(*Parameter\Semaphore)
        EndIf
       
        If TryLockMutex(*Parameter\Mutex)
          If Len(*Parameter\StdIn$)
            *Parameter\StdIn$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\StdIn$, -1, *Parameter\ProgramReadWriteMode)
            *Parameter\StdIn$ = ""
            WriteLen = WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
            SignalSemaphore(*Parameter\Semaphore)
          EndIf
          UnlockMutex(*Parameter\Mutex)
        EndIf
       
        Delay(10)
       
      Until *Parameter\Exit Or Not ProgramRunning(*Parameter\ProgramID)
     
      If *Parameter\Exit
        If ProgramRunning(*Parameter\ProgramID)
          If Len(*Parameter\ProgramExit$)
            *Parameter\ProgramExit$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\ProgramExit$, -1, *Parameter\ProgramReadWriteMode)
            WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
          Else
            WriteProgramData(*Parameter\ProgramID, #PB_Program_Eof, 0)
          EndIf
          Timeout = 300
          Repeat
            Delay(10)
            Timeout - 1
          Until Timeout = 0 Or Not ProgramRunning(*Parameter\ProgramID)
          If Timeout = 0
            Debug "Kill"
            KillProgram(*Parameter\ProgramID)
          EndIf
        EndIf
      EndIf
     
      CloseProgram(*Parameter\ProgramID)
     
    EndIf
   
    FreeMemory(*Buffer)
  EndIf
 
  PostEvent(#ExternalProgram_Event_Exit)
 
EndProcedure
  
Enumeration
 #Form0
 #EditorCommande
 #StringCommande
 #Bouton
EndEnumeration

Define Event.i, Exit.i, i.i, j.i
Define ThreadParameter.ExternalProgramParameterStructure

OpenWindow(#Form0, 379, 176, 608, 542, "Remote Console windows", #PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_TitleBar)
EditorGadget(#EditorCommande, 5, 15, 593, 447)
StringGadget(#StringCommande, 6, 469, 594, 23, "help")
ButtonGadget(#Bouton, 211, 503, 201, 33, "Send to console")

ThreadParameter\Program$ = "C:\Windows\System32\cmd.exe"
ThreadParameter\ProgramReadWriteMode = #PB_UTF8
ThreadParameter\ProgramExit$ = ""
ThreadParameter\Semaphore = CreateSemaphore()
ThreadParameter\Mutex = CreateMutex()
ThreadParameter\Thread = CreateThread(@ExternalProgramThread(), @ThreadParameter)

Repeat

 Event = WaitWindowEvent()

 Select Event
   Case #ExternalProgram_Event_StdOut
     ThreadParameter\StdOut$ = RemoveString(ThreadParameter\StdOut$, #CR$)
     ThreadParameter\StdOut$ = RTrim(ThreadParameter\StdOut$, #LF$)
     i = CountString(ThreadParameter\StdOut$, #LF$)
     For j = 0 To i
       AddGadgetItem(#EditorCommande, -1, StringField(ThreadParameter\StdOut$, j + 1, #LF$))
     Next j
     SignalSemaphore(ThreadParameter\Semaphore)
    
   Case #ExternalProgram_Event_Error
     Debug ThreadParameter\StdErr$
     SignalSemaphore(ThreadParameter\Semaphore)
    
   Case #ExternalProgram_Event_Exit
     Exit = #True
    
   Case #PB_Event_Gadget
     Select EventGadget()
       Case #Bouton
         LockMutex(ThreadParameter\Mutex)
         ThreadParameter\StdIn$ = GetGadgetText(#StringCommande) + #CRLF$
         UnlockMutex(ThreadParameter\Mutex)
         WaitSemaphore(ThreadParameter\Semaphore)
     EndSelect
    
   Case #PB_Event_CloseWindow
     If IsThread(ThreadParameter\Thread)
       ThreadParameter\Exit = #True
       WaitThread(ThreadParameter\Thread)
     EndIf
     FreeMutex(ThreadParameter\Mutex)
     FreeSemaphore(ThreadParameter\Semaphore)
     Exit = #True
 EndSelect

Until Exit
   
Have a good day
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 »

I updated the code above.
The character mode was not correct handled.
#PB_Program_UTF8 is not #PB_UTF8 ...
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 to your fix 8)
But that not works here with W10 X64 / v5.70 X86 :|

The full result of "HELP" is always not returned
And the french characters is not good returned

Perhaps your splendid code for "Phantom.exe" not works with the windows console ?

Code: Select all

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf

CompilerIf Not #PB_Compiler_Thread
  MessageRequester("Info", "Enable Thread-Safe in compiler options!")
  End
CompilerEndIf


CompilerIf Not Defined(FirstCustomValue, #PB_Enumeration)
 
  Enumeration FirstCustomValue #PB_Event_FirstCustomValue
    #ExternalProgram_Event_StdOut
    #ExternalProgram_Event_Error
    #ExternalProgram_Event_Exit
  EndEnumeration
 
CompilerElse
 
  Enumeration FirstCustomValue
    #ExternalProgram_Event_StdOut
    #ExternalProgram_Event_Error
    #ExternalProgram_Event_Exit
  EndEnumeration
 
CompilerEndIf

Structure ExternalProgramParameterStructure
  Program$
  ProgramParameter$
  ProgramWorkingDirectory$
  ProgramReadWriteMode.i
  ProgramExit$
  Semaphore.i
  Mutex.i
  Thread.i
  ProgramID.i
  StdOut$
  StdIn$
  StdErr$
  Exit.i
EndStructure

Procedure ExternalProgramThread(*Parameter.ExternalProgramParameterStructure)
 
  Protected *Buffer, ReadLen.i, WriteLen.i, Error$, Timeout.i, StringMode.i, PeekStringMode.i
 
  *Buffer = AllocateMemory(1024)
 
  If *Buffer
   
    Select *Parameter\ProgramReadWriteMode
      Case #PB_Program_Ascii
        StringMode = #PB_Ascii
        PeekStringMode = #PB_Ascii
      Case #PB_Program_Unicode
        StringMode = #PB_Unicode
        PeekStringMode = #PB_Unicode
      Case #PB_Program_UTF8
        StringMode = #PB_UTF8
        PeekStringMode = #PB_UTF8|#PB_ByteLength
    EndSelect
   
    *Parameter\ProgramID = RunProgram(*Parameter\Program$, *Parameter\ProgramParameter$, *Parameter\ProgramWorkingDirectory$, #PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|#PB_Program_Hide|*Parameter\ProgramReadWriteMode)
   
    If *Parameter\ProgramID
     
      Repeat
       
        ReadLen = AvailableProgramOutput(*Parameter\ProgramID)
        If ReadLen
          ReadLen = ReadProgramData(*Parameter\ProgramID, *Buffer, ReadLen)
          If ReadLen
            *Parameter\StdOut$ = PeekS(*Buffer, ReadLen, PeekStringMode)
            PostEvent(#ExternalProgram_Event_StdOut)
            WaitSemaphore(*Parameter\Semaphore)
          EndIf
        EndIf
       
        Error$ = ReadProgramError(*Parameter\ProgramID, StringMode)
        If Len(Error$)
          *Parameter\StdErr$ = Error$
          PostEvent(#ExternalProgram_Event_Error)
          WaitSemaphore(*Parameter\Semaphore)
        EndIf
       
        If TryLockMutex(*Parameter\Mutex)
          If Len(*Parameter\StdIn$)
            *Parameter\StdIn$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\StdIn$, -1, StringMode|#PB_String_NoZero)
            *Parameter\StdIn$ = ""
            WriteLen = WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
            SignalSemaphore(*Parameter\Semaphore)
          EndIf
          UnlockMutex(*Parameter\Mutex)
        EndIf
       
        Delay(10)
       
      Until *Parameter\Exit Or Not ProgramRunning(*Parameter\ProgramID)
     
      If *Parameter\Exit
        If ProgramRunning(*Parameter\ProgramID)
          If Len(*Parameter\ProgramExit$)
            *Parameter\ProgramExit$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\ProgramExit$, -1, StringMode|#PB_String_NoZero)
            WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
          Else
            WriteProgramData(*Parameter\ProgramID, #PB_Program_Eof, 0)
          EndIf
          Timeout = 300
          Repeat
            Delay(10)
            Timeout - 1
          Until Timeout = 0 Or Not ProgramRunning(*Parameter\ProgramID)
          If Timeout = 0
            Debug "Kill"
            KillProgram(*Parameter\ProgramID)
          EndIf
        EndIf
      EndIf
     
      CloseProgram(*Parameter\ProgramID)
     
    EndIf
   
    FreeMemory(*Buffer)
  EndIf
 
  PostEvent(#ExternalProgram_Event_Exit)
 
EndProcedure


;- Example
CompilerIf #PB_Compiler_IsMainFile
  
  Enumeration
    #Form0
    #EditorCommande
    #StringCommande
    #Bouton
  EndEnumeration
  
  Define Event.i, Exit.i, i.i, j.i
  Define ThreadParameter.ExternalProgramParameterStructure
  
  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, "help")
  ButtonGadget(#Bouton, 211, 503, 201, 33, "Send to Console")
 
  ThreadParameter\Program$ = "cmd.exe"
  ThreadParameter\ProgramReadWriteMode = #PB_Program_UTF8
  ThreadParameter\ProgramExit$ = "console.exit()"
  ThreadParameter\Semaphore = CreateSemaphore()
  ThreadParameter\Mutex = CreateMutex()
  ThreadParameter\Thread = CreateThread(@ExternalProgramThread(), @ThreadParameter)
 
  Repeat
   
    Event = WaitWindowEvent()
   
    Select Event
      Case #ExternalProgram_Event_StdOut
        ThreadParameter\StdOut$ = RemoveString(ThreadParameter\StdOut$, #CR$)
        ThreadParameter\StdOut$ = RTrim(ThreadParameter\StdOut$, #LF$)
        i = CountString(ThreadParameter\StdOut$, #LF$)
        For j = 0 To i
          AddGadgetItem(#EditorCommande, -1, StringField(ThreadParameter\StdOut$, j + 1, #LF$))
        Next j
        SignalSemaphore(ThreadParameter\Semaphore)
       
      Case #ExternalProgram_Event_Error
        Debug ThreadParameter\StdErr$
        SignalSemaphore(ThreadParameter\Semaphore)
       
      Case #ExternalProgram_Event_Exit
        Exit = #True
       
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Bouton
            LockMutex(ThreadParameter\Mutex)
            ThreadParameter\StdIn$ = GetGadgetText(#StringCommande)
            UnlockMutex(ThreadParameter\Mutex)
            WaitSemaphore(ThreadParameter\Semaphore)
        EndSelect
       
      Case #PB_Event_CloseWindow
        If IsThread(ThreadParameter\Thread)
          ThreadParameter\Exit = #True
          WaitThread(ThreadParameter\Thread)
        EndIf
        FreeMutex(ThreadParameter\Mutex)
        FreeSemaphore(ThreadParameter\Semaphore)
        Exit = #True
    EndSelect
   
  Until Exit
 
CompilerEndIf
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 »

I extended the code above.
The exit stuff was not working if the programm have transmitted data in the 'pipeline'.

I think cmd does not listen on stdin.
It only listens on key presses.

Here is an example, which shows that Inkey() does not return any values.
Only ReadConsoleData() returns stdin.

Code: Select all

OpenConsole()

PrintN("Console started")

*Buffer = AllocateMemory(1024)
If *Buffer
  
  Repeat
    
    Inkey$ = Inkey()
    If Inkey$ <> ""
      OpenFile(0, GetPathPart(ProgramFilename()) + "Console.log", #PB_File_Append)
      WriteString(0, "Inkey: " + Inkey$)
      CloseFile(0)
      In$ + Inkey$
    EndIf
    
    If ReadConsoleData(*Buffer, 1) = 1
      Inkey$ = PeekS(*Buffer, 1, #PB_UTF8)
      OpenFile(0, GetPathPart(ProgramFilename()) + "Console.log", #PB_File_Append)
      WriteString(0, "ReadConsoleData: " + Inkey$)
      CloseFile(0)
      In$ + Inkey$
    EndIf
    
    If Inkey$ = #CR$
      Print(In$)
      If In$ = "exit" + #CR$
        OpenFile(0, GetPathPart(ProgramFilename()) + "Console.log", #PB_File_Append)
        WriteStringN(0, "EXIT")
        CloseFile(0)
        Break
      EndIf
      In$ = ""
    EndIf
    
  ForEver
  
  OpenFile(0, GetPathPart(ProgramFilename()) + "Console.log", #PB_File_Append)
  WriteStringN(0, "BREAKED")
  CloseFile(0)
  
  FreeMemory(*Buffer)
EndIf

PrintN("Console terminated")

CloseConsole()
Save it as Console.pb and compile it to Console.exe.
Then call it instead of Cmd.exe.
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 :wink:

Unfortunately your new code always not works :|
See the answer of "HELP" command (Specials characters and not the full answer)
Microsoft Windows [version 10.0.17763.864]
(c) 2018 Microsoft Corporation. Tous droits r�serv�s.

D:\Temp\Lecture console v5.70 [Infratec]>
help
Pour plus d'informations sur une commande sp�cifique, entrez HELP
suivi de la commande.
ASSOC Affiche ou modifie les applications associ�es aux extensions de
fichiers.
ATTRIB Affiche ou modifie les attributs d'un fichier.
BREAK Active ou d�sactive le contr�le �tendu de CTRL+C.
BCDEDIT D�finit les propri�t�s dans la base de donn�es de d�marrage pour
le contr�le du chargement d'amor�age.
CACLS Affiche ou modifie les listes de contr�les d'acc�s aux fichiers.
CALL Appelle un fichier de commandes � partir d'un autre fichier de
commandes.
CD Modifie le r�pertoire ou affiche le r�pertoire actif.
CHCP Modifie ou affiche le num�ro de la page de code active.
CHDIR Modifie le r�pertoire ou affiche le nom du r�pertoire actif.
CHKDSK V�rifie un disque et affiche un rapport d'�tat.
CHKNTFS Affiche ou modifie la v�rification du disque au d�marrage.
CLS Efface l'�cran.
CMD Ex�cute une nouvelle instance de l'interpr�teur de commandes de
Windows.
COLOR Modifie les couleurs du premier plan et de l'arri�re-plan de la
console.
COMP Compare les contenus de deux fichiers ou groupes de fichiers.
COMPACT Modifie ou affiche la compression des fichiers sur une
partition NTFS.
CONVERT Convertit des volumes FAT en volumes NTFS. Vous ne pouvez pas
convertir le lecteur en cours d'utilisation.
COPY Copie un ou plusieurs fichiers.
DATE Affiche ou d�finit la date.
Then the full answer wit "cmd.exe" and "help" manually is
Microsoft Windows [version 10.0.17763.864]
(c) 2018 Microsoft Corporation. Tous droits réservés.

C:\>help
Pour plus d’informations sur une commande spécifique, entrez HELP
suivi de la commande.
ASSOC Affiche ou modifie les applications associées aux extensions de
fichiers.
ATTRIB Affiche ou modifie les attributs d’un fichier.
BREAK Active ou désactive le contrôle étendu de CTRL+C.
BCDEDIT Définit les propriétés dans la base de données de démarrage pour
le contrôle du chargement d’amorçage.
CACLS Affiche ou modifie les listes de contrôles d’accès aux fichiers.
CALL Appelle un fichier de commandes à partir d’un autre fichier de
commandes.
CD Modifie le répertoire ou affiche le répertoire actif.
CHCP Modifie ou affiche le numéro de la page de code active.
CHDIR Modifie le répertoire ou affiche le nom du répertoire actif.
CHKDSK Vérifie un disque et affiche un rapport d’état.
CHKNTFS Affiche ou modifie la vérification du disque au démarrage.
CLS Efface l’écran.
CMD Exécute une nouvelle instance de l’interpréteur de commandes de
Windows.
COLOR Modifie les couleurs du premier plan et de l’arrière-plan de la
console.
COMP Compare les contenus de deux fichiers ou groupes de fichiers.
COMPACT Modifie ou affiche la compression des fichiers sur une
partition NTFS.
CONVERT Convertit des volumes FAT en volumes NTFS. Vous ne pouvez pas
convertir le lecteur en cours d’utilisation.
COPY Copie un ou plusieurs fichiers.
DATE Affiche ou définit la date.
DEL Supprime un ou plusieurs fichiers.
DIR Affiche la liste des fichiers et des sous-répertoires d’un
répertoire.
DISKPART Affiche ou configure les propriétés d'une partition de disque.
DOSKEY Modifie les lignes de commande, rappelle des commandes Windows,
et crée des macros.
DRIVERQUERY Affiche l'état et les propriétés du pilote de périphérique en
cours d'utilisation.
ECHO Affiche des messages ou active/désactive l'affichage des
commandes.
ENDLOCAL Stoppe la localisation des modifications d'environnement dans
un fichier de commandes.
ERASE Supprime un ou plusieurs fichiers.
EXIT Quitte l'interpréteur de commandes (CMD.EXE).
FC Compare deux fichiers ou groupes de fichiers et affiche
les différences.
FIND Recherche une chaîne de caractères dans un ou plusieurs
fichiers.
FINDSTR Cherche des chaînes dans les fichiers.
FOR Exécute une commande sur chaque fichier d'un ensemble de
fichiers.
FORMAT Formate un disque devant être utilisé avec Windows.
FSUTIL Affiche ou configure les propriétés du système de fichiers.
FTYPE Affiche ou modifie les types de fichiers utilisés dans les
associations d'extensions.
GOTO Indique l'exécution d'un fichier de commandes pour une ligne
identifiée par une étiquette.
GPRESULT Affiche les informations de stratégie de groupe pour un
ordinateur ou un utilisateur.
GRAFTABL Permet à Windows d'afficher un jeu de caractères en
mode graphique.
HELP Affiche des informations sur les commandes de Windows.
ICACLS Afficher, modifier, sauvegarder ou restaurer les listes de
contrôle d'accès pour les fichiers et les répertoires.
IF Effectue un traitement conditionnel dans un fichier de
commandes.
LABEL Crée, modifie ou supprime le nom de volume d'un disque.
MD Crée un répertoire.
MKDIR Crée un répertoire.
MKLINK Créer des liens symboliques et des liens physiques
MODE Configure un périphérique du système.
MORE Affiche la sortie écran par écran.
MOVE Déplace un ou plusieurs fichiers d'un répertoire
à un autre.
OPENFILES Affiche les fichiers partagés ouverts à distance par les
utilisateurs.
PATH Affiche ou définit le chemin de recherche des fichiers
exécutables.
PAUSE Interrompt l'exécution d'un fichier de commandes et affiche un
message.
POPD Restaure la valeur précédente du répertoire actif enregistrée
par PUSHD.
PRINT Imprime un fichier texte.
PROMPT Modifie l'invite de commande de Windows.
PUSHD Enregistre le répertoire actif puis le modifie.
RD Supprime un répertoire.
RECOVER Récupère l'information lisible d'un disque défectueux.
REM Insère un commentaire dans un fichier de commandes ou
CONFIG.SYS.
REN Renomme un ou plusieurs fichiers.
RENAME Renomme un ou plusieurs fichiers.
REPLACE Remplace des fichiers.
RMDIR Supprime un répertoire.
ROBOCOPY Utilitaire avancé pour copier les fichiers et les
arborescences de répertoires
SET Affiche, définit ou supprime des variables d'environnement
Windows.
SETLOCAL Commence la localisation des modifications d'environnement dans
un fichier de commandes.
SC Affiche ou configure les services (processus en arrière-plan).
SCHTASKS Planifie les commandes et les programmes à exécuter sur
l'ordinateur.
SHIFT Modifie la position des paramètres remplaçables dans un fichier
de commandes.
SHUTDOWN Permet un arrêt local ou distant correct de l'ordinateur.
SORT Trie les entrées.
START Ouvre une fenêtre séparée pour l'exécution d'un programme ou
d'une commande spécifique.
SUBST Associe un chemin d'accès à une lettre de lecteur.
SYSTEMINFO Affiche les propriétés et la configuration spécifiques de
l'ordinateur.
TASKLIST Affiche toutes les tâches en cours d'exécution, y compris les
services.
TASKKILL Termine ou interrompt un processus ou une application en cours
d'exécution.
TIME Affiche ou définit l'heure du système.
TITLE Définit le titre de la fenêtre pour une session CMD.EXE.
TREE Affiche le graphisme de la structure de répertoire d'un lecteur
ou d'un chemin d'accès.
TYPE Affiche le contenu d'un fichier texte.
VER Affiche la version de Windows.
VERIFY Demande à Windows de vérifier si vos fichiers sont
correctement écrits sur le disque.
VOL Affiche le nom et le numéro de série d'un volume de disque.
XCOPY Copie les fichiers et les arborescences de répertoires.
WMIC Affiche les informations WMI dans l'interface de commande
interactive.

Pour obtenir plus d'informations sur les outils, consultez la référence de
commande en ligne dans l'aide en ligne.
In fact it's worst of that, i have see if i not run manually the "cmd.exe" and do "help" i have no answer :shock:
Then if i run "cmd.exe" and write "help" and close console, your code give the partial answer above
This is the code i use in W10 X64 / v5.70 X86

Code: Select all

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf

CompilerIf Not #PB_Compiler_Thread
  MessageRequester("Info", "Enable Thread-Safe in compiler options!")
  End
CompilerEndIf

CompilerIf Not Defined(FirstCustomValue, #PB_Enumeration)
 
  Enumeration FirstCustomValue #PB_Event_FirstCustomValue
    #ExternalProgram_Event_StdOut
    #ExternalProgram_Event_Error
    #ExternalProgram_Event_Exit
  EndEnumeration
 
CompilerElse
 
  Enumeration FirstCustomValue
    #ExternalProgram_Event_StdOut
    #ExternalProgram_Event_Error
    #ExternalProgram_Event_Exit
  EndEnumeration
 
CompilerEndIf

Structure ExternalProgramParameterStructure
  Program$
  ProgramParameter$
  ProgramWorkingDirectory$
  ProgramReadWriteMode.i
  ProgramExit$
  Semaphore.i
  Mutex.i
  Thread.i
  ProgramID.i
  StdOut$
  StdIn$
  StdErr$
  Exit.i
EndStructure

Procedure ExternalProgramThread(*Parameter.ExternalProgramParameterStructure)
 
  Protected *Buffer, ReadLen.i, WriteLen.i, Error$, Timeout.i, StringMode.i, PeekStringMode.i
 
  *Buffer = AllocateMemory(1024)
 
  If *Buffer
   
    Select *Parameter\ProgramReadWriteMode
      Case #PB_Program_Ascii
        StringMode = #PB_Ascii
        PeekStringMode = #PB_Ascii
      Case #PB_Program_Unicode
        StringMode = #PB_Unicode
        PeekStringMode = #PB_Unicode
      Case #PB_Program_UTF8
        StringMode = #PB_UTF8
        PeekStringMode = #PB_UTF8|#PB_ByteLength
    EndSelect
   
    *Parameter\ProgramID = RunProgram(*Parameter\Program$, *Parameter\ProgramParameter$, *Parameter\ProgramWorkingDirectory$, #PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|#PB_Program_Hide|*Parameter\ProgramReadWriteMode)
   
    If *Parameter\ProgramID
     
      Repeat
       
        ReadLen = AvailableProgramOutput(*Parameter\ProgramID)
        If ReadLen
          ReadLen = ReadProgramData(*Parameter\ProgramID, *Buffer, ReadLen)
          If ReadLen
            *Parameter\StdOut$ = PeekS(*Buffer, ReadLen, PeekStringMode)
            PostEvent(#ExternalProgram_Event_StdOut)
            WaitSemaphore(*Parameter\Semaphore)
          EndIf
        EndIf
       
        Error$ = ReadProgramError(*Parameter\ProgramID, StringMode)
        If Len(Error$)
          *Parameter\StdErr$ = Error$
          PostEvent(#ExternalProgram_Event_Error)
          WaitSemaphore(*Parameter\Semaphore)
        EndIf
       
        If TryLockMutex(*Parameter\Mutex)
          If Len(*Parameter\StdIn$)
            *Parameter\StdIn$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\StdIn$, -1, StringMode|#PB_String_NoZero)
            *Parameter\StdIn$ = ""
            WriteLen = WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
            SignalSemaphore(*Parameter\Semaphore)
          EndIf
          UnlockMutex(*Parameter\Mutex)
        EndIf
       
        Delay(10)
       
      Until *Parameter\Exit Or Not ProgramRunning(*Parameter\ProgramID)
     
      If *Parameter\Exit
        If ProgramRunning(*Parameter\ProgramID)
          If Len(*Parameter\ProgramExit$)
            *Parameter\ProgramExit$ + #CRLF$
            WriteLen = PokeS(*Buffer, *Parameter\ProgramExit$, -1, StringMode|#PB_String_NoZero)
            WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
          Else
            WriteProgramData(*Parameter\ProgramID, #PB_Program_Eof, 0)
          EndIf
          Timeout = 300
          Repeat
            Delay(10)
            Timeout - 1
            While AvailableProgramOutput(*Parameter\ProgramID)
              ReadProgramData(*Parameter\ProgramID, *Buffer, 1)
            Wend
          Until Timeout = 0 Or Not ProgramRunning(*Parameter\ProgramID)
          If Timeout = 0
            Debug "Kill"
            KillProgram(*Parameter\ProgramID)
          EndIf
        EndIf
      EndIf
     
      CloseProgram(*Parameter\ProgramID)
     
    EndIf
   
    FreeMemory(*Buffer)
  EndIf
 
  PostEvent(#ExternalProgram_Event_Exit)
 
EndProcedure

;- Example
CompilerIf #PB_Compiler_IsMainFile
  
  Enumeration
    #Form0
    #EditorCommande
    #StringCommande
    #Bouton
  EndEnumeration
  
  Define Event.i, Exit.i, i.i, j.i
  Define ThreadParameter.ExternalProgramParameterStructure
  
  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, "help")
  ButtonGadget(#Bouton, 211, 503, 201, 33, "Send to console")
 
  ThreadParameter\Program$ = "cmd.exe"
  ThreadParameter\ProgramReadWriteMode = #PB_Program_UTF8
  ThreadParameter\ProgramExit$ = "console.exit()"
  ThreadParameter\Semaphore = CreateSemaphore()
  ThreadParameter\Mutex = CreateMutex()
  ThreadParameter\Thread = CreateThread(@ExternalProgramThread(), @ThreadParameter)
 
  Repeat
   
    Event = WaitWindowEvent()
   
    Select Event
      Case #ExternalProgram_Event_StdOut
        ThreadParameter\StdOut$ = RemoveString(ThreadParameter\StdOut$, #CR$)
        ThreadParameter\StdOut$ = RTrim(ThreadParameter\StdOut$, #LF$)
        i = CountString(ThreadParameter\StdOut$, #LF$)
        For j = 0 To i
          AddGadgetItem(#EditorCommande, -1, StringField(ThreadParameter\StdOut$, j + 1, #LF$))
        Next j
        SignalSemaphore(ThreadParameter\Semaphore)
       
      Case #ExternalProgram_Event_Error
        Debug ThreadParameter\StdErr$
        SignalSemaphore(ThreadParameter\Semaphore)
       
      Case #ExternalProgram_Event_Exit
        Exit = #True
       
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Bouton
            LockMutex(ThreadParameter\Mutex)
            ThreadParameter\StdIn$ = GetGadgetText(#StringCommande)
            UnlockMutex(ThreadParameter\Mutex)
            WaitSemaphore(ThreadParameter\Semaphore)
        EndSelect
       
      Case #PB_Event_CloseWindow
        If IsThread(ThreadParameter\Thread)
          ThreadParameter\Exit = #True
          WaitThread(ThreadParameter\Thread)
        EndIf
        FreeMutex(ThreadParameter\Mutex)
        FreeSemaphore(ThreadParameter\Semaphore)
        Exit = #True
    EndSelect
   
  Until Exit
 
CompilerEndIf

And you have right...the close windows not works too :|
Decidedly....this "black" console have apparently "not affraid" and is very difficult to train :mrgreen:

Image

Thanks MASTER for all you try to help me 8)
Last edited by Kwai chang caine on Sun Nov 24, 2019 7:53 pm, edited 1 time in total.
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 »

What I was trying to show you with my Console.pb is:

Some programs does not react on stdin, they only react on keyboard strokes.
It looks like cmd.exe is also such a program.
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 »

Yes i have understand that, but why your code not return the full answer of "Help" command, and furthermore bad characters :| (See THREAD above viewtopic.php?p=545203#p545203) :wink:
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 »

Hi KCC,

I fixed a bug in my code.
I had a fixed buffer of 1024 bytes. This buffer was to small for the help output.
Now I changed this to be reallocatable.

Next thing in your code:

Code: Select all

ThreadParameter\ProgramExit$ = "console.exit()"
should be

Code: Select all

ThreadParameter\ProgramExit$ = "exit"
Then cmd.exe generates ascii an not utf-8

Code: Select all

ThreadParameter\ProgramReadWriteMode = #PB_Program_Ascii
Next problem:
It returns ASCII for a specific codepage.
So all codes above 127 are codespace specific and are not displayed correctly with a standard font.
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 »

Hi KCC,

I extended again :mrgreen:

Now with CP852 and CP437 converter.
I use now your cmd example, since everyone has cmd.exe on his PC :wink:
You can simpy copy the complete listing.
Marc56us
Addict
Addict
Posts: 1477
Joined: Sat Feb 08, 2014 3:26 pm

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

Post by Marc56us »

Thank you infratec,
It works very well (at least for our french accents)
:wink:
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 »

One thousand of thanks MASTER 8)
You are an angel
Image
Then this time :

1/ The full return of "help" command works perfectly 8)
2/ The close of windows too 8) 8)

3/ But there is again a little problem with the french accent (This french....never do like others :? sometime i say to me, that even Microsoft is not also complex than the french langage :mrgreen:)

See yourself :
Splendid code of master INFRATEC wrote:CALL Appelle un fichier de commandes ů partir d'un autre fichier de
commandes.
FIND Recherche une chaîne de caractŐres dans un ou plusieurs
fichiers.
MODE Configure un périphérique du systŐme.
etc .....
Must be
Cmd.exe ''help'' manual wrote:CALL Appelle un fichier de commandes à partir d’un autre fichier de
commandes.
FIND Recherche une chaîne de caractères dans un ou plusieurs
fichiers.
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 »

Marc56us meant that it is Ok.

Do you now the codepage which is used normally for french?
I thought it is also 852.
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 »

Added codepage 863 Canadian-french and 1252 Latin I
Post Reply