2023-10-28

Capturing stdout and stderr in C or C++ program

Capturing stdout and stderr in C or C++ program

 

如果將 Tcl 內嵌在 C 或者 C++ 程式內,程式用來獲取 Tcl stdout 輸出的方法。下面是我使用 clang-format 編排,並且為了練習將 -buffering 從 none 改為 line 的程式。

#include <iostream>
#include <cstring>
#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;
    }
};

//
// 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) {
    Tcl_FindExecutable(argv[0]);

    // 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", "line");
    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", "line");
    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 [info patchlevel]";
    int result = Tcl_EvalEx(interp, testcommand, strlen(testcommand), 0);
    // show the result, should be zero.
    std::cout << "Result = " << result << std::endl;
    Tcl_Finalize();
    exit(result);
}

然後撰寫一個簡單的 CMakeLists.txt 來使用 CMake 幫忙編譯程式:

cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

# set the project name
project(capture VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# find_package (TclStub REQUIRED)
find_package(TCL REQUIRED)

# add the executable
add_executable(capture capture.cpp)

target_link_libraries (capture ${TCL_LIBRARY})
include_directories (${TCL_INCLUDE_PATH})

沒有留言: