Contributing editor Al Stevens can be contacted at astevens@ddj.com.
Technology marches on. Recently I logged onto to read more about C-Span's coverage of the Alexis de Tocqueville tour of America in 1831 and 1832. I learned from the web site that, by downloading free RealPlayer software, I could watch live video broadcasts from the Internet. I got the free software, installed it, and settled back to watch C-Span as I worked. Of course, the video picture is small, grainy, and jerky; the audio is poor; the broadcast has a several second delay; and the program endures long pauses when I multitask the PC intensively. But there it is, live video content on my PC monitor. I called Judy away from her office to show her this really cool new stuff. She was not impressed.
"I've got the same thing, only much better," she said.
"No way," says I, "Show me." I knew she was wrong. Her 486 isn't as fast as my Pentium, doesn't have as much memory, and her monitor is only 17 inches. Mine is 21 inches.
We went into her office. "Watch," she said, as she sat at her PC where Family Tree Maker was grinding out diagrams of her Stauffer family 28,000 ancestor/descendent database. Then, she deftly picked up the TV remote control, aimed it at the 21-inch Hitachi TV across the room, clicked a few buttons, and there was C-Span in full splendor, no delays, a large, clear, steady picture, good sound, and no interruptions when she did processor-intense things on her PC. "Amazing, isn't it?" Judy said smugly, and turned her attention away from me and back to her work.
"Yeah, but you had to buy the TV," I grumbled and skulked away.
Artificial Tech Support
Artificial intelligence (AI) was a hot buzz phrase a few years back. Inference engines were supposed to replace humans in some activities by making decisions based on criteria that could be described to the computer. The fad flared brightly then fizzled, but the concepts continue to advance quietly in the background now that folks realize that this once-trendy technology is not going to cook dinner, drive trucks, and deliver babies.
One potential application for AI-like processes is tech support. FAQs and bug databases on some Internet sites are examples. Help-desk personnel often have online access to problem databases to assist them in helping customers.
The MidiFitz program I introduced some time ago as a project in this column is available on the web as a freeware download (). MidiFitz is a real-time accompaniment program. It produces a MIDI drums and bass rhythm section in real time based on the chords played on a keyboard. To use programs such as MidiFitz, sequencers, and other interactive MIDI programs, you have to get your PC to play MIDI sounds and to read MIDI input devices (keyboards and their pedals, for example). Potential users of such programs -- musicians -- are not necessarily computer hotshots. Even computer-literate users can be bewildered when they first try to get a MIDI connection working on a PC. Windows 95 has made the process somewhat easier, but there are still some things that you just have to know. When someone downloads MidiFitz and can't get it to work, they usually send me e-mail asking for help.
These requests come often enough that I decided to automate the tech-support process -- to provide a free program that users can download. The program asks questions, examines hardware/software configuration, and advises users on how to get everything turned on, connected, and working -- kind of like what AI intended to do, but without the inference engine and the cryptic scripts. I selected two Windows 95 idioms as the medium for communicating with users. These idioms are the Wizard dialog box and WinHelp database.
This approach is not restricted to tech support for MIDI. Any product that requires installation and configuration of hardware and drivers could use a similar approach, particularly when the installation is tricky or vulnerable to other configuration considerations, things that you cannot predict or cover in a user's guide because there are too many possible configurations.
The program is not completed, and the source code that I am providing this month is only the beginning. In this column, I'll describe the Wizard process and the tools I used to build the program.
Wizard Dialog Boxes
In my November 1996 and January 1997 columns, I described how to build a Property Sheet/Property Page tabbed dialog application with MFC. A Wizard is like a tabbed dialog except that there are no tabs at the top of each page. The command buttons at the bottom include Back and Next buttons to let the user navigate the pages. A tabbed dialog box allows users to select pages in any order. A Wizard requires users to step through pages in a prescribed order. The two idioms support different user interfaces based on the input and information presentation requirements of the application.
Turning a Property Sheet dialog into a Wizard is simple. The application class's InitInstance function instantiates the CPropertySheet object as usual, then calls CPropertySheet::SetWizardMode before calling CDialog::DoModal. The framework takes care of everything else. The property pages that the property sheet adds to itself with the CPropertySheet::AddPage function are available to the user through the Next and Back buttons, which the framework adds to the sheet's dialog. The sequence of the pages is based on the sequence of calls to AddPage.
Wizard pages have a certain look, the format of which is driven more by convention than anything else. There is usually a graphic on the left side, and some text and perhaps some controls on the right side of each page. The graphic is usually on a Windows 95 desktop green background with some icons that represent what the Wizard supports. Figure 1 shows the opening page for the MidiHelp application. This format convention will prevail until Microsoft decides to change it (with Windows 98 or later, for instance).
The example application (available electronically; see "Availability," page 3) is unremarkable. It contains three pages, each of which has its own static text controls. Upon opening the third page, the application displays a message box saying that there are no MIDI output devices installed. Later, this message will depend on the actual configuration and the third page will do something meaningful with MIDI output. There will also be several other pages and an associated context-sensitive Help database. I'll describe those features of the project in a later column. For now, I'll focus on the project, provide a skeleton implementation, and discuss the development tools I used to build it.
CodeWarrior
CodeWarrior Professional Release 1 from Metrowerks () comes in a three CD-ROM package that includes the CodeWarrior development environments for the Macintosh and Win32. The CodeWarrior product line supports several other languages and platforms. I used the Win32 C++ edition for this project and all further references that I make to CodeWarrior, which I will abbreviate here as CW, are about the Win32 edition, which supports C++ and MFC 4.0.
CW is a relatively new toolset. Its degree of integration and its support for a visual programming paradigm are far less than those of its competitors. You can download a light version from the Metrowerks web site. The download version does not allow you to build new projects, but you can experiment with some provided example projects.
CW is a bare-bones integrated development environment (IDE). It is not all that integrated, however. The editor, compiler, and debugger use different text windows to display the code. The IDE does not provide access to many of the help databases that you can install. The dialog editor is Microsoft's DLGEDIT.EXE, and the CW IDE does not integrate this utility or its output with a CW project.
CW has no contemporary visual-programming tools. There are no wizards that construct skeleton MFC applications, for example. You have to know MFC, and you have to write all the code yourself. There is no menu editor. DLGEDIT produces text files that you must manually include in the resource script file. It has no way to import dialogs from other applications when the dialogs are in text script format. Metrowerks says that they are furiously working on tools to fill that void.
CW for Win32 is a descendent of Metrowerks' Macintosh edition. Their previous version elicited howls from Windows programmers who did not like the many so-called "Mac-isms" in the user interface. This version addresses many of those concerns, although the program does not have a real Windows 95 look. That, however, is not necessarily to its detriment. Their custom-tabbed dialogs, tree controls, and tool buttons are intuitive and a pleasure to use.
The editor has no macro feature. The Preference dialog refers to scripts under General/Misc, but the feature is not mentioned in the Help database and, I assume, not included in the software. You can't maximize the editor windows, and there are times when, for unexplained reasons, you can't minimize any of the windows. The editor's Find/Replace dialog is unconventional, and the IDE's About display is almost disconcerting in its lack of conformity to Windows standards.
The IDE includes two commands, Make and Bring Up To Date, and the difference between the two is not clear. Make does not always work properly. The first day, I installed CW and ported a small application to it. Changing a header file would not cause the affected .cpp files to be compiled. The same day, successive execution of the Make and Run commands in any order would always rebuild something that did not need to be rebuilt. This problem persisted until I tried again the next day. Nothing else had happened, but those two problems went away on day two. Go figure. A few days later, the second problem returned. You cannot look at the time/date stamps of compiler output to see what is confusing the Make process, because the object files are hidden away in proprietary datafiles. Until this problem and the preprocessor problem I'll describe next are fixed, CW 2.0 is unusable in my opinion.
The preprocessor has a fatal flaw. It frequently fails to find Standard Library header files. It simply says that they are not there, even though you can see that they are in the same subdirectory with other header files that the preprocessor did find. The bug is inconsistent, but it persisted through two projects -- one that I ported and another, a console application, that I built from scratch.
It took some experimenting and a call to Metrowerks to figure out how to debug a program. There are two debugger windows that open when you start a debug session. One of them has the tool buttons that control running, stepping, and so on. That window does not always open and you are left to wonder where the buttons are, since the other window seems to have everything else you need. It's not clear why there are two windows. I encountered some bugs in the debugger. It looks for a file named APPMODULE.CPP, which is not among the files in the project's file list. When it does not find that file, the debugger opens a modal File Open dialog box so you can specify where the file is. I don't even know what the file is. Clicking Cancel ignores the file, and the program debugs fine without it. The debugger does not do this for the console application, only for the GUI application. Sometimes the debugger fails to terminate itself properly and leaves several windows on the desktop partially displayed. They do not become active when I click them, they will not go away, and you cannot do anything else until they are gone. I had to use the Windows 95 Ctrl-Alt-Del Close Program dialog to get rid of them.
The compiler is close to being ANSI compliant, although it does not implement namespaces or standard wide-character iostream objects. There are no win and wout objects, for example, even though the header files define the wide-character stream classes. It supports the new stringstream classes, including the wstringstream classes. I compiled many exercise programs from my C++ tutorial book as console applications without too many problems. One program, a calculator that demonstrates recursion and exception handling (and works okay with other compilers), does not work with CW. I could not step through it to find the problem because the debugger does not step through C++ code very well. It seems to get lost among the class member functions during exception handling.
Although CW Pro is released as version 2.0, the number of bugs and their effect on the product's usefulness are more indicative of a beta release of version 1.0. In my opinion, this product was released too early with too little testing, and, unless you are interested in the Macintosh edition, you should wait to purchase CW Pro until Metrowerks has fixed some of its Win32 problems. My advice to Metrowerks is to redirect some of that visual-programming effort into fixing what's already released. It's that close to being a really nice development tool. Add the fancy stuff later when the foundation product is stable.
You might wonder why anyone would need yet another C++ development environment for Win32. Its executables are significantly bigger than those of Visual C++ and its feature list is way behind the competition. There are several reasons. First, the more competitors Microsoft has, the less complacent they are likely to get about their development tools. Well, that might be a stretch. Second, products such as CW target several platforms with a single product line, which makes it easier for programmers to be productive on multiple platforms. Third, healthy competition promotes lower prices and better support.
Remember the main Again
A couple of months ago, I discussed the problems that authors have with an emerging, shifting Standard. Here's a problem that we've had since writing about C got popular. In a newsgroup discussion about computer books, someone said:
Any serious author of C books loses all credibility as soon as they endorse "void main"
Someone else said:
Many authors have used void main() in their examples, as have most compiler producers...at one time or another. While most have been gradually switching to int main(), I seriously doubt that they have considered it...something upon which their whole reputations and future success hinges.
Someone else then said:
If I recall correctly, void main() was never part of the Standard, and if it worked correctly or not was rather undefined. It relied on the compiler vendor. Advocating platform-dependent code in a book that purports to be a generic C/C++ text isn't a good thing.
I would like to give credit to the authors for their quotes, but they all use spam-jamming e-mail addresses, and I could not get their permission to use their names.
Although they were talking about someone else's work, they could have been talking about me. I, too, used void main() in the past and now use int main() exclusively, so I feel qualified to comment on the issue from an author's perspective.
The proposed C++ standard is almost specific on the subject of void main(). A look at excerpts from the December 1996 draft might shed some light on the question.
3.6.1 Main function
...This function shall not be overloaded. It shall have a return type of type int, but otherwise its type is implementation-defined. All implementations shall allow both of the following definitions of main:int main() { /* ... */ }
and
int main(int argc, char* argv[]) { /* ... */ }
Section 3.6.1 specifies how main must be supported in a compliant compiler, and it seems to forbid other implementations of main by specifying that you may not provide overloaded main functions. However, even though it seems that void main() is an overloaded main function, it is not, depending on how you interpret Section 13.1.
13.1 Overloadable declarations
...Function declarations that differ only in the return type cannot be overloaded.
The real problem arose from legacy code, found in many old programs and C++ books, that wrote main this way:
main() { // ... }
The main function, implicitly declared as returning int, returned nothing -- as if it were declared as returning void. Some compilers permitted this usage and others did not. The Standard forbids it or discourages it in Section 6.6.3, depending on how you choose to interpret the ambiguity of the second excerpt's two clauses.
6.6.3 The return statement
...A return statement without an expression can be used only in functions that do not return a value, that is, a function with the return value type void, [constructors, and destructors].
...Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.
Some compilers actually made an exception to the rules in 6.6.3 only for the main function. Others did not, issuing warnings about the absence of a return expression. Consequently, authors who wanted to avoid that unnecessary line of code (return 0;) and the warning message, and who were writing for readers who used many different compilers, switched to void main(). The compilers permitted it because there is no global prototype for main. The mechanics of calling main from the startup code works with this idiom. The usage worked, was convenient, and got authors past another stumbling block with writing about a constantly moving target. It is not clear from the wording in the Standard that this idiom will continue to be supported. Nothing in the Standard requires compiler vendors to permit it, although a lot of legacy code uses it. Many authors, myself included, now bite the bullet and code this way in their examples:
int main() { // ... return 0; }
DDJ