Search code examples
tclchannelputs

Create Chan In TCL for specific app


I use an app that has the ability to run TCL the app itself is written in C++ (shorthand 10) I believe that I push it far beyond what it was ever intended to do. As are most programming languages and good tools. This however poses a uniqe issue some of the items that are native to TCL are not included with this so I have to find creative ways to do what I'm looking to accomplish. -Enough of the back story... One of the major drawbacks is that there is no stdout or other regular channels.I know that there is a way to create one but I'm having a hard time. I have found the following code and am trying to make it work but am getting an error. The file path that I would like to use for the stdout is:

#FilePath of output ---> [sh_window exedir]IO_Text/stdout

oo::class create stdout {
    variable var
    constructor {varName} {
        my eval [list upvar \#0 $varName var]
    }
    method initialize {ch mode} {
        if {$mode ne "write"} {error "can't handle reading"}
        return {finalize initialize write}
    }
    method watch {ch events} {
        # Must be present but we ignore it because we do not
        # post any events
    }
    method finalize {ch} {
        my destroy
    }
    method write {ch bytes} {
        append var $bytes
        # Return the empty string, as we are swallowing the bytes
        return ""
    }
}

# Now we create an instance...
set string "The quick brown fox jumps over the lazy dog.\n"
set ch [chan create write [stdout new $string]]

--When this is run I get the following error:

chan handler "::oo::Obj12" does not support all required methods

The workaround that I am using for the moment is as follows. But I know that this is Horrible!

rename puts _puts;
proc puts {WInfo} {
set chan [open "[sh_window exedir]IO_Text/stdout" a+]
_puts $chan $WInfo; close $chan
} 

Solution

  • There's a few places where you've gone wrong. You must support watch even if by doing nothing, and you must return the number of bytes written from your write implementation (luckily, this is trivial for you to do).

    This is also a good time to hide the call to chan create inside the construction function of the class instance; there's no way to use the object created otherwise, so we might as well squirrel it away. (You can't do that in Java without futzing around with factories.)

    oo::class create stdout {
        variable var
        constructor {varName} {
            set s [list upvar \#0 $varName var]
            namespace eval [namespace current] $s
        }
        method initialize {ch mode} {
            if {$mode ne "write"} {error "can't handle reading"}
            return {finalize initialize write watch}
        }
        method watch {ch events} {
            # Must be present but we ignore it because we do not post any events
        }
        method finalize {ch} {
            my destroy
        }
        method write {ch bytes} {
            append var $bytes
            return [string length $bytes]
        }
        # Magic! Define a method on the 'stdout' class object itself
        self method new {varName} {
            set obj [next $varName]
            return [chan create write $obj]
        }
        # Stop normal code from creating named instances
        self unexport create
    }
    

    Finally, you call with a strange variable name in your test code — Tcl's strict about variable names vs. variable usage — and you may run into problems with testing because Tcl's channels (except when going to a terminal) default to fully buffered, which makes no sense in this case.

    set myString "The quick brown fox jumps over the lazy dog.\n"
    set ch [stdout new myString]
    fconfigure $ch -buffering none
    puts $ch "Howdy, pardner!"
    puts >>$myString<<
    

    When I try that, I get this output:

    >>The quick brown fox jumps over the lazy dog.
    Howdy, pardner!
    <<