Ostream is easy

A9102969e779768e6f0b8cb87e864c94
0
dave_ 101 Sep 13, 2006 at 14:00

When debugging a program I generally use a combination of breakpoints and logging. For convenience I use std::cout. It is an instance of std::ostream that redirects its input to the stdio console. std::ostream just provides operations to perform output operations on stream buffer.

Most of the time using this stream is sufficient, but sometimes, for example when working on a windows application, you don’t have access to the console. Rather than change the code that relies on std::cout it is best to redirect its output. Luckily ostream has been designed to have its stream buffer changed. A simple approach is to redirect the output to a file. For example:

    // print to the console
    std::cout << "out to console" << std::endl;

    // open a file
    std::ofstream file("redirect.txt");

    // replace the buffer in cout, remember the old one.
    std::streambuf *old_buffer = std::cout.rdbuf(file.rdbuf());

    // log to cout which now redirects to the file
    std::cout << "to file" << std::endl;

    // and restore the old buffer
    std::cout.rdbuf(old_buffer);

    file.close();

Sometimes redirecting to a file is inconvient. To write to something else we need to create a new stream buffer. As I don’t want to create to many dependencies and I’d like the streambuf to be a bit easier to use I’ve created a simple wrapper (its inline for brevity).

#include <streambuf>

class BufferedStringBuf : public std::streambuf
{
public:
    BufferedStringBuf(int bufferSize) 
    {
        if (bufferSize)
        {
            char *ptr = new char[bufferSize];
            setp(ptr, ptr + bufferSize);
        }
        else
            setp(0, 0);
    }
    virtual ~BufferedStringBuf() 
    {
        sync();
        delete[] pbase();
    }

    virtual void writeString(const std::string &str) = 0;

private:
    int overflow(int c)
    {
        sync();

        if (c != EOF)
        {
            if (pbase() == epptr())
            {
                std::string temp;
                temp += char(c);
                writeString(temp);
            }
            else
                sputc(c);
        }

        return 0;
    }

    int sync()
    {
        if (pbase() != pptr())
        {
            int len = int(pptr() - pbase());
            std::string temp(pbase(), len);
            writeString(temp);
            setp(pbase(), epptr());
        }
        return 0;
    }
};

This class creates buffer for storing streamed output. The class is abstract; virtual void writeString(const std::string &str) = 0; must be overriden. writeString should implement your output operation, this could write the text to an overlay on screen, perhaps some kind of console or whatever you want.

At the end of a line, or when the ostream receives a flush sync is called. This causes the current buffer to be passed as a string to writeString().

When this buffer is filled, the overridden function overflow is called. This causes the stream to flush and attempts to store the character that has overflowed.

The buffer is optional, when the bufferSize is zero it constantly overflows. This is a less than optimal way of operating as it has to output each character individually, but it doesn’t take any extra memory.

This wrapper has been written with simplicity in mind, it might not be the most robust, but it only depends on the STL and its pretty short.

So enough of the details, lets make it do stuff! Here are some examples:

#include "BufferedStringBuf.h"
#include <windows.h>

const int LineSize = 256;

class DebugBuf : public BufferedStringBuf
{
public:
    DebugBuf() : BufferedStringBuf(LineSize) {}

    virtual void writeString(const std::string &str)
    {
        OutputDebugString(str.c_str());
    }
};

The above class implements a log to visual studio’s Output/Debug window.
To use it, take a similar approach to redirecting to a file.

    std::cout << "out to console" << std::endl;

    // replace the buffer
    DebugBuf debug_buffer;
    std::streambuf *old_buffer = std::cout.rdbuf(&debug_buffer);

    std::cout << "to visual studio debug output window" << std::endl;

    // restore the old buffer
    std::cout.rdbuf(old_buffer);

Here is another example buffer:

class MessageBoxBuf : public BufferedStringBuf
{
public:
    MessageBoxBuf() : BufferedStringBuf(LineSize) {}
    virtual void writeString(const std::string &str)
    {
        if ( str.size() > 1 ) // message box doesnt care about single characters
            MessageBox(NULL, str.c_str(), "Error", MB_OK|MB_ICONERROR);
    }
};

This one displays a message box when flushed.
Its probably not such a good one if you’ve got lots of text, but it just shows what can be done.

Finally, here is a useful buffer for chaining these buffers together.

class DupBuf : public BufferedStringBuf
{
public:

    DupBuf(std::ostream *stream1, std::ostream *stream2) :
      BufferedStringBuf(BufferSize), buffer1(stream1->rdbuf()), buffer2(stream2->rdbuf()) 
    {
    }
    
    virtual void writeString(const std::string &str)
    {
        const char *ptr = str.c_str();
        std::streamsize size = std::streamsize(str.size());

        buffer1->sputn(ptr, size);
        buffer2->sputn(ptr, size);
        buffer1->pubsync();
        buffer2->pubsync();
    }

private:

    std::streambuf *buffer1; 
    std::streambuf *buffer2;
};

This stores characters till the buffer is filled or flushed then it puts the characters in the other buffers and forces them to sync.
Used like this:

    DebugBuf debug_window_buf;
    std::ostream debug_stream(&debug_window_buf);

    DupBuf dup_buf(&std::cout, &debug_stream);
    old_buff = std::cout.rdbuf(&dup_buf);

    std::cout << "Print to debug output and stdio" << std::endl;

It will print to both the debug window and to std::cout. Again this is written for simplicity not performance.

Feel free to pick this code apart. I welcome constructive criticism.

Cheers

Dave

2 Replies

Please log in or register to post a reply.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Sep 13, 2006 at 18:06

You can also do this in applications that don’t have a console:

AllocConsole();
freopen("CONOUT$", "w", stdout);

std::cout << "this string outputs to the newly created console window";

Of course, the downside is that it doesn’t output to the outputwindow of VC++

6c301c866bd83b07323e88a609231111
0
cypher543 101 Sep 13, 2006 at 22:49

This is cool. Very useful for a Quake-like console. I think I’ll be using this in my next game project.