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:
- How do I restore
stderrback to the console after a call tofreopen("nul", "a", stderr)? - Did I completely overlook a simple way to do this?
- 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;
}