Ok, it was tricky, but ...
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(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 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
If *Parameter\ProgramWorkingDirectory$ = ""
*Parameter\ProgramWorkingDirectory$ = GetPathPart(*Parameter\Program$)
EndIf
*Parameter\ProgramID = RunProgram(*Parameter\Program$, *Parameter\ProgramParameter$, *Parameter\ProgramWorkingDirectory$, #PB_Program_Open|#PB_Program_Read|#PB_Program_Write|#PB_Program_Error|*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
Debug *Parameter\StdOut$
PostEvent(#ExternalProgram_Event_StdOut)
WaitSemaphore(*Parameter\Semaphore)
EndIf
Wend
; Debug 11
; Error$ = ReadProgramError(*Parameter\ProgramID, StringMode)
; Debug 111
; If Len(Error$)
; Debug Error$
; ShowMemoryViewer(@Error$, StringByteLength(Error$, #PB_Unicode))
; *Parameter\StdErr$ = Error$
; PostEvent(#ExternalProgram_Event_Error)
; WaitSemaphore(*Parameter\Semaphore)
; Debug "Err End"
; EndIf
If TryLockMutex(*Parameter\Mutex)
;Debug 2
If Len(*Parameter\StdIn$)
WriteLen = PokeS(*Buffer, *Parameter\StdIn$, -1, StringMode|#PB_String_NoZero)
*Parameter\StdIn$ = ""
UnlockMutex(*Parameter\Mutex)
SignalSemaphore(*Parameter\Semaphore)
WriteLen = WriteProgramData(*Parameter\ProgramID, *Buffer, WriteLen)
Debug 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\ProgramParameter$ = "/C " + #DQUOTE$ + "c:\Program Files\Python310\python.exe" + #DQUOTE$ + " -i 2>&1"
ThreadParameter\ProgramReadWriteMode = #PB_Program_UTF8
ThreadParameter\ProgramExit$ = "exit()"
;ThreadParameter\CodePageConverter = @ExternalProgram_CP850ToUnicode()
ThreadParameter\Semaphore = CreateSemaphore()
ThreadParameter\Mutex = CreateMutex()
ThreadParameter\Thread = CreateThread(@ExternalProgram_Thread(), @ThreadParameter)
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) + #CRLF$
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
The main problem was, that ReadProgramError() hangs also until a CRLF is at the end.
python uses stderr as output.
So I need to redirect stderr to stdout.
Also the -i parameter for python was needed to make the output 'unbuffered'.