• TWAPI start process and get return value

    From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Tue Nov 18 15:34:44 2025
    From Newsgroup: comp.lang.tcl

    I'm using the code below to start a process and wait for the process to
    finish and get the return value.

    the script test4.tcl only contains a "puts" for the result and and
    "exit" so tht the process is ended.

    It works, but the result is written to the given output file.
    Instead I would like it to behave more like a return value, to get the
    result of the script directly.

    I tried to pass "stdout" directly to the create_process command, but it doesn't work.

    I guess there must be a more elegant solution for this.
    Any ideas?

    Many thanks
    Alex


    proc ::GlobalVarSetFromTwapiCallback {name value args} {
    set $name $value
    }

    set outfile c:/temp.txt
    set fid [open $outfile w+]

    set cmdline {C:/Tcl/bin/wish.exe C:/test4.tcl}
    set res [::twapi::create_process "" -cmdline $cmdline -returnhandles 1 -inherithandles 1 -stdchannels [list $fid $fid $fid]]
    lassign $res pid tid hproc hthread
    set ::twapi_pid($pid) ""
    ::twapi::wait_on_handle $hproc -executeonce 1 -async [list ::GlobalVarSetFromTwapiCallback ::twapi_pid($pid) ""]
    vwait ::twapi_pid($pid)
    ::twapi::close_handle $hproc
    catch {close $fid}
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ralf Fassel@ralfixx@gmx.de to comp.lang.tcl on Tue Nov 18 16:04:40 2025
    From Newsgroup: comp.lang.tcl

    * meshparts <alexandru.dadalau@meshparts.de>
    | I'm using the code below to start a process and wait for the process
    | to finish and get the return value.

    | the script test4.tcl only contains a "puts" for the result and and
    | "exit" so tht the process is ended.

    | It works, but the result is written to the given output file.
    | Instead I would like it to behave more like a return value, to get the
    | result of the script directly.

    The canonical way would be to set up a pipe via [chan pipe]

    https://www.tcl-lang.org/man/tcl/TclCmd/chan.html#M30
    https://wiki.tcl-lang.org/page/chan+pipe

    and pass the write-end as stdout to the process and read the output via
    the read-end in your process.

    HTH
    R'
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Tue Nov 18 16:25:43 2025
    From Newsgroup: comp.lang.tcl

    Am 18.11.2025 um 16:04 schrieb Ralf Fassel:
    * meshparts <alexandru.dadalau@meshparts.de>
    | I'm using the code below to start a process and wait for the process
    | to finish and get the return value.

    | the script test4.tcl only contains a "puts" for the result and and
    | "exit" so tht the process is ended.

    | It works, but the result is written to the given output file.
    | Instead I would like it to behave more like a return value, to get the
    | result of the script directly.

    The canonical way would be to set up a pipe via [chan pipe]

    https://www.tcl-lang.org/man/tcl/TclCmd/chan.html#M30
    https://wiki.tcl-lang.org/page/chan+pipe

    and pass the write-end as stdout to the process and read the output via
    the read-end in your process.

    HTH
    R'
    Okay, got it!
    So I do:

    lassign [chan pipe] readChanId writeChanId

    set res [::twapi::create_process "" -cmdline $cmdline -returnhandles
    1 -inherithandles 1 -stdchannels [list $readChanId $writeChanId
    $writeChanId]]

    set result [gets $readChanId]
    catch {close $readChanId}
    catch {close $writeChanId}

    Thanks!
    Alex
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ralf Fassel@ralfixx@gmx.de to comp.lang.tcl on Tue Nov 18 18:00:00 2025
    From Newsgroup: comp.lang.tcl

    * meshparts <alexandru.dadalau@meshparts.de>
    | Am 18.11.2025 um 16:04 schrieb Ralf Fassel:
    | > * meshparts <alexandru.dadalau@meshparts.de>
    | > | I'm using the code below to start a process and wait for the process
    | > | to finish and get the return value.
    | >>
    | > | the script test4.tcl only contains a "puts" for the result and and
    | > | "exit" so tht the process is ended.
    | >>
    | > | It works, but the result is written to the given output file.
    | > | Instead I would like it to behave more like a return value, to get the
    | > | result of the script directly.
    | > The canonical way would be to set up a pipe via [chan pipe]
    | > https://www.tcl-lang.org/man/tcl/TclCmd/chan.html#M30
    | > https://wiki.tcl-lang.org/page/chan+pipe
    | > and pass the write-end as stdout to the process and read the output
    | > via
    | > the read-end in your process.
    | > HTH
    | > R'
    | Okay, got it!
    | So I do:

    | lassign [chan pipe] readChanId writeChanId

    | set res [::twapi::create_process "" -cmdline $cmdline -returnhandles
    | 1 -inherithandles 1 -stdchannels [list $readChanId $writeChanId
    | $writeChanId]]

    Ah, no. This would connect the stdout of the process to its own stdin.

    If you want to send to the process as well, you need a second pipe:

    lassign [chan pipe] processStdin writeChanId
    lassign [chan pipe] readChanId processStdoutErr

    ::twapi::create_process ... \
    -stdchannels [list $processStdin $processStdoutErr $processStdoutErr]

    Now whatever you write to writeChanId arrives in the process at
    processStdin, and whatever the process writes to processStdoutErr,
    arrives at your readChanId.

    R'
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ralf Fassel@ralfixx@gmx.de to comp.lang.tcl on Tue Nov 18 18:03:43 2025
    From Newsgroup: comp.lang.tcl

    * Ralf Fassel <ralfixx@gmx.de>
    | If you want to send to the process as well, you need a second pipe:

    | lassign [chan pipe] processStdin writeChanId
    | lassign [chan pipe] readChanId processStdoutErr

    | ::twapi::create_process ... \
    | -stdchannels [list $processStdin $processStdoutErr $processStdoutErr]

    | Now whatever you write to writeChanId arrives in the process at
    | processStdin, and whatever the process writes to processStdoutErr,
    | arrives at your readChanId.

    But I wonder why you don't use the regular TCL [open "|"] syntax, where
    you get all this for free?

    set fd [open "| process" w+]

    # send something to the process
    puts $fd "are you there?"

    # read response
    set response [gets $fd]

    Seems so much easier...

    R'
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ralf Fassel@ralfixx@gmx.de to comp.lang.tcl on Tue Nov 18 18:05:18 2025
    From Newsgroup: comp.lang.tcl

    * Ralf Fassel <ralfixx@gmx.de>
    | * Ralf Fassel <ralfixx@gmx.de>
    | | If you want to send to the process as well, you need a second pipe:
    | >
    | | lassign [chan pipe] processStdin writeChanId
    | | lassign [chan pipe] readChanId processStdoutErr
    | >
    | | ::twapi::create_process ... \
    | | -stdchannels [list $processStdin $processStdoutErr $processStdoutErr] | >
    | | Now whatever you write to writeChanId arrives in the process at
    | | processStdin, and whatever the process writes to processStdoutErr,
    | | arrives at your readChanId.

    | But I wonder why you don't use the regular TCL [open "|"] syntax, where
    | you get all this for free?

    | set fd [open "| process" w+]

    Or even plain 'exec', since you wait for the process anyway?

    set result [exec process]

    R'
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Tue Nov 18 18:32:33 2025
    From Newsgroup: comp.lang.tcl

    Am 18.11.2025 um 18:05 schrieb Ralf Fassel:
    * Ralf Fassel <ralfixx@gmx.de>
    | * Ralf Fassel <ralfixx@gmx.de>
    | | If you want to send to the process as well, you need a second pipe:
    | >
    | | lassign [chan pipe] processStdin writeChanId
    | | lassign [chan pipe] readChanId processStdoutErr
    | >
    | | ::twapi::create_process ... \
    | | -stdchannels [list $processStdin $processStdoutErr $processStdoutErr]
    | >
    | | Now whatever you write to writeChanId arrives in the process at
    | | processStdin, and whatever the process writes to processStdoutErr,
    | | arrives at your readChanId.

    | But I wonder why you don't use the regular TCL [open "|"] syntax, where
    | you get all this for free?

    | set fd [open "| process" w+]

    Or even plain 'exec', since you wait for the process anyway?

    set result [exec process]

    R'
    At least with Tcl 8 I hat issues with using exec with paths containing
    umlaute on Windows. Twapi solved this for me. Somehow I trust more Twapi
    with proceses than Tcl built in commands.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Tue Nov 18 19:07:06 2025
    From Newsgroup: comp.lang.tcl

    Am 18.11.2025 um 18:00 schrieb Ralf Fassel:
    Ah, no. This would connect the stdout of the process to its own stdin.

    If you want to send to the process as well, you need a second pipe:

    lassign [chan pipe] processStdin writeChanId
    lassign [chan pipe] readChanId processStdoutErr

    ::twapi::create_process ... \
    -stdchannels [list $processStdin $processStdoutErr $processStdoutErr]

    Now whatever you write to writeChanId arrives in the process at
    processStdin, and whatever the process writes to processStdoutErr,
    arrives at your readChanId.

    R'

    Hm, I can't get it work this way. Either I get nothing from the "gets"
    command or it complains that the channel was not open for reading.

    I understand your point, but somehow my solution worked...


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Tue Nov 18 19:11:50 2025
    From Newsgroup: comp.lang.tcl

    Am 18.11.2025 um 19:07 schrieb meshparts:

    Hm, I can't get it work this way. Either I get nothing from the "gets" command or it complains that the channel was not open for reading.

    I understand your point, but somehow my solution worked...

    Forget that. I works! My mistake.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ralf Fassel@ralfixx@gmx.de to comp.lang.tcl on Tue Nov 18 19:46:49 2025
    From Newsgroup: comp.lang.tcl

    * meshparts <alexandru.dadalau@meshparts.de>
    | Am 18.11.2025 um 19:07 schrieb meshparts:
    | > Hm, I can't get it work this way. Either I get nothing from the
    | > "gets"
    | > command or it complains that the channel was not open for reading.
    | > I understand your point, but somehow my solution worked...

    | Forget that. I works! My mistake.

    You need to take the usual configuration of channels into account:
    - make sure to flush your output channel (or set it unbuffered) to make
    sure any output is actually sent to the process
    - you might need to close the 'unused' ends of the pipe in your process
    after creating the child process (see https://www.tcl-lang.org/man/tcl/TclCmd/chan.html#M30)
    to get correct [eof] notifications
    - make sure to avoid deadlocks if you send more data than the process
    reads by setting the pipe channels non-blocking

    R'
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Thu Nov 20 09:25:24 2025
    From Newsgroup: comp.lang.tcl

    Am 18.11.2025 um 19:46 schrieb Ralf Fassel:
    You need to take the usual configuration of channels into account:
    - make sure to flush your output channel (or set it unbuffered) to make
    sure any output is actually sent to the process
    - you might need to close the 'unused' ends of the pipe in your process
    after creating the child process (seehttps://www.tcl-lang.org/man/tcl/TclCmd/chan.html#M30)
    to get correct [eof] notifications
    - make sure to avoid deadlocks if you send more data than the process
    reads by setting the pipe channels non-blocking

    R'

    Below is the current state of the code.
    I introduced a listener, so that I can output all the messages that are written by the process.
    But somehow, when multiple lines are written, the first and last line is
    not read.
    I tries setting all channels non-blocking and getting the result after
    "chan close" but nothing seems to help.
    Any ideas?

    proc ::GeneratePartBatchRead {readChanId} {
    variable lastline
    # read the last line
    set line [gets $readChanId]
    # output to the main app
    puts $line
    # store the line for later use
    set lastline($readChanId) $line
    }

    # Open different channels for read and write
    lassign [chan pipe] processStdin writeChanId
    lassign [chan pipe] readChanId processStdoutErr
    # React when the read channel becomes readable (meaning, a new line was written to the channel)
    chan event $readChanId readable "::GeneratePartBatchRead $readChanId"
    # Start the part generation process
    set res [::twapi::create_process "" -cmdline $cmdline -returnhandles 1 -inherithandles 1 -stdchannels [list $processStdin $processStdoutErr $processStdoutErr]]
    # Store the process handles
    lassign $res pid tid hproc hthread
    # Wait for the process to end
    set ::twapi_pid($pid) ""
    ::twapi::wait_on_handle $hproc -executeonce 1 -async [list ::GlobalVarSetFromTwapiCallback ::twapi_pid($pid) ""]
    vwait ::twapi_pid($pid)
    ::twapi::close_handle $hproc
    # Get the last line written which is normally the path of the generated
    model file
    chan flush $processStdoutErr
    set result $lastline($readChanId)
    catch {close $readChanId}
    catch {close $writeChanId}
    catch {close $processStdin}
    catch {close $processStdoutErr}
    array unset lastline $readChanId


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ralf Fassel@ralfixx@gmx.de to comp.lang.tcl on Thu Nov 20 10:33:25 2025
    From Newsgroup: comp.lang.tcl

    * meshparts <alexandru.dadalau@meshparts.de>
    | Am 18.11.2025 um 19:46 schrieb Ralf Fassel:
    | > You need to take the usual configuration of channels into account:
    | > - make sure to flush your output channel (or set it unbuffered) to make
    | > sure any output is actually sent to the process
    | > - you might need to close the 'unused' ends of the pipe in your process
    | > after creating the child process (seehttps://www.tcl-lang.org/man/tcl/TclCmd/chan.html#M30)
    | > to get correct [eof] notifications
    | > - make sure to avoid deadlocks if you send more data than the process
    | > reads by setting the pipe channels non-blocking
    | > R'

    | Below is the current state of the code.
    | I introduced a listener, so that I can output all the messages that
    | are written by the process.
    | But somehow, when multiple lines are written, the first and last line
    | is not read.
    | I tries setting all channels non-blocking and getting the result after
    | "chan close" but nothing seems to help.
    | Any ideas?

    There is a potential for data race here: are you sure
    ::GeneratePartBatchRead has collected all data before you call 'close'
    on the channels (add a diagnostic to stderr to see whether it has been
    called)? I'd rather let ::GeneratePartBatchRead decide when to
    stop: if it gets eof on the readChanId, the other side should have
    closed it (note that you will need to close that channel in your
    process, too, see chan manpage on 'pipe').

    Also note that calling flush on an read channel ($processStdoutErr) is a
    no-op, only write-channels will react to flush.

    Someone with more experience in twapi will have to comment on the rest.

    R'


    | proc ::GeneratePartBatchRead {readChanId} {
    | variable lastline
    | # read the last line
    | set line [gets $readChanId]
    | # output to the main app
    | puts $line
    | # store the line for later use
    | set lastline($readChanId) $line
    | }

    | # Open different channels for read and write
    | lassign [chan pipe] processStdin writeChanId
    | lassign [chan pipe] readChanId processStdoutErr
    | # React when the read channel becomes readable (meaning, a new line
    | # was written to the channel)
    | chan event $readChanId readable "::GeneratePartBatchRead $readChanId"
    | # Start the part generation process
    | set res [::twapi::create_process "" -cmdline $cmdline -returnhandles 1
    | -inherithandles 1 -stdchannels [list $processStdin $processStdoutErr
    | $processStdoutErr]]
    | # Store the process handles
    | lassign $res pid tid hproc hthread
    | # Wait for the process to end
    | set ::twapi_pid($pid) ""
    | ::twapi::wait_on_handle $hproc -executeonce 1 -async [list
    | ::GlobalVarSetFromTwapiCallback ::twapi_pid($pid) ""]
    | vwait ::twapi_pid($pid)
    | ::twapi::close_handle $hproc
    | # Get the last line written which is normally the path of the
    | # generated model file
    | chan flush $processStdoutErr
    | set result $lastline($readChanId)
    | catch {close $readChanId}
    | catch {close $writeChanId}
    | catch {close $processStdin}
    | catch {close $processStdoutErr}
    | array unset lastline $readChanId
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Thu Nov 20 12:27:15 2025
    From Newsgroup: comp.lang.tcl

    Am 20.11.2025 um 10:33 schrieb Ralf Fassel:
    are you sure
    ::GeneratePartBatchRead has collected all data before you call 'close'
    Hm, I was sure, because I'm closing after the wait comman (vwait ::meshparts::twapi_pid($pid)) which in turn only finishes after the
    process has stopped. I was assuming, that all write command are finished
    when the process is gone.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From meshparts@alexandru.dadalau@meshparts.de to comp.lang.tcl on Thu Nov 20 12:29:19 2025
    From Newsgroup: comp.lang.tcl

    Am 20.11.2025 um 10:33 schrieb Ralf Fassel:
    I'd rather let ::GeneratePartBatchRead decide when to
    stop: if it gets eof on the readChanId, the other side should have
    closed it (note that you will need to close that channel in your
    process, too, see chan manpage on 'pipe').
    besides, how can it be, that the first message is never received while
    the second is received?
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Ralf Fassel@ralfixx@gmx.de to comp.lang.tcl on Thu Nov 20 13:34:36 2025
    From Newsgroup: comp.lang.tcl

    * meshparts <alexandru.dadalau@meshparts.de>
    | Am 20.11.2025 um 10:33 schrieb Ralf Fassel:
    | > I'd rather let ::GeneratePartBatchRead decide when to
    | > stop: if it gets eof on the readChanId, the other side should have
    | > closed it (note that you will need to close that channel in your
    | > process, too, see chan manpage on 'pipe').
    | besides, how can it be, that the first message is never received while
    | the second is received?

    Are there \r involved in the messages? In that case some output via
    'puts' might overwrite the previous output. Rather collect the received messages via lappend for debugging...

    R'
    --- Synchronet 3.21a-Linux NewsLink 1.2