52

In my application, I want to redirect the output that would normally go to the stdout stream to a function I define. I read that you can redirect stdio to a file, so why not to a function?

For example:

void MyHandler( const char* data );

//<<Magical redirection code>>

printf( "test" );
std::cout << "test" << std::endl;

//MyHandler should have been called with "test" twice, at this point
  • How can I achieve this / similar behaviour?
Charx
  • 521
  • 1
  • 4
  • 4
  • 4
    A good question, for which a general solution is still very much lacking. – user664303 Jun 04 '14 at 10:09
  • 1
    @Charx the answer provided by @Wang Xuancong does redirect the output of `stdout` to your own `FILE` instance. You can then probably receive the data using pipes. Link to mentioned answer http://stackoverflow.com/a/21136532/293175 – MKroehnert Feb 08 '16 at 19:58

8 Answers8

46

@Konrad Rudolph is right, you can totally do this, easily, at least for cout/cerr/clog. You don't even need your own streambuf implementation, just use an ostringstream.

// Redirect cout.
streambuf* oldCoutStreamBuf = cout.rdbuf();
ostringstream strCout;
cout.rdbuf( strCout.rdbuf() );

// This goes to the string stream.
cout << "Hello, World!" << endl;

// Restore old cout.
cout.rdbuf( oldCoutStreamBuf );

// Will output our Hello World! from above.
cout << strCout.str();

Same thing works for cerr and clog, but in my experience that will NOT work for stdout/stderr in general, so printf won't output there. cout goes to stdout, but redirecting cout will not redirect all stdout. At least, that was my experience.

If the amount of data is expected to be small, the freopen/setbuf thing works fine. I ended up doing the fancier dup/dup2 thing redirecting to a pipe.

Update: I wrote a blog post showing the dup2 method I ended up using, which you can read here. It's written for OS X, but might work in other Unix flavors. I seriously doubt it would work in Windows. Cocoa version of the same thing here.

zpasternack
  • 17,494
  • 2
  • 59
  • 78
10

All the other answers are wrong. You can do this, and you don’t need to resort to freopen, nor to any other C or nonstandard functions.

Instead, you need to create your own std::streambuf implementation, i.e. your own stream buffer.

Once you have that, you can redirect the cout stream by switching the buffer:

your_stream_buffer new_buffer;
streambuf* old_buffer = cout.rdbuf(&new_buffer);

cout << "Hello"; // This will be redirected to `new_buffer`.

// Restore original buffer:
cout.rdbuf(old_buffer);

Since I’ve never done that myself I cannot tell you exactly how the implementation of streambuf has to look like. My advice is to have a look at the documentation and do a dummy implementation that prints diagnostics for all implemented functions. This should help figuring out how the class works.

Alternatively, have a look at a C++ reference book of your choice that describes the streambuf class.

Konrad Rudolph
  • 482,603
  • 120
  • 884
  • 1,141
  • 2
    @user664303 [X/Y problem](http://meta.stackexchange.com/q/66377/1968). The OP wants to redirect calls to `std::cout`’s streaming to their handler. My code does exactly that. It does not redirect the underlying operating system, stream, true, but that is *usually* not necessary, and much harder to accomplish, and not in a platform independent way. – Konrad Rudolph Jun 04 '14 at 10:42
  • 9
    No, a solution that redirects stdout was specifically asked for (even if you don't think it's usually necessary). Examples given were redirecting cout and redirecting printf output. Your solution doesn't work for printf (or other sources of stdout output). I mention this because I also need to redirect stdout, google pointed me to this question, and the 2 top voted answers don't achieve what was asked for or what I (and anyone else finding this question as opposed to "Redirecting cout") need. Why not post the "redirecting cout" question yourself, and answer it? It's a good solution to that. – user664303 Jun 04 '14 at 10:53
  • @user664303 Hence my reference to the X/Y problem: it seems like most people thought I answered OP’s actual problem (rather than the question they asked to solve their problem). In your case, take a look at the blog post referenced in zpasternack’s answer, which gives a solution for stdout using the `dup` system call. But again, that’s of course not platform independent, and, to reiterate, no such solution exists. – Konrad Rudolph Jun 04 '14 at 10:54
  • 1
    I'm looking into the dup solution, thanks. Also, let's avoid "other people disagree" style arguments - votes and opinions are only useful when they're independent, so let's not discourage different ones. – user664303 Jun 04 '14 at 11:09
6

It's possible to disable stdin/stdout by dereferencing its pointer:

FILE fp_old = *stdout;  // preserve the original stdout
*stdout = *fopen("/dev/null","w");  // redirect stdout to null
HObject m_ObjPOS = NewLibraryObject(); // call some library which prints unwanted stdout
*stdout=fp_old;  // restore stdout
Wang Xuancong
  • 61
  • 1
  • 2
  • This piece of code actually does redirect the output to `/dev/null` or any file you specify. To receive and further process the data probably requires using pipes. Also don't forget to close the replacement file. – MKroehnert Feb 08 '16 at 19:55
4

Answer: Yes you can, via a dup. freopen will only reopen stdout to a file, as you talked about.

Check out How to buffer stdout in memory and write it from a dedicated thread

Community
  • 1
  • 1
stefan
  • 2,797
  • 19
  • 27
  • This will require some extra code to manage the other end of the pipe and arrange for `MyHandler` to be called, but I think it's about as close as @Charx is going to get. +1 from me. – Jim Lewis Jan 27 '11 at 00:26
1

The std::cout object has a fixed meaning, and that is to output to the standard out stream. The user of your program gets to control where standard out is connected to, not you. What you can do is decide whether you wish to write to a file, to standard out or to any other output stream. So in your code you switch what stream you write to.

Again, the point of writing to the standard out stream is to give the user a flexibility in choosing where the output goes to. You're not supposed to redirect standard out; this is something the user is supposed to have the freedom to do.

Another thing is that you shouldn't mix C IO and C++ IO in a C++ program. Choose which IO library you wish to work with and stick to it.

That said, you can in C++ quite elegantly switch streams for a function to take input from by templating the handler function on the template parameters of std::basic_istream<>. Then the function will read its input from the input stream independently of the real kind of stream it's working with. Here's an example:

#include<iostream>
#include<fstream>
#include<string>

template<class Ch, class Tr>
void dodge_this(std::basic_istream<Ch, Tr>& in)
{
    // in is an input stream. read from it as you read from std::cin.
}

int main(int argc, char* argv[])
{
    if( std::string(argv[1]) == "cin" ) {
        dodge_this(std::cin);
    } else if( std::string(argv[1]) == "file" ) {
        std::ifstream file("input.txt");
        dodge_this(file);
    } else {
        dodge_this(dev_null_stream);  // i just made that up. you get the idea.
    }
}
wilhelmtell
  • 53,297
  • 19
  • 89
  • 128
  • Thanks but I can't really understand how this redirects stuff that goes to stdout to dodge_this, can you maybe help me with that? – Charx Jan 26 '11 at 22:11
  • The `main()` function will redirect input to `dodge_this()` from a source that depends on the first commandline argument to the program. – wilhelmtell Jan 26 '11 at 22:13
  • Thanks for your efforts, but I think you've got my question wrong – Charx Jan 26 '11 at 22:16
  • 4
    there are times you do want to redirect stdout, for instance when you write cgi applications and want to add process handlers to the output. Also whats good manners for the system you are working on really depends on how mature the platform is. There are platforms who doesn't have a console who can output stdout but you might want to use a ported library or code that uses printf/stderr for error handling. Then you will have to catch that data somehow and redirect it to your own graphical rendering. Development on consoles / handhelds spring to mind. – stefan Jan 27 '11 at 09:40
0

The following is not the best implementation, but it should work for most output...

using std::cout;
using std::endl;
using std::string;
using std::stringstream;

class Handler {
        public:
                Handler() {
                };
                virtual ~Handler() {
                };
                void operator<<(string str) {
                        this->stream << str;
                };
                string print(void) {
                        return this->stream.str();
                }
        private:
                stringstream stream;
};

int main(int argc, char ** argv) {
        Handler handle;
        handle << argv[1];
        cout << handle.print() << endl;
        return 0;
}

I just put a small main function underneath for you to try out and see if it works the way you want it to..

Sagar
  • 9,163
  • 5
  • 47
  • 93
0

Another option is to place your handler class calls into the inherited streambuf class. I had a requirement to redirect cout to a Win GUI edit control in a dialog box that may be of some use. Here is the class code:

//-------------------------------- DlgStringbuf Definition -----------------------     
class DlgStringbuf : public std::stringbuf
{
public:
  DlgStringbuf(void) : _hwndDlg(NULL), _editControlID(0), _accum(""), _lineNum(0) {}

  void SetDlg(HWND dlg, int editControlID)
    { _hwndDlg = dlg; _editControlID = editControlID; }
  void Clear(void)
    { _accum.clear(); _lineNum = 0; }

protected:
  virtual std::streamsize xsputn(const char* s, std::streamsize num) 
  {
    std::mutex m;
    std::lock_guard<std::mutex> lg(m);

    // Prepend with the line number
    std::string str(s, (const uint32_t)num);
    str = std::to_string(_lineNum) + ": " + str + "\r\n";

    // Accumulate the latest text to the front
    _accum = str + _accum;

    // Write to the Win32 dialog edit control.
    if(_hwndDlg != NULL)
      SetDlgItemTextW(_hwndDlg, _editControlID, (LPCWSTR)(std::wstring(_accum.begin(), _accum.end())).c_str());

    _lineNum++;
    return(num);
  }//end xsputn.

private:
  std::string     _accum;
  HWND            _hwndDlg;
  int             _editControlID;
  uint32_t        _lineNum;

};//end DlgStringbuf.

//-------------------------------- DlgStream Definition ------------------------------
class DlgStream : public std::ostream
{
public:
  DlgStream(void) : std::ostream(&_sbuff) {}

  void SetDlg(HWND dlg, int editControlID)
    { _sbuff.SetDlg(dlg, editControlID); }

  void Clear(void)
    { _sbuff.Clear(); }

private:
  DlgStringbuf  _sbuff;
};

...and in the WinMain, someplace after the dialog box and its edit control is created:

  // Redirect all cout usage to the activity dlg box.
  // Save output buffer of the stream - use unique pointer with deleter that ensures to restore
  // the original output buffer at the end of the program.
  auto del = [&](streambuf* p) {  cout.rdbuf(p); };
  unique_ptr<streambuf, decltype(del)> origBuffer(cout.rdbuf(), del);

  // Redirect the output to the dlg stream.
  _dlgStream.SetDlg(hwndActivityDlg, IDC_EDIT_ACTIVITY);
  _dlgStream.copyfmt(cout);
  cout.rdbuf(_dlgStream.rdbuf());

  cout << "this is from cout";
Keith
  • 21
  • 3
-1

You can use sprintf to write to a character array and then read the value:

char buf[1024];
sprintf(buf, "test");
MyHandler(buf);

there are also snprintf and a few others depending on platform

Foo Bah
  • 23,255
  • 5
  • 49
  • 78
  • 3
    I think OP wants to leave all the `printf` calls alone and just redirect them. – Tim Cooper Jan 26 '11 at 22:03
  • 1
    Yes, that is what I want to achieve @Tim. If the way my question is formulated seems unclear, feel free to edit it! – Charx Jan 26 '11 at 22:04