TCL的输出通道重定向
Im trying to embedded Tcl interpreter to C# GUI application, and everything works fine, even AttachingNewFunction to TclCommand. But one thing is to hard for me, I want to redirect stdout, stdin, stderr to some TextBox'es. Im working now with C++, becouse its easier to debug and compile. so i using code
Tcl_Channel StdOut = Tcl_GetStdChannel(TCL_STDOUT);Tcl_UnregisterChannel(interp,StdOut);Tcl_Channel myStdOut = Tcl_CreateChannel(typePtr, "stdout", NULL, TCL_READABLE | TCL_WRITABLE);Tcl_RegisterChannel(interp, myStdOut);Tcl_SetStdChannel(myStdOut, TCL_STDOUT);
to register new stdout, typePtr look like
typePtr->typeName = "stdout"; typePtr->version = TCL_CHANNEL_VERSION_2; typePtr->getHandleProc = Tcl_MyDriverGetHandleProc; typePtr->inputProc = Tcl_MyDriverInputProc; typePtr->outputProc = Tcl_MyDriverOutputProc; typePtr->flushProc = Tcl_MyDriverFlushProc; typePtr->watchProc = Tcl_MyDriverWatchProc; typePtr->closeProc = Tcl_MyDriverCloseProc; typePtr->blockModeProc = Tcl_MyDriverBlockModeProc; typePtr->seekProc = NULL; typePtr->close2Proc = NULL; typePtr->handlerProc = NULL; typePtr->wideSeekProc = NULL; typePtr->truncateProc = NULL; typePtr->setOptionProc = NULL; typePtr->getOptionProc = NULL; typePtr->threadActionProc = NULL;
and every function which i connect return TCL_OK or EINVAL (i know it from API) and puts some text to file, example
int Tcl_MyDriverCloseProc(ClientData instanceData, Tcl_Interp *interp) { std::cout << "\n Tcl_MyDriverCloseProc\n"; file << "\n Tcl_MyDriverCloseProc\n"; file.flush(); return EINVAL; }
i also use std::cout to debugging, but i dont believe him. When i compile&run nothing happen, stdout doesnt work, the result is for example
result:stderr file8adcd0 stdout stdin: result::
the code what i compiled is
Tcl_GetChannelNames(interp);std::cout << "result:" << Tcl_GetStringResult(interp) << ":\n"; Tcl_Eval(interp, "puts SomeOneHelp");std::cout << "result:" << Tcl_GetStringResult(interp) << ":\n";
I also cannot create custom channel and used it like
"puts myChannel pleHdeeNI"
when i done with C++ im going to make function in C# which will be writing 3 TCL standart channels into TextBox'es, but its easy.
You probably want to make that channel type declaration be a static struct; Tcl assumes that it is. (It's just like a vtable, except for a C API.) – Donal Fellows Aug 27 '13 at 7:59
The documentation of the low level of Tcl channels isn't the easiest, so it is probably instructive to look at example code. generic/tkConsole.c in Tk's implementation shows how the real stdout and stderr redirections are done. In particular, the fields that need non-NULL values are the name, version, closeProc (or close2Proc), inputProc, outputProc, watchProc and getHandleProc, and many of those can actually be dummies for the channels you create to handle stdout and stderr.
However, the Tk console widget doesn't support actually providing a real stdin (instead, it uses Tcl_Eval to run commands in the main interpreter) and the one it provides just claims to always be at end-of-file. It's a bit of a cop-out. Also, none of the channels are at all able to be passed to subprocesses as they don't have any representation at the level of the OS. Fixing that would require enormously more work (perhaps with anonymous pipes and worker threads and tricks to deal with the inevitable buffering issues; using something like the Expect package would do a much more complete job, though at a cost of even more complexity).
You probably want to return non-error results from things. For example, always returning 0 from your outputProc will cause great problems with the generic parts of Tcl's channel code; it assumes that this means that things are blocked and just buffers things up until it gets told that they have become unblocked. For a real swallow-everything first try, return the number of bytes written as the same as the number of bytes you were asked to write. Similarly, it is also important to make the closeProc work right; if you've no instance data to dispose of or underlying OS resources to get rid of, you can just return 0 there to indicate that everything is OK.
Tcl was designed from the beginning to be easy to embed in compiled programs. But sometimes -- particularly in event-driven GUI programs -- it is desirable to capture the output Tcl normally sends to the stdout and stderr I/O channels. The channel IO system in TCL is described in exhaustive detail in the TCL manual https://www.tcl-lang.org/man/tcl8.5/TclLib/CrtChannel.htm but it's one of those manual pages that can't be understood until you know enough about the subject that you don't need the manual page.
This C++ program demonstrates the minimum work necessary to properly catch TCL stdout and stderr channels. This is only one small part of a huge topic, but since non one else has pulled this information together in a clear example elsewhere, I think people should find it to be of some use.
#include <iostream>#include <tcl.h>//// Example class to catch stdout and stderr channel output.//// In the real world, this would be a GUI class (in Qt, KWWidgets etc)// that makes the proper API calls to display the output in the right// widget.class TclIOCatcher{public: void outputText(const char *buf, int toWrite) { std::cout << "-----TclIOCatcher--------------" << std::endl; std::cout.write(buf,toWrite); std::cout << std::endl << "---------------------" << std::endl; }};//// Tcl is pure C, and this is a C++ program; to ensure proper// calling linkage, encapsulate callbacks in a extern "C" section.extern "C"{ // outputproc is callback used by channel to handle data to outpu static int outputproc(ClientData instanceData, CONST84 char *buf, int toWrite, int *errorCodePtr) { // instanceData in this case is a pointer to a class instance TclIOCatcher *qd = reinterpret_cast<TclIOCatcher *>(instanceData); qd->outputText(buf,toWrite); return toWrite; } // inputproc doesn't do anything in an output-only channel. static int inputproc(ClientData instancedata, char *buf, int toRead, int *errorCodePtr) { return TCL_ERROR; } // nothing to do on close static int closeproc(ClientData instancedata, Tcl_Interp *interp) { return 0; } // no options for this channel static int setoptionproc(ClientData instancedata, Tcl_Interp *interp, CONST84 char *optionname, CONST84 char *value) { return TCL_OK; } // for non-blocking I/O, callback when data is ready. static void watchproc(ClientData instancedata, int mask) { /* not much to do here */ return; } // gethandleproc -- retrieves device-specific handle, not applicable here. static int gethandleproc(ClientData instancedata, int direction, ClientData *handlePtr) { return TCL_ERROR; } // Tcl Channel descriptor type. // many procs can be left NULL, and for our purposes // are left so. Tcl_ChannelType TclChan = { "tclIOTestChan", /* typeName */ TCL_CHANNEL_VERSION_4, /* channel type version */ closeproc, /* close proc */ inputproc, /* input proc */ outputproc, /* output proc */ NULL, /* seek proc - can be null */ setoptionproc, /* set option proc - can be null */ NULL, /* get option proc - can be null */ watchproc, /* watch proc */ gethandleproc, /* get handle proc */ NULL, /* close 2 proc - can be null */ NULL, /* block mode proc - can be null */ NULL, /* flush proc - can be null */ NULL, /* handler proc - can be null */ NULL, /* wide seek proc - can be null if seekproc is*/ NULL /* thread action proc - can be null */ };}int main(int argc, char **argv){ // create instance of the Tcl interpreter Tcl_Interp *interp(Tcl_CreateInterp()); Tcl_Init(interp); // class object to catch output TclIOCatcher test; // create a new channel for stdout Tcl_Channel m_Out = Tcl_CreateChannel(&TclChan, "testout", &test,TCL_WRITABLE); // // IMPORTANT -- tcl Channels do buffering, so // the output catcher won't get called until a buffer // is filled (default 4K bytes). // These settings are stolen from TkWish. Tcl_SetChannelOption(NULL,m_Out, "-translation", "lf"); Tcl_SetChannelOption(NULL,m_Out, "-buffering", "none"); Tcl_SetChannelOption(NULL,m_Out, "-encoding", "utf-8"); // // make this new channel the standard output channel. Tcl_SetStdChannel(m_Out,TCL_STDOUT); // // I'm not sure why this is necessary, but apparently it has // something to do with how reference counting inside the interpeter works. Tcl_RegisterChannel(0,m_Out); // // do all the same stuff for stderr. In our case, we push the // output all to the same place, but you could handle it seperately. Tcl_Channel m_Err = Tcl_CreateChannel(&TclChan, "testerr", &test,TCL_WRITABLE); Tcl_SetChannelOption(NULL,m_Err, "-translation", "lf"); Tcl_SetChannelOption(NULL,m_Err, "-buffering", "none"); Tcl_SetChannelOption(NULL,m_Err, "-encoding", "utf-8"); Tcl_SetStdChannel(m_Err,TCL_STDERR); Tcl_RegisterChannel(0,m_Err); // // run one command to demonstrate how it works const char testcommand[] = "puts $tcl_version"; int result = Tcl_EvalEx(interp,testcommand,strlen(testcommand),0); // show the result, should be zero. std::cout << "Result = " << result << std::endl; exit(result);}
ajmilford - 2012-07-03 07:51:08
I found the lines
Tcl_RegisterChannel(0,m_Out); : Tcl_RegisterChannel(0,m_Err);
should be
Tcl_RegisterChannel(interp,m_Out); : Tcl_RegisterChannel(interp,m_Err);
to get this to work