Introduction
This is the third in my trilogy of articles on iostreams. Having covered how to create inserters, extractors and modifiers, it
follows that I should explain how to write a stream. I am going to provide two classes, one of which overrides basic_streambuf
,
and provides the streaming implementation and one which derives from ostream
in order to provide us with a stream to
pass information to our underlying code.
The idea
The basic idea for this article came about because someone asked a question in the C++ forum, regarding redirecting printf
to an edit
box. So the task at hand will be to create a stream which appends the stream input to the text of an edit box.
MFC ?
The article code takes the form of an MFC dialog project, but the actual stream does not use MFC. You can copy the header file into
any Win32 project, and it should work just as well. I suspect it should work for WTL as well, but I have not tested this at ALL, OK ?
Point of entry
Because we are deriving from an iostream
class, we need to override specific functions in order for our code to integrate
into the library. We have three options. A stream operation can look something like this:
cout << "this is some text " << 42 << InstanceOfSomeOtherType;
In this example, the possible places for cout to handle the input are as follows:
Function | Where called |
overflow | Gets called once for each character passed into the stream |
flush | gets called when a flush occurs, usually via endl or a destructor |
xsputn | Gets called once per shift operator |
So we see we have three options.
- pass each character into the edit box when
overflow
is called
- store each character when
overflow
is called and pass it in one go in flush
- pass each string or other object one at a time in
xsputn
Option 1 should be used only by streams with low overhead in performing their functionality. Option 2 is best for costly
operations. Option 3 is about as easy as option 1, and more efficient. We will pursue option 3.
The stream
Basically our approach inside our function is to call GetWindowTextLength
to get the length of the text in the window,
and GetWindowText
to get the string, then we concatenate it with the one we already have, and use (surprise)
SetWindowText
to put it back in.
In order to do all this, we need the HWND
of the edit box, so we provide a function in both stream objects that takes a HWND
and returns the one previously stored. If it has not been set, our stream does nothing.
The code looks like this:
std::streamsize xsputn(const charT * pChar, std::streamsize n)
{
if (NULL != m_hWnd)
{
const int nLength = ::GetWindowTextLength(m_hWnd) + 1;
char * pText = new char [nLength];
::memset(pText, 0, nLength);
::GetWindowText(m_hWnd, pText, nLength);
std::string s(pText);
delete [] pText;
s += std::string(pChar);
::SetWindowText(m_hWnd, s.c_str());
return static_cast<std::streamsize>(s.length());
}
else
return 0;
}
Now we just need to initialise our stream with the HWND
of the edit box, and call our stream with the text we want to pass
in. The demo program provides a disabled edit box that shows the text already
received, and an enabled one, whose contents is streamed
in when you press the button provided.
If you're the person I wrote this for, I hope it helps you. Either way, I hope it provides you with some more ideas as to ways that
iostreams can be used to make your life easier with regard to input and output. It is an awesome library in my opinion, and one that
seems to me to be under utilised by a lot of people. I hope you find it useful to you.