开发者

Override c++ streams

In a C++ program, we have 3 streams: stdin, stdout, and stderr. Can I override these in a console application and use them in a application that uses forms?

For example, if in som开发者_开发百科e base class, I have cout<< "...", can I "redirect" to something visual (like a Windows Form)?


What I would recommend doing is having a class which wraps around an iostream like this :

#include <iostream>
#define LOG Log()

class Log
{
   public:
      Log(){}
      ~Log()
      {
         // Add an newline.
         std::cout << std::endl;
      }


      template<typename T>
      Log &operator << (const T &t)
      {
         std::cout << t;
         return * this;
      }
};

Then, whenever you want to change where the data goes, you just change the class behavior. Here is how you use the class:

 LOG << "Use this like an iostream.";

[edit] As potato swatter suggested, I'll add an example with something other than cout:

#include <sstream>
#define LOG Log()

// An example with a string stream.
class Log
{
   private:
      static std::stringstream buf;
   public:
      Log(){}
      ~Log()
      {
         // Add an newline.
         buf << std::endl;
      }


      template<typename T>
      Log &operator << (const T &t)
      {
         buf << t;
         return * this;
      }
};

// Define the static member, somewhere in an implementation file.
std::stringstream Log::buf;

As for why you should try this instead of inheriting from something like a string stream, mainly because you can easily change where the Logger outputs to dynamically. For instance, you could have three different output streams, and use a static member variable to swap between at runtime:

class Log
{
   private:
      static int outputIndex = 0;
      // Add a few static streams in here.
      static std::stringstream bufOne;
      static std::stringstream bufTwo;
      static std::stringstream bufThree;
   public:
      // Constructor/ destructor goes here.

      template<typename T>
      Log &operator << (const T &t)
      {
         // Switch between different outputs.
         switch (outputIndex)
         {
            case 1:
               bufOne << t;
               break;
            case 2:
               bufTwo << t;
            case 3:
               bufThree << t;
            default:
               std::cout << t;
               break;
         }
         return * this;
      }

      static void setOutputIndex(int _outputIndex)
      {
          outputIndex = _outputIndex;
      }
};

// In use
LOG << "Print to stream 1";
Log::setOutputIndex(2);
LOG << "Print to stream 2";
Log::setOutputIndex(3);
LOG << "Print to stream 3";
Log::setOutputIndex(0);
LOG << "Print to cout";

This can easily be expanded to create a powerful way of dealing with logging. You could add filestreams, use std::cerr, etc.


Here's the code I use to redirect std::cout to a GUI on Windows:

struct STDOUT_BLOCK : SLIST_ENTRY
{
    char sz[];
};

class capturebuf : public std::stringbuf
{
protected:
    virtual int sync()
    {
        if (g_threadUI && g_hwndProgressDialog) {
            // ensure NUL termination
            overflow(0);
            // allocate space
            STDOUT_BLOCK* pBlock = (STDOUT_BLOCK*)_aligned_malloc(sizeof *pBlock + pptr() - pbase(), MEMORY_ALLOCATION_ALIGNMENT);
            // copy buffer into string
            strcpy(pBlock->sz, pbase());
            // clear buffer
            str(std::string());
            // queue string
            ::InterlockedPushEntrySList(g_slistStdout, pBlock);
            // kick to log window
            ::PostMessageA(g_hwndProgressDialog, WM_APP, 0, 0);
        }
        return __super::sync();
    }
};

Then inside main():

capturebuf altout;
std::cout.set_rdbuf(&altout);

Of course, you then need to handle the WM_APP message in your window procedure and pull the strings off the SList. But this handles the cout redirection part.

As jweyrich correctly notes, you need to change the streambuf* back before altout goes out of scope. This code will do so:

struct scoped_cout_streambuf_association
{
    std::streambuf* orig;
    scoped_cout_streambuf_association( std::streambuf& buf )
        : orig(std::cout.rdbuf())
    {
        std::cout.rdbuf(&buf);
    }

    ~scoped_cout_streambuf_association()
    {
        std::cout.rdbuf(orig);
    }
};

And inside main:

capturebuf altout;
scoped_cout_streambuf_association redirect(altout);


The usual way to put an iostream interface on something else is to use the stringstream/istringstream/ostringstream classes in <sstream>.

The best way is to change the use of cin, cout, etc to parameters of type istream & and ostream &. If that is really impossible, you can modify cin.rdbuf() to point at the stream you wish to modify, but that is extremely hackish and won't work with multithreading.

Copy text from the form into an istringstream, then pass it to the existing code as an istream &. When you're done, read the results from the stringstream passed as ostream & that replaced cout and copy it into the form.

Do not declare arguments of type istringstream & or ostringstream &. Use the base classes.

If you really want, you could subclass std::basic_stringbuf< char > and override the sync and underflow virtual functions to tie streams directly to text fields. But that's fairly advanced and likely not worth it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜