[KBK] (8 January 2001) -- ''Purpose:'' Provide sample code for hooking up C++ iostreams and Tcl channels. One thing that annoys C++ programmers who integrate their code with the Tcl system is that C++ stream buffers and Tcl I/O channels are totally divorced from one another. The C++ programmer naturally expects that cout << "Hello, world!" << endl; will result in a message on the Wish console, for instance. This does not happen by default. The problem is worst on Windows. On Windows, if Wish is launched in the usual way, the line shown above will cause a nasty program crash because there is no '''cout''' stream at all. It turns out that the low-level iostream mechanisms can be programmed to use Tcl channels. The attached files, '''TclChannelStreambuf.h''' and '''TclChannelStreambuf.cpp''', make this attachment. All that a C++ extension must do is to call TclConsoleStreambufSetup out of its initialization, and the '''cerr''' and '''cout''' streams will designate the Tcl '''stdout''' and '''stderr''' channels. Alas, '''cin''' is more of a problem. One could develop an extension to TkCon to handle it, but I never did. NOTE: I cut and pasted these files out of a larger app, and I haven't tried compiling them since; I haven't the time right now. Feel free to e-mail if you have trouble or to post a followup with fixes. ---- // -*- C++ -*- // NOTE: One leading blank has been added to each line of this file // to cope with the demands of the Wiki. This blank should // be deleted before trying to compile. /* ## File: TclChannelStreambuf.h ## ## Synopsis: ## Class to support redirection of a C++ stream to a Tcl channel ## (particularly, routing cin/cout/cerr to the console). #@CDef */ #ifndef __TclChannelStreambuf_h #define __TclChannelStreambuf_h 1 extern "C" { #include } #include #ifdef _MSVC_VER #include #else #include #endif class TclChannelStreambuf : public streambuf { private: Tcl_Channel channel_; Tcl_DString iBuffer_; int iOffset_; public: TclChannelStreambuf (Tcl_Channel); ~TclChannelStreambuf (); virtual int overflow (int); virtual int underflow (); virtual int sync (); }; extern "C" void TclConsoleStreambufSetup _ANSI_ARGS_(( void )); #endif /* __TclChannelStreambuf_h */ ---- // -*- C++ -*- // NOTE: One leading blank has been added to each line of this file // to cope with the demands of the Wiki. This blank should // be deleted before trying to compile. /* ## File: TclChannelStreambuf.cpp ## ## Synopsis: ## Class to support redirection of a C++ stream to a Tcl channel ## (particularly, routing cin/cout/cerr to the console). */ #include "TclChannelStreambuf.h" #include /* ## Function: TclChannelStreambuf::TclChannelStreambuf ## ## Synopsis: ## Associate a Tcl channel with a streambuf. */ TclChannelStreambuf::TclChannelStreambuf (Tcl_Channel channel) : channel_ (channel) { Tcl_DStringInit (&iBuffer_); iOffset_ = 0; } /* ## Function: TclChannelStreambuf::~TclChannelStreambuf ## ## Synopsis: ## Destroy a streambuf associated with a Tcl channel. */ TclChannelStreambuf::~TclChannelStreambuf () { Tcl_DStringFree (&iBuffer_); } /* ## Function: TclChannelStreambuf::overflow ## ## Synopsis: ## Handle a full output buffer on a streambuf writing to a Tcl channel. */ int TclChannelStreambuf::overflow (int c) { int status; // Allocate reserve area if necessary if (base() == 0) { doallocate (); } // If there's no output buffer, allocate one. Place it after the // end of the input buffer if there's input. if (!pbase ()) { if (egptr () > gptr ()) { setp (egptr (), ebuf ()); } else { setp (base (), ebuf ()); } } // If there's stuff in the output buffer, write it to the channel. if (pptr () > pbase ()) { status = Tcl_Write (channel_, pbase (), pptr () - pbase ()); if (status < (pptr () - pbase ())) { return EOF; } setp (pbase (), ebuf ()); } // Save the next character in the output buffer. If there is none, // flush the channel if (c != EOF) { *(pptr()) = c; pbump (1); return c; } else { setp (0, 0); status = Tcl_Flush (channel_); if (status != TCL_OK) return EOF; return 0; } } /* ## Function: TclChannelStreambuf::underflow ## ## Synopsis: ## Get a fresh input buffer from a Tcl channel for a streambuf */ int TclChannelStreambuf::underflow () { // Nothing to do if the buffer hasn't underflowed. if (egptr() > gptr()) { return *gptr (); } // Make sure we have a reserve area if (!base()) { doallocate (); } // Flush any pending output if (pptr () > pbase ()) { if (overflow (EOF) == EOF) { return EOF; } } // Get a fresh line of input if needed if (iOffset_ >= Tcl_DStringLength (&iBuffer_)) { if (Tcl_Gets (channel_, &iBuffer_)) { return EOF; } Tcl_DStringAppend (&iBuffer_, "\n", 1); } // Determine how much input to transfer. Don't fill the reserve // area more than half full size_t xferlen = Tcl_DStringLength (&iBuffer_); if ((long) xferlen > (ebuf () - base ()) / 2) { xferlen = (ebuf () - base ()) / 2; } // Copy string into the buffer, and advance pointers memcpy ((void *) base (), (void *) Tcl_DStringValue (&iBuffer_), xferlen); iOffset_ += xferlen; setg (base (), base (), base () + xferlen); // Free the input string if we're finished with it if (iOffset_ >= Tcl_DStringLength (&iBuffer_)) { Tcl_DStringFree (&iBuffer_); iOffset_ = 0; } // Return the first character read. return *gptr (); } /* ## Function: TclChannelStreambuf::sync ## ## Synopsis: ## Synchronize input and output on a Tcl channe associated with ## a streambuf */ int TclChannelStreambuf::sync () { // Flush output if (overflow (EOF) == EOF) { return EOF; } // Discard input setg (0, 0, 0); return 0; } /* ## Function: TclConsoleStreambufSetup ## ## Synopsis: ## Bind the standard streams to the Tcl console */ extern "C" void TclConsoleStreambufSetup () { cin = new TclChannelStreambuf (Tcl_GetStdChannel (TCL_STDIN)); cout = new TclChannelStreambuf (Tcl_GetStdChannel (TCL_STDOUT)); cerr = new TclChannelStreambuf (Tcl_GetStdChannel (TCL_STDERR)); cerr << "C++ standard input and output now on the Tcl console" << endl; } [SLB] The C++ streams interface changed during the ISO standardisation process. With modern streams implementations, statements such as: cout = new TclChannelStreambuf (Tcl_GetStdChannel (TCL_STDOUT)); probably won't compile. You can fix this by changing it to: cout.rdbuf(new TclChannelStreambuf (Tcl_GetStdChannel (TCL_STDOUT))); Some variation on this will be needed for stdin, I haven't investigated that. ---- CJL - 2006/11/01 A question has been posted to comp.lang.tcl (titled "C++ streams to Tcl channels") asking for somebody with an understanding of the inner workings of Tcl and C++ streambuffers to update this. [KBK] - Ugh. I wrote that page nearly six years ago, but the code is several years older than that. I daresay that something similar could be made to work with std::iostream, but for *me* to do it, I'd have to relearn how the ISO streams work. It would most likely be several months before I could look at it; life gets in the way. If someone else wants to take on the job, I'd be happy to advise. ---- CJL - Moved a general request for help to 'Ask #5' ([http://wiki.tcl.tk/16668]) where it will get a bigger audience ---- [[ [Category Channel] | [Category Foreign Interfaces] | [Category Example] ]]