January 1996
Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows++: Writing Reusable Code in C++ (Addison-Wesley, 1992).
Click to open or copy the DCLIKBAR project files.
Click to open or copy the TWINUPDT project files.
QA problem I encountered in designing and using a simple class hierarchy has annoyed me sufficiently to ask your advice. Since I have only been using C++ for a short while, I would be interested to know if I am missing something obvious. I have an abstract base class with the following member operator function:
virtual ostream& operator<<( ostream& ) const = 0;
My intention was to iterate through all objects derived from this base class and, using a base class pointer p, make all the objects print themselves. But if I write
cout << *p;
it doesn't work because this is interpreted as
cout.operator<<(*p);
which is undefined, instead of
*p.operator<<( cout );
If I write
*p << cout;
my code works, but this doesn't look right because the arrows are pointing the wrong way. I can make it work by writing an operator>> instead:
virtual ostream& operator>>( ostream& ) const = 0;
That is, use what is normally regarded as an input operator for the output operator. If I do that, then I can write
*p >> cout;
which the compiler interprets as
*p.operator>>( cout );
which works fine (and looks more sensible). But it doesn't seem right, and in any event it doesn't let me write
a >> b >> c >> cout;
A friend function won't work, either, because friend functions can't be virtual. So, my question is: is there a way of using operator<< that has escaped me, or is it the case that member operator functions like operator<< can't be made virtual and used in the way I intended?
Peter Edgley
AWell, you certainly have a good understanding of C++ for a beginner! But C++ can be a confusing beast sometimes, especially when it comes to operator overloading (among other things). In C++, you can write an operator in one of two ways: as a member function or as a nonmember function. The member function will always take one fewer argument than the nonmember function because the left-hand side of the operator is implicitly an object of the class the function belongs to. For example, if you have a String class, you can write operator+ as a nonmember function
String operator+ (const String& s1, const String& s2)
{
// do it
}
or as a member function:
String String::operator+ (const String& s) const
{
// do it
}
Whether to use a member or nonmember function is an important design decision. There's no simple answer I can give you; it all depends on what you want to do. (For a full discussion on member vs. nonmember operators, see C++ Primer by Stanley Lippman, Section 6.3, published by Addison-Wesley, 1991). In the case of Strings, the nonmember operator+ is probably better because it offers more opportunities for conversion. You can write expressions like
s = s1 + s2; // operator+(s1,s2)
s = s1 + "foo"; // operator+(s1,String("foo"))
s = "foo" + s1; // operator+(String("foo"),s1)
If you write a member function String::operator+, the last of these expressions won't work because the left-hand side is not a String object.
But you didn't ask about Strings, you asked about operator<<. Since you want to write
cout << obj;
you have no choice: the left-hand side is an ostream, which automatically rules out implementing operator<< as a member function. (It would have to be a member function of class ostream, which you are not free to modify.) If you have two choices, and one is no good, that leaves one choice: operator<< has to be a nonmember function. But then it can't be virtual. Does that mean you're hosed? Of course not! Remember, there's always a way!
All you have to do is write one nonmember operator<< for your base class, and have it call a virtual print member function to actually do the work. I wrote a simple program, VIRTOP.CPP (see Figure 1), that shows how to do it. This program has a dopey class hierarchy: a base Fruit class and two derived classes for Apple and Orange. Fruit declares a friend function operator<<, implemented like so:
inline ostream& operator<< (ostream& os,
const Fruit& fruit)
{
fruit.print(os); // call virtual function
return os;
}
operator<< is declared as a friend so it can call Fruit::print, which is protected. The print function is protected because it's considered part of the implementation of Fruit, not a public function for anyone to call. Now each derived class implements its own print function to print itself however it pleases. Most important, you can now write
Fruit *pFruit; // ptr to some fruit
cout << *pFruit; // calls the right print function
and the correct print function is invoked, depending on what kind of fruit pFruit actually points to. Since operator<< is inline, there's no extra function call. And since operator<< returns a reference to the ostream, you can concatenate operators.
cout << a << b << ... << z;
The operators are evaluated left to right: "cout << a" returns cout, which is then used to call "cout << b", and so on.
To sum up, your problem is not that operators can't be virtual (they can), but that you need to use a nonmember function to get the arguments in the right order: ostream on the left, object on the right. The solution I've shown is a standard C++ gimmick you can use to "virtualize" any nonmember function. You should add it to your bag of tricks. Of course, if you want to compare apples and oranges, you'll have to write a bunch more operators.
QI've noticed that any app that's written with MFC has a window name that begins with "Afx:" followed by a bunch of numbers. You can see this by looking at the window with a tool like Spy. Is there some easy way to change the name of the window? I'd like to make the name something more descriptive, like "AlphaWare1.0," and I don't want anyone to know my app was developed with MFC.
Name withheld upon request
AMy February 1994 column answers a similar question (how to change the background color of the main window), but that was almost two years ago, and this question comes up so frequently I thought it would be a good time to revisit this issue-especially since things have changed in release 4.0 of MFC. The machinations MFC goes through to register window classes are a bit convoluted.
Fortunately, changing the window class name is fairly easy. All you have to do is register your own window class with the name you want and use it. The only tricky part is figuring out where and when to register the window class, and how to get MFC to use it instead of doing its own thing. I wrote a program DCLIKBAR (see Figures 2 and 3) that shows how to do it. The basic idea is to change the window class name in your frame window's PreCreateWindow function.
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CFrameWnd::PreCreateWindow(cs))
return FALSE;
cs.lpszClass = "MyMainFrame"; // use my class name
return TRUE;
}
Figure 2 DCLIKBAR
The only problem is, you can't just create a window with any old class name unless you first register the class with Windows®. So you have to write a few more lines of code. Sorry.
// static data member to hold window class name
LPCSTR CMainFrame::s_winClassName = NULL;
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CFrameWnd::PreCreateWindow(cs))
return FALSE;
if (s_winClassName==NULL) {
// One-time initialization: register the class
//
s_winClassName = RegisterSimilarClass("MyMainFrame",
cs.lpszClass, IDR_MAINFRAME);
if (!s_winClassName)
return FALSE;
}
cs.lpszClass = s_winClassName;
return TRUE;
}
The first time this code runs, s_winClassName is NULL, so the if clause executes. CREATESTRUCT contains the window class name, which is "AfxFrameOrView". In a normal MFC program, this name would get changed to "Afx:x:y:z:w" where x, y, z, and w are hex values for the window style, cursor, background color, and icon. This is just MFC's way of generating a unique name for the window class. In DCLIKBAR, I call the helper function RegisterSimilarClass to register a new class with the name MyMainFrame. RegisterSimilarClass copies the window class information (WNDCLASS) from the already registered AfxFrameOrView, modifies the class name and icon, and registers a new class. Doing it this way guarantees that your main frame has all the same class properties as a normal main frame, except for the name. By the way, if you wanted to change the cursor or background color or any other class property, this would be the place to do it.
Since DCLIKBAR is an MDI app, I implemented my own child frame to change its class name too. Figure 4 is a Spy++ screen capture that shows the window class names for a vanilla DCLIKBAR program (one without the code to modify the class names); the main frame's class name is Afx:b:14ae:6:3e8f. Figure 5 shows the modified version with the new window class names: MyMainFrame, MyChildFrame, and MyControlBar (described in the next question). As you can see, there's still AfxControlBar for the status line and AfxFrameOrView for the view. If you really want to hide the fact that you're using MFC, you'll have to derive newclassesforthesewindowclassestoo,usingthetechnique described. If you're using MFC 4.0 or later, you don't need to do it for toolbars or status bars (see the next question).
Figure 4 Vanilla window class names
Figure 5 Modified window class names
While you're hiding all evidence of MFC, don't forget to link with the static library instead of the DLL! Requiring MFC30.DLL to run your app is a dead giveaway. There are other clues, too, like "Afx" strings embedded in your EXE file. I suspect it's impossible to totally hide the fact that your app was built with MFC, if someone knows where to look. What are you doing, anyway? Writing code for IBM?
QUsing Visual C++ª 2.2, I have created a CToolBar-derived class that I would like to handle double clicks. Using Spy++, I found that WM_LBUTTONDBLCLK messages are not getting sent to my toolbar window. This is due to the fact that CToolBar::Create calls CWnd::Create and specifies _afxWndControlBar (that is, AfxControlBar) as the window class. The AfxControlBar window class is registered in APPINIT.CPP without including the CS_DBLCLKS flag in the style bits of the WNDCLASS structure. Is there anything I can do (short of editing and recompiling MFC source) to make double clicks get sent to my toolbar window?
Brian P. Donaldson
A Well, the simplest thing would be to use ::SetClassLong to modify the window class style for control bars, perhaps in your control bar's OnCreate handler.
DWORD dwStyle = ::GetClassLong(m_hWnd, GCL_STYLE);
dwStyle |= CS_DBLCLKS;
::SetClassLong(m_hWnd, GCL_STYLE, dwStyle);
The main drawback with this approach is that it modifies the class for all instances of AfxControlBar in your app. For example, the status bar will get the same double-clicks-allowed property, since it's a control bar too. This may or may not be what you want. A better way that requires only slightly more work is to create a new control bar class that has the desired style. I wrote a program called DCLIKBAR (see Figures 2 and 3) that does it. It's the same program described in the previous question. (I just love it when I can kill two questions with one app!)
As you discovered, MFC registers a special window class for control bars, AfxControlBar. This window class is registered without CS_DBLCLKS. To get a control bar that accepts double clicks, you have to register your own window class with CS_DBLCLKS turned on. To make sure your window class has the same style as MFC's, you can start by getting the WNDCLASS info for AfxControlBar, then modify the style, change the name, and register a new class. Just don't forget to use the new name when you create the control bar (see Figure 6). That wasn't so bad, was it?
If you're using MFC 4.0 or later, things are even easier: you don't have to do anything. Starting with version 4.0, MFC uses the real Windows common toolbar control, ToolbarWindow32, which-lucky you!-allows double clicks. Since we're on the subject, I should point out that all the built-in window class names are different in MFC 4.0. Figure 7 is a Spy++ screen capture that shows how the names have changed with version 4.0. The toolbar and status line now use real Windows common controls (ToolbarWindow32 and msctls.statusbar32 and-don't you love the consistent naming conventions), and AfxFrameOrView is changed to AfxFrameOrView40sd. You can probably guess what 40 stands for; the sd indicates that this instance of DCLIKBAR was compiled with MFC as a static link library, with _DEBUG turned on. (Of course, you have no reason on earth to care about these names-just use whatever name comes to you in cs.lpszClass in PreCreateWindow.)
Figure 7 MFC 4.0 window class names
For those of you who need to worry about such things, I must also point out that MFC 4.0 has a totally different way of registering its built-in window classes. Instead of doing it all in APPINIT.CPP, each C++ class that uses one of the built-in window classes now registers it on an as-needed basis, using a new internal function (actually, it's a macro) called AfxDeferRegisterClass. This macro takes a flag that identifieswhich kind of window to register: a normal window, a frame/view, a common control, or something else. Warning: if you have code that mucks around this part of the framework, it might break in MFC version 4.0.
I'd like to deliver a brief update on the TRACEWIN program I described in the October 1995 issue. If you recall, TRACEWIN is an applet that displays MFC TRACE diagnostic messages even when you don't run your app under the debugger. It works by sending strings from the application to the TRACEWIN window. In the October column, I showed you how to send the strings as global atoms to avoid using the Clipboard or OLE data transfer.
A few readers pointed out that a better way of doing it is to use WM_COPYDATA. With my brain firmly anchored (should I say mired?) in the past, I missed WM_COPYDATA. (It's true, I don't read the API manuals the minute a new operating system comes out!) WM_COPYDATA is new for Win32, and it's exactly what TRACEWIN needs: a simple and easy way to send a string of text from one application toanother.WM_COPYDATAtakestwoparameters:WPARAM is the window handle of the sending window; LPARAM is a pointer to a COPYDATASTRUCT, which looks like this.
// (defined in WINUSER.H)
struct COPYDATASTRUCT {
DWORD dwData; // whatever you want
DWORD cbData; // length of data
PVOID lpData; // ptr to data
};
This is pretty self-explanatory. dwData is just any DWORD you want to send. You can use it to identify different kinds of data you might send with WM_COPYDATA. For TRACEWIN, I send a special value, ID_COPYDATA_TRACEMSG, that TRACEWIN looks for: if the incoming COPYDATASTRUCT doesn't have this value, TRACEWIN ignores it. Figure 8 shows the modified Write function in TRACEWIN.H, which is the file you must include in your app to use TRACEWIN; Figure 9 shows the modified TRACEWIN code, from the file MAINFRM.CPP. The new, improved TRACEWIN is available from the usual MSJ sources.
WM_COPYDATA is a truly wonderful message-so simple and useful that I can't understand why it got added. Someone over there must be slipping.
Have a question about programming in C or C++? You can mail it directly to C/C++ Q&A, Microsoft Systems Journal, 825 Eighth Avenue, 18th Floor, New York, New York 10019, or send it to MSJ (re: C/C++ Q&A) via:
Internet:
Paul DiLascia
72400,2702
Eric Maffei
ericm@microsoft.com