How to redirect stderr and std::cerr to a stringstream?

127 views Asked by At

The library 'libmodbus' is a C library that uses fprintf(stderr, "") to send debug information to the console. Capturing this text continuously for use within my C++ application has been a challenge.

This post explains how to perform this in C++ with just a few lines:

redirect stdout/stderr to a string

But, after attempting this, I could not get it to work. Calls to std::cerr were captured, but calls to fprintf(stderr, "") were unaffected.

Doing the same in C is not as easy. As explained in this post, one can assign a char[] for stderr to be written to:

Redirect standard error to a string in C

Working with setbuf(stderr, char[], size), and the text being stored in it, is not documented very well. The standard calls to fgetch, fread and fscanf all return -1, with no error message. I attribute this to the fact that stderr is write only and reading from it will fail.

I tried opening the file handle with each option, some would allow write access, but none allowed read access. Using the debugger and stepping through the code one line at a time was the only way I found to solve this problem.

Using "Microsoft Visual Studio Community 2022 (64-bit) - Current Version 17.1.2"

As data is pushed into the char[], no null termination is added, but by using fgetpos, the terminator can be placed in the correct spot.

I have almost completed a class which can perform this function. I have not found documentation of how to reassign stderr back to the console. It just feels incomplete without restoring everything back to the way it was.

After a long search, I thought it appropriate to ask:

  1. How do I restore stderr back to the console after a call to freopen("nul", "a", stderr)?
  2. Did I completely overlook a simple way to do this?
  3. How portable will this solution be?

I'm forced to meddle with the inner workings of my current tool chains libraries. No good can come from that.

/* fstream_redirect.h */
#ifndef FSTREAM_REDIRECT_H
#define FSTREAM_REDIRECT_H
    #include <iostream>
    #include <sstream>
    #include "stdio.h"
    

    char fstream_redirect_buffer[BUFSIZ+1];

    class fstream_redirect {
    public:
        fstream_redirect(std::stringstream &new_buffer, std::ostream &fstream, FILE* c_handle){
            
            //save the pointers needed later
            _new_buffer = &new_buffer;
            _old        = fstream.rdbuf();
            _fstream    = &fstream;

            //Direct C++ calls to std::cout, std::cin, or std::cerr to a new_buffer
            fstream.rdbuf(new_buffer.rdbuf());


            //Direct C calls to stdout, stdin, or stderr to a new_buffer. 
            //This is not easy in C, as it is in C++
            // Order of Operations
            // 1) check buffer for change
            // 2) null terminate the new data
            // 3) retrive data
            // 4) rewind file handle to begining
            // 5) GoTo step 1

            //send stream to a void so it does not print
            #ifdef _WIN32
                _file = freopen("nul", "a", c_handle);
            #else
                _file = freopen("/dev/null", "a", c_handle);
            #endif  
            //set the _file to use our temp buffer
            if( setvbuf( _file, fstream_redirect_buffer, _IOFBF, BUFSIZ) )
                std::cout << "setvbuf failed";
        }

        ~fstream_redirect() {
            //C++ restore the stream to the correct output.
            _fstream->rdbuf(_old);
            //C figure out how to restore the stream to the proper output
            //  <<<< HERE IS WHERE YOU CAN HELP >>>>
        }

        void update(){
            fpos_t pos;
            char *p = fstream_redirect_buffer;
            fgetpos(_file, &pos);
            //Poll the C buffer for new data
            if(pos){
                p[pos] = 0;             //null terminate the data
                *_new_buffer << p;      //send data to the new_buffer
                rewind(_file);          //reset the next write to the start of the buffer       
            }
        }

    private:
        fstream_redirect() {};
        std::stringstream*  _new_buffer;
        std::streambuf*     _old;
        std::ios*           _fstream;
        FILE*               _file;
    };

#endif
/* main.cpp */
#include "fstream_redirect.h"

std::stringstream           _gError_buffer;
fstream_redirect            _gError_redirect( _gError_buffer, std::cerr, stderr );

int main(){
    fprintf(stderr, "1");
    std::cerr << "2";
    fprintf(stderr, "3");
    std::cerr << "4";
    fprintf(stderr, "5");
    std::cerr << "6";

    std::cout << _gError_buffer.str() << std::endl; //OUTPUT '246'
    _gError_redirect.update();
    std::cout << _gError_buffer.str() << std::endl; //OUTPUT '246135'
    return 0;
}
0

There are 0 answers