Introduction

( 1 )
Next (Table Of Contents)

CPPIDE

(C++ Integrated Development Environment)

by John Kopplin

Copyright © 2002-05

Welcome to CPPIDE, a Windows program that provides an Integrated Development Environment (IDE) for the C++ programming language. An IDE for a programming language provides the tools you need to compose, edit, run, and debug (usually in that order) programs written in that language. It is called an integrated development environment because back in the early days of the computer industry you had to employ a separate tool -- a separate program -- to accomplish each of these steps. You will be able to employ this IDE to compose arbitrary C and C++ programs, but initially I want to walk you through a carefully scripted sequence of example programs that will teach you the C and C++ languages and teach you about Windows programming.

This is the third and final component of my self-study curriculum which introduces computer programming to beginners. I start my students with a programmable RPN calculator that offers a gentle introduction to programming. I use the RPN calculator to demonstrate fundamental programming concepts such as program memory, data memory, looping, single-stepping, labels, subroutines, etc. Next we move on to the Intel 8051 microprocessor where we learn about microprocessors, assembly language, syntax errors, addressing modes, interrupts, etc. Only after this foundation is in place do I unleash the maelstrom of C++, the most powerful high-level language in wide usage today. I chose to teach the C++ language because it's a mainstream language widely used in industry. And because C++ is a superset of the C language, you will really be learning 2 languages (or 3 since 90 % of what you learn will be applicable to Java as well).

The help page that you are now viewing is merely the first of an extensive set of help pages that together represent a self-study guide that should allow you to learn to program on your own, at your own pace. To use these help pages as a self-study guide simply read them in the order they are listed on the Table Of Contents page. There is a hyperlink at the top and bottom of each page labeled "Next" which will take you to the next page in the order I intended. You will also see a page number in the upper right hand corner of each page to help you keep track of your progress. Of course I have also provided a keyword index and lots of other hyperlinks to help you navigate directly to particular topics. You can leave this help page window open while you interact with CPPIDE (and you probably want to make it bigger than its default size).

You have probably seen a Help system like this one already. It is built upon Microsoft's standard HTML Help technology. In the left-hand pane you can observe 4 tabs labeled Contents, Index, Search, and Favorites. The Contents tab simulates a book's table of contents while the Index tab simulates a book's index. The Search tab offers the feature that books would kill for. The Favorites tab is where you can record bookmarks on your favorite pages. These bookmarks make it easy to return to these spots later. One good use for a bookmark is to record the spot where you break from your studies so you can resume there later.

The home page for all of my software is www.computersciencelab.com For questions, you can reach me at support@computersciencelab.com. Enjoy!

Next (Table Of Contents)


( 2 )
Previous Table Of Contents Next

Table Of Contents

Introduction

Table of Contents (this page)

Background

High Level Languages

Programming Language Family Tree

History of C++

Console-mode Programs

Console-mode Programs

The Hello World Example Program

The Hello World 2 Example Program

The Basics of the C/C++ Language

Variables and Data Types

Functions

The Factorial Example Program

Debugging

The Factorial 3 through 5 Example Programs

The Factorial 6 through 8 Example Programs

The Factorial 9 through 10 Example Programs

Call By Value and Call By Reference

The Factorial 11 through 12 Example Programs

More About Debugging

Classes and C++

Using The cScale Class

Implementing The cScale Class

Extending The cScale Class

Compiler Error Messages

Strings

The String Manipulation Functions

Static Variables

Command Line Arguments

Language Review

The New Project Wizard

Windows Programs

Windows Programs

Event Driven Programs

The Maze Solver Example Program

Debugging Windows Programs

Enums And Macros

Arrays

Pointers

Static Data Members

Static Member Functions

The Peak Detector Example Program

The Breakout Example Program

The Physics of Collisions

The Struct Data Type

Dynamic Memory Allocation

The New Project Wizard Continued

The CSL Framework

Child Window Classes

Extending The CSL Framework

Unicode

The Built-In Classes

Default Function Arguments

Resource (.RC) Files

Interconnecting The Child Windows

Wrapping Up

The Example Programs

Appendices

CPPIDE Options

Gnu Compiler Options

Makefiles

Hungarian Prefixes

Math Functions

The Iostream class

The Java Language

About CPPIDE


High-level Languages

( 3 )

High Level Programming Languages

C++ is one of many high-level languages in use today. Let's start by remembering the reason why high-level languages were created. As we learned when studying the 8051 microprocessor, while a program is being executed its only manifestation inside the computer is a stored pattern of voltage levels. Since low voltage levels are not perceivable by humans (high-voltages certainly are!), we need an alternate representation for computer programs. This alternate representation needs only 2 symbols to correspond to a high and a low voltage which in turn correspond to an open and a closed switch.

Machine language is the name given to a computer program written using a binary alphabet having only the two letters 0 and 1. A byte-oriented machine (such as the 8051 microprocessor) consumes (processes) 8 bits at a time. Hence a computer program in machine language consisting of four operations for a byte oriented machine might look like:

    01101110
    10111100
    00000100
    11001111

Humans don't enjoy working with this notation, so they have invented a number of labor saving devices. One labor saving device is the adoption of hexadecimal notation. This same sequence of 4 bytes can be represented in hex as:

    0x6E
    0xBC
    0x04
    0xCF

The 0x prefix (that's "zero x" not "oh x") indicates a hex number. Just imagine that you needed to dictate a long program over the telephone to a buddy. You would definitely choose to call out the hex version.

But even hex provides little comfort for a modern 32 bit microprocessor such as the x86 that is probably inside the computer in front of you. A sequence of four 32 bit operations for the x86 might look like:

    10010001110110011010111000111011
    00010101111010010001010111011111
    00101000000110000011101001011100
    11110001001101111111100001010100

in binary or like:

    0x91D9AE3B
    0x15E915DF
    0x28183A5C
    0xF137F854

in hex.

The programmers that worked on ENIAC (the first all-electronic digital computer) had to construct their programs using a notation like this. They apparently looked down their noses at the newbies who later employed the new fangled thing called assembly language. You know all about assembly language because in the last section of my curriculum you wrote a number of programs for an Intel 8051 microprocessor using 8051 assembly language.

Every computer (that is, every different model of computer) has a different architecture and hence requires a different assembly language. The 8051 could perform multiplication if the two operands were loaded into the A and B registers. Hence the multiplication mnemonic in 8051 assembly code was chosen to be "MUL AB". Other microprocessors may not be capable of performing multiplication at all or may not have registers called the A and B registers or may not require the operands to be loaded into these particular registers prior to a multiplication. So the term "assembly language" really refers to a whole set of languages. But they are all more alike than different and once you know one you can easily pick up another.

Now the invention of assembly language was a major labor saving innovation and it wasn't long before no one was programming in machine code any more. The fact that assembly language reads a bit like a natural (i.e., human) language makes it easier to comprehend your program. You can simply scan through your program listing, thinking like a computer does, and find your programming mistakes.

But think back to when you first saw 8051 assembly language. At that time, a sequence of statements such as:

Coef   EQU   11           ; employ data memory address 11 to
                          ;   hold the value of the coefficient
Result EQU   12           ; employ data memory location 12 to
                          ;   hold the result
     ...
     MOV    A,#5          ; load the A register with the value 5
     MOV    B,Coef        ; load the B register with the Coef value
     MUL    AB            ; compute the product of the values in
                          ;   the A and B registers
     MOV    Result,A      ; copy the product to data memory

probably looked intimidating. Wouldn't it be wonderful if you could instruct the computer to perform this same computation just by typing:

  Result = 5 * Coef;

This is legal syntax for most high-level languages including C, C++, and Java.

Consider what is missing in the statement "Result = 5 * Coef". Where do you see mention of any data memory locations? Which data memory locations will be used during this computation? Will the value 5 or the value of Coef be placed into a register instead of a data memory location? And where do you see mention of any number limits, such as the fact that an 8 bit register can only hold an unsigned number smaller than the value 256? Somehow this high-level language statement is going to cause two numbers to get multiplied together, but we don't really know how this will happen. And we don't know how fast it will happen either. If we were programming in assembly we could count our statements and know exactly how long this sequence would take to execute. All these aspects of our program are considered "details" that high-level languages hide.

This hiding of detail is both the advantage and the disadvantage of high-level languages. The hiding of hardware details is an advantage because often you just want to request that two numbers be multiplied together and you don't really care how it gets done. "Just do it", you want to say. Hence the command "Result = 5 * Coef" remains appropriate regardless of whether this program is going to run on an 8051 or on an x86 microprocessor, which have completely different memory organizations, registers, accumulators, word widths, etc.

But this hiding of details is also a drawback because if you are not specifying the details then you are depending upon a computer program to arrange the details. That computer program is the compiler. A compiler for an 8051 microprocessor knows how to translate the statement "Result = 5 * Coef" into legal 8051 assembly language and a compiler for an x86 microprocessor knows how to translate this statement into legal x86 assembly language. You can ask the compiler to show you its solution if you want to inspect it, but you usually don't want to inspect it if you have chosen to program in a high-level language. By selecting a high-level language you are choosing to leave lots of details to your assistant, the compiler.

But computer programs are not yet as clever as people. And this means that although the compiler will get the two numbers multiplied together, it may not do it in the most optimum manner. For example, a compiler for the 8051 microprocessor will probably assume that it must load the A register with the value 5 and load the B register with the Coef value and then issue the MUL AB instruction. But a human might realize that due to earlier steps in this program, the A register already holds the value 5 and hence the "MOV A,#5" statement is not necessary and can be omitted.

Now I know you are saying to yourself, "Well I could improve that stupid compiler!" And that is exactly what has been happening during the 40 years that compilers have been around. A modern compiler does check for all sorts of optimizations such as this one. Today, a top-notch compiler will be able to generate a program that runs only about 10 % slower and takes only about 10 % more memory than a hand-crafted program written in assembly language by an expert. For this reason, most programs today are written in high-level languages since in most cases we gladly trade the missing 10 % of performance for the benefits we gain by using a high-level language.

What are those benefits? The most important benefit is that our source code is portable. You may be familiar with the LaserJet printers produced by Hewlett-Packard. Over a period of about a decade HP has written over a million lines of source code that generates the intelligence that goes into these printers (software that is permanently embedded inside a dedicated-purpose machine is called firmware because it essentially becomes a part of the hardware; that is, the circuitry). Now suppose that million lines of code was written in assembly language for an x86 microprocessor and further suppose that Intel either goes bankrupt or decides to quit manufacturing the x86 microprocessor in order to transition all their customers to their next hot chip (these things do happen). Suddenly HP's investment in their million lines of source code has lost almost all its value. This is because there is no easy way to convert from x86 assembly language to some other assembly language. This is because the details of the x86 architecture are suffused all through the source code and would have to be rooted out to port the code to a different microprocessor.

But if this source code was in a high-level language, then all HP would have to do was jettison their x86 compiler and replace it with, say, an Itanium compiler (the Itanium is Intel's hot new chip). A compiler is not very expensive and can process the million lines of LaserJet source code into Itanium assembly language in a few hours. Problem solved! In fact, HP could decide to (and in fact does) sell different printer models that employ different microprocessors and yet share the same million lines of source code. This process of compiling high-level source code is illustrated below:

Another advantage to a high-level language is that each high-level statement generally corresponds to multiple assembly language statements. For example, the high-level statement "Result = 5 * Coef" corresponds to the four 8051 assembly language statements we saw above. Studies have shown that programmers produce about the same number of statements each day, regardless of whether they program in a high-level or a low-level language (this makes perfect sense if the programmer spends his day typing as fast as he can). So if you are an employer responsible for paying a team of programmers until they get the new product finished, you may want them to write in a high-level language in order that the software will be completed sooner.

So why does assembly language survive? Sometimes you just can't leave that 10 % on the table. Many microprocessors have extremely tight limits on the amount of program memory they can address. I once worked on a team that designed a disk drive that employed a microprocessor that was a speed demon but could only address 4096 words of program memory (a word is 2 bytes). The firmware that this team wrote grew past the 4096 word limit long before we finished accomplishing all the functionality the product needed. We had to continually revisit the code we had written and find little tricks that would save a word here or there. Each time we got the program back below 4096 words we could add new functionality. When we finally shipped this product our program consumed exactly 4096 words. We didn't have a single instruction to spare! If we had been programming in a high-level language our memory consumption would have been at least 10 % greater and hence we would not have been able to complete the product. Furthermore, the tricks that we used to save memory were only available to an assembly language programmer. This makes it clear that we didn't have a choice, and in fact we didn't because this was a brand new microprocessor and no high-level language compiler was yet available for it!


Family Tree

( 4 )

Programming Language Family Tree

The name C++ (which is pronounced "see plus plus") reflects the fact that this programming language was an augmentation of the older C language. We will study the improvements that C++ introduced when we get to classes. In the meantime, let's take a quick look at the history of programming languages. The first all-electronic digital computer was ENIAC which became operational in 1945. The first commercial (that is, mass produced) computer was UNIVAC which started being sold in 1951. Grace Murray Hooper developed the first operational compiler in 1952. 1952 was also the year IBM entered the computer business. The late 1950's saw the introduction of several high-level programming languages which are still in use today: FORTRAN (which stood for "FORmula TRANslation"), LISP (which stood for LISt Processing language"), and COBOL (which stood for "COmmon Business Oriented Language"). Considering how fast computer hardware becomes obsolete and worthless, it is truly amazing that these early high-level languages still have utility today.

The popular features introduced by each new programming language would be adopted into subsequent programming languages. This cross-pollination was very beneficial to the nascent industry. Remember it was Isaac Newton who said, "If I can see farther than those who came before me, it is because I am standing on their shoulders." In the family tree below I attempt to show the most important instances where one language inherited significant features from an older language.

The bottom row of this illustration identifies the programming languages in wide use today.

An even more complete programming language family tree might still be available on the Internet at www.levenez.com/lang/history.html#03 or people.mandrakesoft.com/~prigaux/language-study/diagram.png

You might enjoy hearing a little bit about some of these languages. FORTRAN is optimized for scientific calculation such as the mathematical manipulation of radio signals captured from outer space by the SETI (Search for Extra-Terrestrial Intelligence) project.

The COBOL language was and is widely used for business record keeping. It was the programming language most affected by the infamous Y2K bug.

LISP was and still is the favorite language of the AI (artificial intelligence) community that seeks to write programs that mimic human intelligence.

The BASIC and Pascal languages were both created as teaching languages. BASIC was exceptionally easy to learn, but is widely criticized for its use of the goto statement, which allows you to jump from any spot in your program to any other spot in your program. If you employ this goto statement too much your BASIC program will degenerate into what is called spaghetti code: a program that jumps from here to there and back in such a twisted knot that nobody, not even the original author, can make sense of it.

The Pascal language eliminated the goto statement and replaced it with block structure, a design methodology still favored today. An example of block structure is the if/else statement which forces all the relevant pieces of each computational step to be located nearby to one another in a block of statements. This results in cleaner, more easily understood code. Unfortunately, the designer of Pascal (Niklaus Wirth) was so determined to eliminate bad coding practices that he designed an overly restrictive language. At times it feels like Pascal is your opponent, preventing you from accomplishing your goal.

Because of this the C language quickly overtook Pascal. The philosophy of the designer of C (Dennis Ritchie) was the complete opposite of the philosophy of Wirth. The C language allows the programmer unprecedented freedom to manipulate the machine. This can be both a good and a bad thing because the C language will give you the rope to hang yourself. Whereas Pascal was designed to prevent certain practices, the C compiler takes the attitude that it will attempt to satisfy all requests and it is the programmer's responsibility to know what he is doing.

There is a famous anecdote that attempts to humorously explain the differences in expressive power available to a programmer working with the many high-level languages. The original version of this anecdote was published in the December 1991 issue of "Developer's Insight" magazine (now defunct) but it has been augmented a number of times since then. The anecdote compares whether each programming language allows you to shoot yourself in the foot.

Goal: Shoot yourself in the foot.

With the Pascal language [which is very restrictive], the compiler won't let you shoot yourself in the foot.

With the C language, you shoot yourself in the foot.

With the C++ language you accidentally create a dozen instances of yourself and shoot them all in the foot. [We will learn about instances soon.]

With the Ada language [which is optimized for concurrent -- meaning simultaneous -- activities] you attempt to concurrently load the gun, pull the trigger, scream, and shoot yourself in the foot only to discover you can't because your foot is of the wrong type. [We will learn about data types shortly.]

With the BASIC language [which is considered underpowered], you shoot yourself in the foot with a water pistol. On large computers you continue until your entire lower body is waterlogged.

With Assembly language [which is primitive and lacks the infrastructure you get with a high-level language] you try to shoot yourself in the foot only to discover you must first invent the gun, the bullet, the trigger, and your foot.

With the Prolog language [which is very cryptic] you tell your program that you want to be shot in the foot. The program figures out how to do it, but the syntax doesn't permit it to explain it to you.

With Windows [which is not a programming language per se but definitely deserving of a little dissing] you discover your gun is not compatible with the operating system and hence you must first buy an upgrade and install it before you can continue. Then you will be informed that you don't have enough memory.

With the Modula2 language [which was designed by that overly restrictive language designer Wirth] you realize that you can't actually accomplish anything in this language and in frustration you shoot yourself in the head.

With the Perl language, Perl will give you ten bullets and a laser scope and then stand by and cheer you on.

Isn't this fun? Who says programmers are humorless geeks. When this anecdote was originally written the description of the C language was probably intended as an insult but after reading all the other descriptions it stands out as exactly what you want if your goal is to shoot yourself in the foot. And indeed C has become one of the most dominant programming languages. I will be teaching you C++ which augments the C language with additional firepower, so get out your steel-toed shoes.

I would like to tell you that the choice of a programming language is a very personal decision, but usually it's not: when you get a job in industry you generally are told what language to employ. As your career progresses you will be exposed to a handful of languages. At that time you will be ready to try your hand at cutting insults such as the following:

The use of COBOL cripples the mind; its teaching should, therefore, be regarded as a criminal offence. (Edsger Dijkstra)

Consistently separating words by spaces became a general custom about the tenth century A.D., and lasted until about 1957, when FORTRAN abandoned the practice. (Sun FORTRAN Reference Manual)

Cobol has almost no fervent enthusiasts. As a programming tool, it has roughly the sex appeal of a wrench. (Charles Petzold)

C++ is the only current language making COBOL look good. (Bertrand Meyer)

C++ has its place in the history of programming languages. Just as Caligula has his place in the history of the Roman Empire. (Robert Firth)

Arguing that Java is better than C++ is like arguing that grasshoppers taste better than tree bark. (Thant Tessman)

Java is, in many ways, C++--. (Michael Feldman)

If Java had true garbage collection, most programs would delete themselves upon execution. (Robert Sewell)

It is practically impossible to teach good programming style to students that have had prior exposure to BASIC; as potential programmers they are mentally mutilated beyond hope of regeneration. (Edsger Dijkstra)

In my egotistical opinion, most people's C programs should be indented six feet downward and covered with dirt. (Blair P. Houghton) [This is a pun referring to the stylistic choice of how to indent your statements as you type them.]

C++ is history repeated as tragedy. Java is history repeated as farce. (Scott McKay)

Unix and C are the ultimate computer viruses. (Richard P Gabriel)

If the automobile had followed the same development cycle as the computer, a Rolls-Royce would today cost $100, get a million miles per gallon, and explode once a year, killing everyone inside. (Robert X. Cringely)


History of C++

( 5 )

History of the C++ Programming Language

The C++ language builds upon the C language so we should start with a few words about C. Thanks to Alexander Graham Bell's invention of the telephone, his firm -- Bell Telephone -- became the telephone supplier for all of America. This was one of those rare situations where a monopoly was legal since it would be impractical to string telephone wires from multiple telephone companies to every house in America. The research and development arm of the Bell Telephone system was known as Bell Labs and this institution made many scientific breakthroughs such as the invention of the transistor and the laser. Perhaps because of their status as a legal monopoly, Bell Labs saw fit to make much of their work freely available to universities. An example would be the UNIX operating system which was invented at Bell Labs and then offered to universities who could both employ it to run their computers and augment it as a research project for their students. Since the price was right, UNIX became very widespread on college campuses and very visible to the next generation of computer engineers.

Consequently, UNIX (and its open source variant LINUX) became (and remains) one of the dominant computer operating systems. It is typically employed for the largest servers, such as those computers running airline reservation systems, credit card transaction authorizations, Internet resellers such as Amazon.com, etc.

The C programming language was invented by the same Bell Labs engineer (Dennis Ritchie) who invented the UNIX operating system. He wanted to employ this language to implement the UNIX operating system itself. This is why C had to be the most expressive and powerful language possible, since it would be employed to wire the brains of the world's most powerful computers.

C is the language of Microsoft's WIN32 API (application programming interface), which is the infrastructure that allows you to write a program that displays the Windows "look and feel".

In 1983 another Bell Labs employee (Stroustrup) invented a new programming language he called "C with classes" since it was basically an augmentation of the C language to add the concept of classes, which had first appeared in the Simula language. This language became today's C++.

Because C++ augments C, most legal C programs are also legal C++ programs. Today, Microsoft employs C++ for all their killer apps (applications) such as Word, Excel, Power Point, etc. And most video games are created using C++. So I will really be teaching you two languages at once. Or maybe three since Java can be viewed as a simplification of the C++ language.


( 6 )

Console-mode Programs

If you remember the MS-DOS operating system then you remember console-mode programs. These are programs whose only method for taking input from the operator and only method for showing results to the operator is via lines of text. These programs cannot have a menu, a toolbar, a status bar, pushbuttons, comboboxes, or any of the other hallmarks of a modern graphical user interface (GUI) program. And because these programs are so much more primitive, they are a lot easier to create. So we will use this type of program to begin learning the C++ language. Actually, we will be learning the C language up until that time I introduce classes (don't hold your breath, we've got a ways to go).

Here is a screen capture of a console-mode program that comes with the Windows operating system:

You can create this same window on any Windows computer. Under the Windows 95/98/ME operating systems simply select "Start/Programs/MS-DOS Prompt". Under the Windows NT/2000/XP operating systems select "Start/Programs/Command Prompt". Either way, this particular console window is called the DOS box or DOS prompt window.

Now that you have a console-mode window you can interact with it by typing a textual command and then observing the textual response. For example, type the command "dir" and then hit the ENTER key and, in response, Windows will list the names of all the files in the current directory. Immediately after you issue the "Start/Programs/MS-DOS Prompt" command on Windows 95/98/ME, the current directory will be the "C:\Windows" directory (as seen in the photo above). Immediately after you issue the "Start/Programs/Command Prompt" command on Windows NT/2000/XP, the current directory will be the "C:\" directory. The name of the current directory is printed in front of the prompt which is the greater-than character >. When you see the prompt the computer is ready for you to issue a command.

Congratulations. You have now interacted with a computer in the manner that was customary back in the 1970's (in the 1960's you had to prepare punched cards to issue commands to a computer). This man-to-machine interface is known as the command line and was the original method of interacting with both the Microsoft and UNIX operating systems. Graphical user interfaces have replaced command line interfaces because they make your choices visible. With a command line interface you see the prompt blinking at you but you may not know the commands that you are allowed to type. With a graphical user interface each of your options is identified by a visual cue such as a list of words in the menu or a small icon on the toolbar. These visual memory aids are definitely superior when you are first learning a new operating system, but they are not faster. An experienced computer operator can get more work done in less time using a command line interface because he doesn't need to keep moving his hand between the mouse and the keyboard.

Okay, so we have seen a console-mode program and the command line interface that it presents. So how do we create a program that employs this methodology? The original book on the C language (written by the authors of the language, Kernighan and Ritchie) employed a very simple example as the first program. This program did nothing but print the words "Hello world!" to the screen. This has become the standard first program in almost all programming books, no matter what language they teach. So let's start with the C (and C++) version of the famous Hello World program.


Hello World Program

( 7 )

The Hello World Example Program

We desire to write a program that will print the words "Hello world!" to the screen. In the old days the screen was the entire surface of your video display monitor, but in the Windows operating system it refers to just a single Window.

The program that we need is shown below:

// This is the Hello World program.

#include <stdio.h>

int main()
{
    printf( "Hello world!\n" );
    return 0;
}

Pretty simple huh? If we could compose, compile, and then execute this program we would see the following letters printed in a console-mode window:

Incidentally, you may still be able to find on the Internet this same Hello World program expressed in hundreds of different programming languages here.

So we have our goal: to employ the C or C++ language to instruct the computer to print out some arbitrary text. And we have seen the program that will do it. If we place the text of our Hello World program into a file whose filename suffix is .C then the compiler will automatically treat it as a C language program. If instead we place our code in a .CPP file then the compiler will automatically treat it as a C++ language program. The Hello World program is a legal C program and it is a legal C++ program, so we can go either way. However my intention is to teach you C++ so I will always employ filenames that end with .CPP and hence the compiler will always understand that I am programming in the C++ language.

I am assuming that you have just started CPPIDE and hence are staring at the default CPPIDE window which looks like this:

The first thing you might notice about CPPIDE (which I pronounce "see pied") is that the first choice on the Menu bar is "Project" rather than the customary "File". When you are working with a word processor, the fundamental unit is the file. But when you are developing C++ programs the fundamental unit is the project, which is an entity that will usually need to hold multiple files.

Projects are described by .PRJ ("project") files. Let's go ahead and open our first project. Select the "Open Project..." choice found under the Project menu (or else click on the manila folder pushbutton seen on the toolbar). The ellipsis (the 3 dots ...) remind you that this menu choice leads to a dialog box. In this case it is the customary Open dialog box that any Windows user has seen a thousand times. Use this dialog to navigate into the HelloWorld directory (folder) that I have provided and then select the file named HelloWorld.prj. Remember: every time we open a new project we do so by selecting a .PRJ file. The "Open Project..." menu choice is shown in the following photo:

If you were successful at opening the HelloWorld project then the CPPIDE application will now look like:

We will study the statements that make up this program shortly, but right now I need to explain the operation of CPPIDE itself.

The screen real estate of the CPPIDE window is divided into 3 panes called the Files listbox, the Source code editor, and the Compiler/Debugger message area. These 3 areas are identified in the photo below. Incidentally, you can drag the dividers between these 3 panes to adjust their relative sizes, if you so desire (such dividers are called splitter bars).

The Files listbox will show you all of the editable files that are a part of the currently open project. Hence we can see that the HelloWorld project employs only 1 editable source file, and it is named "Main.cpp". Should you ever create a project from scratch, you will have complete freedom in selecting the file names, but there are rules for the file name suffixes. To indicate to the compiler that this file holds C++ code, you must employ a .CPP file name suffix.

The Source code editor pane shows you the contents of whatever file is selected in the Files listbox. This project only has 1 source file, and the Main.cpp name is selected in the Files listbox, so the Source code editor is currently displaying the contents of the Main.cpp file.

The Compiler/Debugger message area is currently empty because we have not yet performed a compile. While you are composing and compiling your programs this pane displays messages from the compiler. When you enter the debugger this pane will be used to display messages from the debugger.

Of these 3 panes, the only one you directly influence is the Source code editor pane. You can type whatever you want here and it will become a permanent part of that file. You click on the file names shown in the Files listbox in order to switch between the source files and thereby get a chance to edit all of them. The only time that the content of the Files listbox changes is when you open a new project.

So we have loaded a project and don't suspect that we should need to edit any of the source files (there is actually only one: Main.cpp) so we are ready to compile. This is most easily accomplished using the Compile button ( [image]) found on the toolbar, as seen in the photo below:

After clicking on the Compile button the Gnu make process will get busy and instruct the Gnu C++ compiler to compile whatever .CPP files need to get compiled. In this case we just need to compile the Main.cpp file. Compiling can take from seconds to minutes depending upon the speed of your computer and the size of the project you are working on. During this time -- if you are running under Windows 95/98/ME -- you will be able to observe some new icons appearing and disappearing automatically from your Windows task bar. You might be fast enough to read the names: make, cc1plus, as, ld, and cpp0. These are each individual processes (programs) that the compiler invokes (initiates) to compile your program. Be patient during this time until you see the following messages displayed in the Compiler/Debugger message area:

Because the compilation was successful (it generated no errors), the Execute ( [image]) and Debug ( [image]) buttons on the toolbar become enabled. This reminds you that you can only run programs that have been compiled and which didn't produce errors during the compile.

DO NOT click on the Debug button ( [image]) until I tell you (or you'll get confused!). Instead click on the Execute button ( [image]) on the toolbar. You will observe a new console-mode window that displays the text produced by your program and then the message, "Press any key to continue...". This "press any key" message is generated by CPPIDE in order to hold the console-mode window open after the process that owned it has terminated. After you have inspected this console-mode window you can dismiss it by (duh) pressing any key. More precisely, this keystroke must occur at a time when this particular window is the active window which means that it is the one window with its title bar (caption bar) highlighted in blue. The Windows operating system delivers all keystrokes to the active window. We say that the active window has keyboard focus. Because the Windows operating system allows you to have many windows open simultaneously, these windows must all share the keyboard. The window whose title banner is painted in blue is the active window and has the keyboard focus meaning that it is the destination for all keystrokes.

When you initiate a computer program you are asking the computer's operating system to initiate a new process. The operating system will close that process's window when the process terminates, which occurs when execution reaches the end of your program.

You can see this happening by using Windows Explorer (Start/Programs/Windows Explorer) to navigate (browse) to the Debug subfolder of the HelloWorld project. Assuming you accepted the defaults during installation of Computer Science Lab, you will find the HelloWorld executable (HelloWorld.exe) at the following location:

C:\Computer Science Lab\CppIde\HelloWorld\Debug\HelloWorld.exe

Double-click on this .EXE filename and you will observe a black window opening and closing so fast that you don't get a chance to observe its contents.

This behavior was designed into the Windows operating system by Microsoft so we cannot change it. The problem is that console-mode programs are intended to be run from the console, which is that "MS-DOS Prompt" or "Command Prompt" window that I have already shown you. Go ahead and select "Start/Programs/MS-DOS Prompt" (assuming Windows 95/98/ME) and then use the cd command to navigate to the folder where you chose to install Computer Science Lab. Continue into the CppIde folder and then into the HelloWorld folder and then into the Debug folder. Assuming you accepted the default installation options offered by the Computer Science Lab SETUP program then the necessary sequence of cd commands is:

    cd \
    cd "Computer Science Lab"
    cd CppIde\HelloWorld\Debug

Once you have arrived at the Debug folder you can employ the dir command to observe that this folder holds a file called HelloWorld.exe. This is the executable file that we just prepared. You can now run this console-mode program by typing its name (with or without the .EXE suffix). You should observe something similar to the following:

The output from the program, the words "Hello World!" can be seen near the bottom of this window. The directory path seen in this photo assume that you accepted the default installation of Computer Science Lab. Don't worry if you have trouble with these cd and dir commands: this is part of the old MS-DOS operating system and won't be important to us as we proceed forward.

Let's take a look at the HelloWorld.prj file. Normally you don't need to inspect the contents of a .PRJ file, but here is what they hold:

[Editable Files]
File1=Main.cpp

[Project Type]
Type=console

[Makefile]
File=HelloWorld.mak

[Executable File]
File=HelloWorld.exe

You can see that .PRJ files consist of sections, with the section identifiers enclosed in square brackets. Each section can then have any number of lines of the type "A=B". The "Editable Files" section lists the filenames that will be shown in the Files listbox when you open this project. So we can again observe that the HelloWorld project contains only 1 editable file, and it is named Main.cpp. The "Project Type" section will hold either "Type=console" or "Type=windows". Right now we are learning about console-mode programs which are textual. You will have even more fun when we get to the Windows programs which offer a full graphical user interface (GUI).

The "Makefile" section of the .PRJ file lists the name of the file used by the Gnu make process. The Gnu project is the crowning achievement of the Free Software Foundation, which is the largest source of free (open-source) software in the world. Gnu make is a utility that makes sure you don't waste your time compiling files you don't need to compile. The HelloWorld project contains only 1 editable file and hence whenever we change Main.cpp we are going to have to recompile. Later projects will contain multiple source files. When we change one of these files we may just need to recompile it, or we may need to also recompile additional files. This is determined by how the files depend upon each other. A makefile is a file written in a specialized language that describes how various source files depend upon each other and what you need to do to compile them into executable files. If you don't employ makefiles you have to recompile all of your source files every time you make a change to any of them. The use of makefiles saves you time because the Gnu make utility will intelligently only recompile the minimum number of files. We will be able to observe this in our later projects.

The final section of the .PRJ file identifies the name of the executable file. In the Windows operating system all executable file names must end with an .EXE suffix. It is this file that is run when you click on the Execute ( [image]) button on CPPIDE's toolbar. When you installed the contents of the Computer Science Lab CD-ROM onto your computer I gave you a bunch of .CPP source code files organized into a bunch of projects, none of which had any executable files. Only by compiling the .CPP files will you obtain the .EXE files that will allow you to run these programs on your computer.

Open up a Windows Explorer window (i.e., select "Start/Programs/Windows Explorer") and navigate to the HelloWorld folder. Immediately after you installed Computer Science Lab this folder held only the 3 files: Main.cpp, HelloWorld.prj, and HelloWorld.mak (the makefile). But at this time you observe that this folder holds a new "Debug" folder. And inside this Debug folder you can now find HelloWorld.exe (the executable) and Main.o (an object file). The Main.o file is an intermediate file produced by the C++ compiler while the HelloWorld.exe file is the big kahuna, our ultimate goal: a file which can -- by itself -- be transported to another Windows computer and run there to produce the exact same results.

These two derived files are placed into a "Debug" subfolder because CPPIDE defaults to performing a Debug build. When you compile a Debug build you are including extra information in your executable for the benefit of the debugger (which we will see shortly). CPPIDE offers an option that allows you to request a Release build which does not include the special debugger info and hence is more appropriate for distributing to your customers. When you request a Release build the executable (.EXE) and object (.O) files will be placed into a "Release" subfolder.

I don't want to overwhelm you with details about the C++ compiler. You can learn this stuff later, after you have had some fun writing programs. But I want to quickly explain the food chain from a .CPP file to an .EXE file. Back when we clicked on the Compile ( [image]) button on the CPPIDE toolbar we observed the following messages in the Compiler/Debugger message area:

initiating compile, be patient...
mkdir Debug
g++ -g -D_DEBUG -c -o Debug/Main.o Main.cpp
g++ -o Debug/HelloWorld.exe Debug/Main.o
make finished normally.

CPPIDE employs the Gnu C++ compiler and this compiler resides in an executable file named g++.exe. In Windows, you can initiate an executable by typing its name with or without the .EXE suffix. So the second and third lines of text seen in the Compiler/Debugger message area are places where we invoke the C++ compiler. The first time we invoke the C++ compiler we ask g++ to produce a file named Main.o by compiling the file named Main.cpp. So this is where the Main.o object file comes from. Each .CPP file must become a .O file and then all the .O files are further processed into an executable. The second time we invoke the C++ compiler we ask it to process all the .O files (in this case that's the 1 file: Main.o) and create the executable HelloWorld.exe. So now you see where both of the new files came from. Once you have created the .EXE file you really don't need to keep the .O files. You can remove them by selecting the "Build/Make Clean Objs" choice from CPPIDE's menu. There is also a "Build/Make Clean" menu choice which is a superset of "Build/Make Clean Objs" in that it not only removes all the .O files but it also removes the .EXE file.

The Gnu C++ compiler is a tremendously complex program and it supports a very large number of command-line switches such as the "-g", "-c", and "-o" switches that we can observe in the Compiler/Debugger message area. I would not get sidetracked right now reading about these things, but I have provided an appendix that describes the compiler options that my makefiles employ and another appendix that describes the language of makefiles. The complete manuals for Gnu make and the Gnu C++ compiler can be found at: www.fsf.org/manual/manual.html. Note that you use the manual for the Gnu C compiler (gcc) as the manual for the Gnu C++ compiler (g++) since it is actually all the same compiler. In the meantime, CPPIDE will insulate you from this complexity. All you need to do is just click on the Compile button!

A final warning: the Windows operating system will not allow the compiler to replace the .EXE file with a new version if the prior executable is currently running as a process on the computer. You must close (shut down) all instances of your program before you can re-compile it or you will get the not very descriptive "Error 1" error message.


Hello World2 Program

( 8 )

The Hello World 2 Example Program

While preparing these materials I spent some time perusing the Internet newsgroups (e.g., comp.programming, alt.comp.lang.learn.c-c++, comp.lang.c++, etc.) where newbies post questions and hope some kind-hearted pro will explain their problem. I was amazed how often I saw a posting asking: "My program's window opens and closes so fast I can't read it! What can I do to prevent this?"

I have shown you that CPPIDE offers a feature that holds all console-mode windows open until you explicitly dismiss them. Before we dive into the C++ language itself I'll show you the 3 lines you can add to any console-mode program and achieve this same effect.

The problem is that the Windows operating system assumes the window is not needed once the console-mode process has completed, and hence closes it. A process is a running program (an executable file that is currently executing). The Windows operating system is maintaining a tight correlation between the process and the window that displays its results. Why keep the window around when the process has died? To keep the window open we just need to give the program (process) something to do so it won't end before we want it to. The solution is to add the following statement to the very top:

#include <conio.h>

and the following 2 statements to the very bottom of any console-mode program you write:

printf( "Hit any key to continue..." );
_getch();

Since I haven't begun to explain C++ itself, these statements will have to remain a mystery. But I will tell you that the _getch( ) statement makes a call to a function that waits for some key to be pressed on the keyboard. Until you press a key, the _getch( ) ("get character") function can't return and hence your program can't conclude and hence Windows (the operating system) can't close your window.

You can find the Hello World program modified in this manner in the HelloWorld2 folder. Go ahead and open that project (that is, select Project/Open Project and then navigate to the .PRJ file in the HelloWorld2 folder). Compile this program in order to build an executable. Then click on the Execute button ( [image]) on the toolbar. A new console-mode window will open and contain the following:

When you do hit a key, this window will then change to:

Obviously we don't need to employ this trick in the console-mode programs we run from CPPIDE. But this is a good technique to employ when you intend to execute console-mode programs from Windows Explorer. Go ahead and use Windows Explorer to initiate the HelloWorld2.exe file found in the Debug subfolder of the HelloWorld2 project.

Are you brave enough to modify the Hello World 2 program to print out your own name? Try this but limit your changes to the program to the characters between the double quote marks. And don't employ either the backslash character (\) or the percent sign (%) within these double quotes because these characters have special meaning. For example, the 2 character sequence \n is a special symbol meaning "print a carriage return now". Go ahead and try to use this special 2 character symbol to print out a multi-line message. Remember that each time you change the source you must recompile before you can execute the new program.


Language Basics

( 9 )

The Basics of the C and C++ Languages

Okay, we are finally ready to dive into the C/C++ language itself. Just as we saw with assembly language, a high-level program is composed of statements. Any statement can be assigned to one of the following 3 basic categories:

  • executable statements
  • preprocessor statements
  • comment statements

You are free to write as many comment statements as you wish. In the example programs that I provide you will notice a lot of comments. They help to explain the operation of your program to whoever it is that looks at it next. This will often be you and you will be grateful for the comments if it's been a couple years since you lasted looked at the code.

There are two ways you can create comments. A single-line comment is created by a pair of slashes. Anything on a line after a pair of slashes is ignored by the compiler (8051 assembly language used a semicolon for this purpose). A block comment is a multi-line comment which must begin with the 2 characters /* and eventually end (perhaps many lines later) with the 2 characters */. The compiler will ignore everything between the /* and the */ characters. You will often see me use the following format for my multi-line comments but this is a personal style issue and is not required:

/* This is a multi-line
 * comment that stretches
 * over many lines.
 */

In 8051 assembly language we had "assembler directives" which were instructions to the assembler that did not result in actual operations by the microprocessor. In the C/C++ language this need is met by the preprocessor, which is a separate program that inspects our code before the compiler. All commands to the preprocessor begin with a pound symbol # and must be on a line by themselves. Hence we can see the use of one preprocessor statement in the Hello World program. Here is an excerpt from that program:

#include <stdio.h>

printf( "Hello World\n" );

The first statement in this excerpt is a command to the preprocessor. We are asking the preprocessor to include the contents of a particular header file in our program. This is the same purpose as the INCLUDE statement that we saw in 8051 assembly language.

When you write a C or C++ program, you will be creating some number of .CPP and .H files. Most of your code goes into the .CPP files (this filename suffix stands for "C plus plus"). But you also have to write a few header (.H) files. The stdio.h header file is already written for you and is supplied with the C++ compiler. This particular header file holds a declaration for the printf( ) function. We have to include this declaration in our source code since we want to employ the printf( ) function. You can see that we do make a call to the printf( ) function in the excerpt above. We will learn more about header files and declarations shortly.

The last general category of statements is the executable statement. This is the bread-and-butter statement of a programmer. It is via some artistic arrangement of executable statements that you accomplish your programming goal. These statements accomplish the algorithm that solves the problem you are attacking. In general in the C and C++ language, executable statements must be terminated with semicolons (but there are exceptions).

Back when I was contrasting different high-level programming languages I said that C and C++ gave the programmer an incredibly expressive language. What this really means is that there is an unlimited number of legal executable statements (there is only a small number of preprocessor statements). My 8051 IDE was able to provide you with a list of all of the generic forms of all of the possible 8051 assembly language statements. No such list is possible with the C++ language.

To understand what makes for a legal executable statement, you must understand the 4 following topics:

  • variables
  • data types
  • control structures
  • functions

Variables are like mailboxes: they have a name and they have contents. You get to choose their name and you get to choose their contents. When you choose the data type for your variables you are specifying the legal range for their contents. For example, a mailbox for a number can't hold a sentence, and vice versa. The Hello World program is so simple that it does not employ any variables so I will defer discussion of variables and data types until the next page.

The Hello World program has only one control structure and that happens to be a block. A block is any section of statements placed inside curly braces (you know, { and }). The block that we see in the Hello World program happens to be the body of a function.

The Hello World program defines a single function, called the main( ) function. You get to choose the names for all the functions you write except for the main( ) function. Every program must include a main( ) function because this is where your program starts. That is, when you request that the operating system run your program, the operating system will load your program into memory and then begin executing the instructions found inside your main( ) function (hence the name). You will notice that whenever I write the name of the main( ) function I include a pair of empty parentheses. This notation reminds us that the identifier "main" refers to a function. If we also had a variable named main then we would refer to it as "main" without the parentheses.

Functions are the big deal of high-level programming languages. You will love them. Remember when we discovered a bug in the RPN Calculator example program named BadCombination.txt? This bug occurred because two different sections of our program thought they had exclusive use of a hardware resource (the stack) that was in fact shared by them both. I promised you that high-level languages would solve this problem. Functions are the solution. We will see that functions allow different sections of your program to be isolated from all your other code.

The very first line of any function declares its signature. The first line of the main( ) function is the following:

   int  main()

This signature tells us that the function is named "main", that it takes no passed parameters (since the parentheses are empty), and that it returns an integer. The name, passed parameters (also called formal parameters or arguments), and return value together constitute the signature of a function.

Here is the signature of a function named Square( ) that takes (accepts) a single integer called i and returns an integer.

    int  Square( int i )

We will complete the invention of the Square( ) function in a later section after we learn about variables and data types. But in the meantime let's keep looking at the Hello World program.

After the signature for the main( ) function we see the body of the main( ) function. The body of any function is enclosed within curly braces. The code which is placed in the body may (but doesn't have to) employ the passed parameters. Execution will leave the function once a return statement is encountered. This return statement must return a value of the type declared in the function signature as the function's return type. The main( ) function is supposed to return an integer and we do indeed see that the "return 0;" statement returns the integer value 0.

There is only one other statement in the body of this particular main( ) function and that is a call to another function, the printf( ) function (the name printf stands for "print function"). When calling this printf( ) function, our Hello World program passes it the literal string "Hello World!\n". This is a literal string because it is characters enclosed in double quote marks (single quotes have another purpose in the C/C++ language). The printf( ) function accomplishes the work of printing these letters to the screen.

So who wrote the printf( ) function and where is it? The printf( ) function comes with the C (or C++) compiler as part of the standard library. The language designers wanted to provide you with as much infrastructure as possible so they have already written hundreds of functions like printf( ). You can use any of these functions as long as you include the necessary signature (also called the function prototype) so the compiler can understand the particulars of the function. Our Hello World program is allowed to call the printf( ) function anytime after the "#include <stdio.h>" statement, since the stdio.h header file includes the function prototype for the printf( ) function. Note that this #include statement is a preprocessor directive and preprocessor directives DO NOT end with semicolons.

Now let me point out one of the really nice features of CPPIDE: automatic syntax color coding. As you type in your program the Source code editor will automatically select an appropriate color for your statements. You can observe with the Hello World program that comments are shown in green, language keywords such as #include, int, and return are shown in blue, character strings (characters inside double quotes) are shown in red, and numbers are shown in purple. This color coding will help you spot errors in your code by helping your brain to quickly recognize the structure of your code. As an example, let's suppose you meant to employ a multi-line comment but then forgot to terminate the comment with the necessary */ characters. In this situation the compiler will treat all the code in the multi-line comment as a comment, and will continue to treat all subsequent code as more of the same comment, until it finally comes to your next multi-line comment where it discovers the */ characters that end that later multi-line comment. So basically, there would be a huge chunk of your program that the compiler was ignoring. This program might even compile but it would not run correctly. If your source code editor displayed all the text in black, this situation would be very hard to spot (people my age have spent countless hours tracking these types of bugs down). But if a great big section of your program was displayed in green your brain would instantly say, "That's messed up!"


Variables

( 10 )

Variables and Data Types

In order to write a more useful program than the Hello World program, we are going to need to employ variables. A variable is the name associated with a value that can change throughout a program's execution. Consider the code fragment shown below and imagine that it is located inside a loop:

    Result = 2 * Operand;

Operand and Result are both variables. Every time this statement is executed the value of the Operand variable may be different, and hence the value of Result will keep changing as well. The number 2 seen in this statement is an example of a constant. The value of a constant does not change. The use of variables and a loop is what allows a 10 statement program to generate an unlimited number of results.

You can probably guess that the mechanism that allows variables to keep changing their value is that a variable is merely the name assigned to a data memory location. The value of the variable changes whenever the data memory location is rewritten (a.k.a. re-initialized or re-populated).

Variables are like mailboxes: they have a name and they have contents. You get to choose their name and you get to choose their contents. The rules for a legal variable name are exactly what we saw in 8051 assembly language: a legal variable name must start with an alphabetic character or the underscore character and can then continue with alphabetic characters and numeric characters and underscores. The total length of the variable name should not be more than 31 characters. Variable names are case sensitive so the variable names "Result" and "RESULT" are distinct from each other. By the way, these rules for variable names are really the rules for identifiers, and another example of an identifier is a function name.

When we first started programming the 8051 microprocessor in assembly language, we employed the actual data memory addresses in our programs. For example, when we developed the Walking Triangle example program we wanted to keep jumping to a "Loop" label until the contents of data memory location 0 had been decremented to 0. Initially we coded this as:

    DJNZ  0, Loop

But in any large program where it is difficult to keep track of how all the data memory locations are being used, it helps to assign labels to the data memory locations so that you can refer to them symbolically in your program. In assembly language this is the purpose of the EQU directive. Once we had added the following declaration to our Walking Triangle program:

LoopCounter   EQU   0

we could then employ the label "LoopCounter" wherever we previously employed the data memory address 0. The DJNZ statement could then be rewritten as:

        DJNZ  LoopCounter,Loop

which makes it a lot clearer that the purpose of this statement is to keep branching back to the Loop label until the contents of the LoopCounter data memory location reach 0.

In 8051 assembly language the EQU statement is sufficient to create what we call a variable in a high-level language. But no high-level language employs EQU statements because they only declare an address: a high-level language variable also requires a data type. We didn't have to deal with the concept of data types in 8051 assembly language because there was only 1 data type: the byte. The 8051 microprocessor is a byte oriented machine meaning that bytes are the only thing it can process. We didn't have to introduce the concept of data type with our RPN calculator because it too was only able to deal with 1 data type: the floating point number (numbers such as 1.23x103). But all modern 32 bit microprocessors such as the one inside the PC in front of you can process a collection of different data types.

For example, an x86 microprocessor can process BYTEs (unsigned 8 bit wide numbers), WORDs (unsigned 16 bit wide numbers), and DWORDs ("double words" or unsigned 32 bit wide numbers). I show these names capitalized because that is the way we will employ them in the C and C++ languages (most high-level languages are case sensitive, remember). Furthermore, an x86 microprocessor can perform a different type of multiplication when the two operands are signed and when the two operands are unsigned. Hence there are the additional data types char (for signed 8 bit numbers), short int (for signed 16 bit numbers), and int (for signed 32 bit numbers). [I am simplifying here: the C and C++ languages actually define an int as the native width of the machine, but for all modern desktop computers this means that an int will be 32 bits.] A char variable can have a value in the range from -128 to +127 (the asymmetry is due to the use of two's complement numbers as we learned while studying the 8051 microprocessor). A short int variable can have a value in the range from -32768 to +32767. An int variable can have a value in the range from -2,147,483,648 to +2,147,483,647.

And there are still more data types. A modern 32 bit microprocessor can deal with an individual character from the English language (or most European languages) using a variable of the type char which is 8 bits wide. An individual character from the Asian languages that have immense alphabets can be held in a variable of type wchar_t which is 16 bits wide. A Boolean variable that can only have the two values "true" or "false" can be held in a variable of type BOOL. A floating point number such as 1.23x103 can be held in a variable of type float (32 bits wide) or double (64 bits wide). Obviously you attain greater precision (while using up more memory) when you select to use doubles instead of floats.

There are still other data types, but let's stop with these as we will be able to write an incredible profusion of programs just using these.

The entity that in a high-level language replaces the EQU statement found in assembly language is the declaration statement. You are required to explicitly declare the data types of all the variables you use in your programs. As an example, the first line of the code fragment below declares two variables named Byte1 and Byte2 which will hold unsigned 8 bit quantities. The second line of this code fragment declares a variable named Letter that holds a single character such as any one of the characters on the keyboard. The third line declares a variable named BigIntegerNumber that holds a signed 32 bit integer. The fourth line declares a variable named SmallerIntegerNumber that holds a signed 16 bit integer. The fifth line declares a variable named RealNumber that holds a 64 bit floating point number.

    BYTE       Byte1, Byte2;
    char       Letter;
    int        BigIntegerNumber;
    short int  SmallerIntegerNumber;
    double     RealNumber;

Variable declarations such as these also define their variables. A definition causes memory to be set aside to hold the variable. All definitions are also declarations but all declarations are not definitions. A declaration merely states for the compiler the fact that you want to employ an identifier such as Byte1 to refer to a variable with a certain data type. We will see later that when our programs get so big as to need multiple files to hold all the code, one of these files will typically have the definition for the variable and the other file will have just a declaration (if the second file also defined the variable then we would be double allocating memory to hold the variable).

Once you have defined a variable you can then begin to employ it. Almost always, the first thing you need to do is to initialize the variable. Initialization is the process of placing the first value into your variable. The following code fragment shows how to initialize the variables we just declared:

    Byte1 = 7;
    Byte2 = 0xA3;   // 0x prefix indicates hex
    Letter = 'a';   // single quotes indicate a single letter
    BigIntegerNumber = 321247655;
    SmallerIntegerNumber = 17;
    RealNumber = 1.23e3;

Now that these variables have a value, you can employ them in your program, perhaps as is seen in the following code fragment:

    int   Result = 2 * BigIntegerNumber;

This statement declares a new variable named Result and initializes it, all in one statement. This is perfectly legal. What is not legal is to attempt to employ on the right-hand side of an equal sign a variable that you have not previously declared.

Note that single quote marks are required around the single letters that initialize variables of type char. These single quote marks tell the compiler to substitute the ASCII value (which is just a number smaller than 256) for the letter. If you happen to know that the ASCII value for 'a' is 97 (0x61) then you could replace:

   Letter = 'a';

with

   Letter = 97;

or

   Letter = 0x61;

There is another data type which is an array of characters which can hold a complete sentence. An array of characters is called a string. Double quote marks are used around strings.

The equal sign = is used in computer programming languages in a way that must infuriate mathematicians. When you need to increment the value of a variable you write:

    Byte1 = Byte1 + 1;

To the mathematician this statement is nonsense since there are no values for the variable Byte1 that makes this a true statement. But to the compiler this statement simply means: "initialize the variable listed on the left-hand side of the equals sign with the value of the expression listed on the right-hand side". Note that you are only allowed to use a variable name (not an expression) on the left-hand side but you are free to use an expression (meaning a compound operation involving any declared variables and constants) on the right-hand side.

The above statement is so common that the C and C++ languages offer two shorthand notations that mean the same thing:

    Byte += 1;
    Byte++;

I am now going to pretend the compiler is a sentient being and describe its thought process as it encounters the following statements:

    int    Value1;
    int    Value2 = Value1 + 2;
    int    Value3 = Value4 + 3;

In response to the first statement the compiler says: "Okay, I see that the programmer wants me to set aside enough data memory to hold a 32 bit signed integer so I choose the 4 bytes (32 bits) at data memory address 0x700f324b. Furthermore he wants to refer to this data memory location using the identifier Value1. He is not giving me any initial value to place in this variable so it is going to continue to hold whatever it held previously (it happens to be holding the number 74). So be it."

In response to the second statement the compiler says: "He wants another integer valued variable to be known as Value2. For this I select the 4 bytes at data memory address 0x2237cc02. Furthermore, he wants me to initialize the contents of this variable with 2 larger than the current value held in the Value1 variable. Well let me go get that value, oh, here it is, that data memory address is currently holding the value 74. So I will initialize the contents of data memory address 0x2237cc02 with the value 76. So be it."

In response to the third statement the compiler says: "He wants another integer valued variable to be known as Value3. For this I select the 4 bytes at data memory address 0x37dad42d. Furthermore, he wants me to initialize the contents of this variable with 3 larger than the current value held in the Value4 variable. Well let me go get that value, uh oh, I have never heard of any variable called Value4. I am throwing my yellow flag on the field right now and declaring an error."

So in conclusion, this 3 statement program fragment would not compile. If we delete the third statement, then the remaining 2 statements would compile and you could run the program. But the program almost certainly contains a bug because it employs the contents of the Value1 variable without first having initialized these contents. Every piece of data memory will always have a value. If your program does not initialize the location then you will be employing whatever happened to be the prior value at that address. This value is almost certainly garbage (yes, that's the technical term).

I also want to point out that it is the compiler rather than the programmer who selects the actual data memory addresses employed for the variables. That is, the declaration:

    int    Value1 = 3;

specifies a data type, a variable name (identifier), and an initial value; but it does not specify the address. When you choose a high-level language you are agreeing to let the compiler select the actual addresses for all of your variables. You can't influence that choice but you can ask the compiler after the fact to tell you what choice it made (but usually you don't need to).

All modern programming languages are free-form which means that you are allowed to add white space most anywhere you like. White space consists of space characters (produced by the spacebar on your keyboard), TAB characters, and newline characters (produced by the ENTER key on your keyboard). So the following version is perfectly legal even though it looks like muck:

 BYTE   Byte1,          Byte2     ;
    char                     Letter;
int
     BigIntegerNumber;
   short    int
SmallerIntegerNumber ;
                          double        RealNumber;

Take a look at what didn't change though. Every statement must end with a semicolon. This code fragment consists of five declaration statements and hence we see five semicolons. A comma must separate the items of a list, as we see between Byte1 and Byte2. And a minimum of a single space (or other white space character) must separate all keywords from the words we choose. In this code fragment the keywords are BYTE, char, int, short, and double. Keywords are those words that were selected by the designers of the language to have special meaning. Because int is a keyword we aren't allowed to give one of our variables the name int.

Here is a complete list of all the keywords employed in the C language. It is a surprisingly small list given the incredible power of the language.

    auto      double   int        struct     while
    break     else     long       switch
    case      enum     register   typedef
    char      extern   return     union
    const     float    short      unsigned
    continue  for      signed     void
    default   goto     sizeof     volatile
    do        if       static     wchar_t

The C++ language adds a few more keywords, but the only ones we will deal with are:

    class   delete   new   private   protected   public

I have already mentioned that identifiers (such as variable names and function names) are case sensitive. The same is true of keywords: all keywords MUST employ only lower-case letters as shown above.

If you are paying attention you will be wondering why BYTE, WORD, and DWORD have not been listed as keywords. These symbols are always shown in all upper-case letters because they were defined by Microsoft and are not actually part of the C or C++ language. The typedef statement allows anyone to create a new alias for a keyword. Microsoft used the typedef statement to define BYTE, WORD, and DWORD as follows:

typedef   unsigned char       BYTE
typedef   unsigned short int  WORD
typedef   unsigned int        DWORD

The convention is to employ upper-case letters for any user-defined type such as BYTE, WORD, or DWORD.


( 11 )

Functions

Now that we know about variables, we can learn more about functions. A function is an arbitrary collection of statements that you choose to package together into an entity because you expect to find it convenient to call into (that is, execute) this code at multiple times during the running of your program.

Every function has a name and a body. The name of a function is a part of its signature which is the first line of the function. The function's body is the portion inside the outermost pair of curly braces and this is where you put the executable statements. Here is an example of a function:

int  Square( int i )
{
    int   Result = i * i;  // compute the square of the passed param i

    return Result;         // return this new value to the caller
}

The signature of the Square( ) function is shown below:

    int  Square( int i )

This signature states that the function is named Square( ) and that it takes (accepts) a single integer called i and returns an integer. The return value does not have a name, only a type. This taking and returning of values is how functions interact with the rest of your program. Every time you initiate the function you provide it raw materials and it returns a result. These inputs and outputs are shown in the following figure:

The inputs to a function are called its passed parameters or its formal parameters. When a function accepts more than 1 input the function's signature will include a list of passed parameters, separated by commas.

Although a function can accept many inputs, it can only return a single value. The return statement declares the value returned to the calling code. The data type of the value returned by the return statement must agree with the data type specified as the return type of the function in the function's signature. When the flow of execution reaches the return statement, the function exits. Even if there were additional statements beyond the return statement, they will not execute. It is legal to place any desired number of return statements in a function.

Anytime after we have declared the Square( ) function, we can employ it by calling it, as shown below:

   int  Answer = Square(3);

This statement declares a new variable of type int which gets initialized with the value returned by the Square( ) function when the Square( ) function is called with the input value of 3. Hence the variable named Answer will be initialized with 9. Remember how we employed the CALL statement in 8051 assembly language to call subroutines? "Call" is not a keyword in the C/C++ language, instead you just employ the function's name in the call statement.

Did it bother you when I said that the value returned by a function had no name? Look again at the statement above which calls the Square( ) function and uses the value it returns to initialize the variable named Answer. The equals sign transfers the value returned from a function to a variable. The variable has a name, of course, but the return value itself does not need a name since we access it using the equals sign.

You can call the same function as many times as you like, possibly changing the passed parameters each time. Shown below is a code fragment where we call the Square( ) function as part of an initialization statement and then again as part of an assignment statement. The first statement below is an initialization statement because a new variable named Answer is being defined in that statement. The second call to the Square( ) function is an assignment statement that changes the value of the existing Answer variable.

   int  Answer = Square(3);
   printf( "The first answer is %d\n", Answer );
   Answer = Square(4);
   printf( "The second answer is %d\n", Answer );

To declare a function you merely need to list the function signature followed by a semicolon. A function declaration is also called a function prototype. So the following statement declares the Square( ) function:

    int  Square( int i );

It is pretty easy to see why you have to declare data variables, since this is how you tell the compiler about a new name you have chosen and its associated data type which determines how much data memory the compiler must set aside. Functions are executable statements and hence don't reside in data memory. They reside in program memory. But the compiler must still know about the name of the function and its input types and output type before you employ that function. The need to declare your functions is actually a great benefit because it allows the compiler to spot and alert you to many types of errors. For example, anytime after the compiler has seen the declaration for the Square( ) function it will then know to flag an error if it sees something like this:

    BYTE  MyByte = Square();

You might expect the compiler to complain about the fact that this statement uses a 32 bit integer returned by the Square( ) function to initialize an 8 bit value. Depending upon the particular 32 bit value, this could cause truncation. But you would be wrong. The compiler does not complain about this fact. Instead the compiler complains because the Square( ) function is not provided with the one passed parameter it requires.

The fact that the compiler allows you to perform a dangerous statement that may cause truncation, is an example of the latitude allowed you by the C and C++ languages. The compiler always assumes you know what you are doing, and hence follows your orders. Perhaps you happen to know that the Square( ) function will never be called with a passed parameter larger than 16 and hence the value placed into MyByte will never get truncated. Other languages (most famously, Pascal) refuse to allow you to employ statements such as this in your programs. The C language gives you the freedom to shoot yourself in the foot.

You will get a lot more comfortable with C language statements, variables, and functions, as we proceed through the next sequence of example programs.


Factorial Example Program

( 12 )

The Factorial Example Program

Since we looked at a factorial program with our RPN calculator and again with our 8051 microprocessor, it will be fun to see how a high-level language would compute the factorial of a number. Remember that the factorial operation is defined for a positive integer n as:

    n ! = n * (n-1) * (n-2) * ... * 2 * 1

As we have seen before, it is convenient to employ a loop to compute a factorial. For example, to compute 5! we could initialize a variable to the value 1 and then enter a loop where we multiply that variable by 5 during the first loop iteration, and multiply that result by 4 during the next loop iteration, and so on until we have multiplied by 1. So our algorithm can be summarized as shown below:

- initialize the variable "Result" to the value 1
- then execute the following loop:

  iteration    perform the operation
  ---------   --------------------------------------------
      1       Result = Result * 5 hence Result = 5
      2       Result = Result * 4 hence Result = 5*4
      3       Result = Result * 3 hence Result = 5*4*3
      4       Result = Result * 2 hence Result = 5*4*3*2
      5       Result = Result * 1 hence Result = 5*4*3*2*1

We will need to use a variable to control the loop (to count out the iterations). This variable would increment from 1 to 5. We will need another variable to hold the value that we multiply with Result. This variable would decrement from 5 to 1. Have you seen enough programming yet to spot that we could combine these 2 variables? Why not have the loop be controlled by a variable that decrements from 5 to 1? This variable could serve double-duty as it will also hold the value we need to multiply with Result. So our improved algorithm is:

- initialize the variable "Result" to the value 1
- initialize the variable "n" to the desired operand (5 in
   our example)
- then execute the following loop:

      n        perform the operation
  ---------   --------------------------------------------
      5       Result = Result * n hence Result = 5
      4       Result = Result * n hence Result = 5*4
      3       Result = Result * n hence Result = 5*4*3
      2       Result = Result * n hence Result = 5*4*3*2
      1       Result = Result * n hence Result = 5*4*3*2*1

When we replaced our original algorithm with our improved algorithm we were performing step-wise refinement. This is one of the real joys of computer programming: when you edit your code to fix a bug or adopt an improved algorithm there is absolutely no trace left of the older program. As far as the world can tell, you were a genius to begin with! Oil painters get to enjoy this same benefit but not woodworkers or diamond cutters. Since it is so easy to reach perfection, let's make yet another improvement: let's not bother performing the last iteration of the loop where we multiply by 1, as that's pointless. This final algorithm is coded in the Factorial example program.

At this time load the Factorial project into CPPIDE. That is, select "Open Project..." from the Project menu (or else click on the manila folder button on the toolbar) and then navigate into the Factorial directory (folder) and select the Factorial.prj file. The Files listbox in CPPIDE will then show you that this project consists of a single editable source file called Main.cpp. I keep using this filename since these files hold the main( ) function. The content of the Main.cpp file will be displayed in the Source code editor. This content is shown below, but it actually looks better in the Source code editor pane of CPPIDE since there it benefits from syntax color coding.

// This is the Factorial C++ program.
// It can only do one thing and that is
// to print out the value of 5 factorial
// (5! = 120).

#include <stdio.h>

int main()
{
    int     TheOperand;         // define a variable which has the
                                // "data type" of integer
    int     TheFactorial;       // define another integer

    TheOperand   = 5;           // initialize the first integer
    TheFactorial = 1;           // initialize the second integer

    while ( TheOperand > 1 )    // a while loop causes iteration
    {
        // The following statement iteratively computes the
        // desired factorial. During the first iteration of
        // the while loop this statement computes 1 * 5
        // since the TheFactorial variable had previously
        // equaled 1 and the TheOperand variable equals 5.
        // During this same iteration of the while loop
        // we decrement the value of TheOperand.  Hence
        // during the second iteration the statement below
        // computes (1*5) * 4. During the third iteration
        // it computes ((1*5)*4) * 3. When the while loop
        // terminates TheFactorial will hold the value 1*5*4*3*2.

        TheFactorial = TheFactorial * TheOperand;

        // The following statement decrements the value held in
        // the variable TheOperand during each iteration of the
        // while loop.  When TheOperand reaches the value of 1 the
        // while loop will terminate.

        TheOperand = TheOperand - 1;
    }

    // Display the results:

    printf( "5 factorial = %d\n", TheFactorial );

    // You use "return" statements to exit from functions.  When
    // we exit from the main() function the program terminates.

    return 0;           // since the main() function is declared to
                        // return an int, we must return an int
}

Most C/C++ programs will have more than 1 function. But every C/C++ program must have a function called main( ) because that is where execution starts. This is the only function name you don't get to choose.

Just like any other function, the main( ) function has a function signature. It is:

int main()

This signature states that the main( ) function takes no inputs (passed parameters) and returns an integer. Therefore every return statement seen in the main( ) function must include an operand having a data type of int. This is why there is a "return 0;" statement at the end of the main( ) function.

When the code that you write makes a call to a function, you have the option of employing or tossing the value returned by that function. But you don't call the main( ) function, instead the operating system calls it to get your program started. So what value should you return to the operating system? It is conventional to return 0 if your program succeeds and some other value if your program fails.

When we programmed the 8051 microprocessor we just started writing code at program memory address 0, knowing that that is where the microprocessor will start to execute once it is released from being reset (typically, when power is applied to the chip). In 8051 assembly language we had the option of placing code inside subroutines which we could branch to with a CALL statement.

In the C/C++ language, subroutines become functions, and ALL code must reside inside a function. Execution will start in the main( ) function and will proceed into whatever other functions are called from the main( ) function.

Look at the start of the main( ) function and you will observe some variable definitions:

  int    TheOperand;       // define a variable which has the
                           // "data type" of integer
  int    TheFactorial;     // define another integer

A variable definition is a variable declaration that also reserves memory for the variable. Continuing on with the program, we next come to some variable initializations:

  TheOperand   = 5;        // initialize the first integer
  TheFactorial = 1;        // initialize the second integer

and then you come to a while loop. This is the first example we have seen of this control structure. A while loop is a convenient way to accomplish iteration. A while loop consists of a test followed by a body. The test is any expression that results in a Boolean value. The body is any code surrounded by curly braces. The test for this particular while loop is:

    while ( TheOperand > 1 )

Note that TheOperand is an integer valued variable and 1 is an integer valued constant. But "TheOperand > 1" is a Boolean expression that evaluates to either true or false. The greater-than operator takes two numbers and compares them in order to produce a Boolean result. Like any Boolean expression, the expression "TheOperand > 1" can only evaluate to 2 possible values: it is either true or false.

The body of a while loop will be executed every time the test evaluates to true. In our Factorial program, the initial value of TheOperand is 5 (thanks to the initialization statement just above the while loop) and hence the test initially evaluates to true. Therefore the flow of execution enters the body of the while loop. This body consists of:

{
    TheFactorial = TheFactorial * TheOperand;
    TheOperand = TheOperand - 1;
}

During the first iteration of the while loop the first statement in the body will cause the value of TheFactorial to change from 1 (the value to which it was initialized) to 5 and the second statement will cause TheOperand to change from 5 to 4. Since we then reach the closing curly brace of the while loop the flow of execution will return to the test. This test will still evaluate to true and hence we will enter the body once again. But since the body keeps decrementing the value of TheOperand, eventually the test will evaluate to false. When that happens the flow of execution will move on beyond the while loop.

When execution does move beyond the while loop we arrive at the following statement:

    printf( "5 factorial = %d\n", TheFactorial );

We have already seen the printf( ) function in the Hello World program. But that time the printf( ) function took a single passed parameter (the character string "Hello World!\n") while this time it is taking two passed parameters: the character string "5 factorial = %d\n" and the integer variable name TheFactorial. The printf( ) function is one of the few functions that takes a variable number of arguments. It manages this trick because it always receives the first passed parameter and that first passed parameter tells it how many more passed parameters are coming.

The printf( ) function is surprisingly complex, because it has the capability of printing arbitrary sentences that display the current values of arbitrary variables of arbitrary data types. If you want the printf( ) function to display the current value of an integer valued variable you specify %d inside the character string you employ for the first passed parameter. This first passed parameter is called the format and the %d is an example of a format specifier. The printf( ) function will replace the letters %d with the current value of the corresponding passed parameter.

Here is a list of the most commonly used format specifiers, but there are actually many more:

  %d    print a signed integer (be it a char, short int, or int)
  %u    print an unsigned integer (be it a BYTE, WORD, or DWORD)
  %c    print a single character
  %s    print a string of characters
  %f    print a float (32 bit floating point number)
  %lf   print a double (64 bit floating point number)

The characters \n tell the printf( ) function to cause a carriage return at this point in the output that it is generating. It is kind of funny that \n is 2 characters but the carriage return itself is only 1 character. But think about it, we don't want the carriage return to happen at the moment we are typing in our program, we want it to happen when we run our program. So we need a code that will in the future be interpreted as a carriage return. Characters such as the carriage return that you can't print in your program are known as unprintable characters. Another example is the tab character which you specify as \t.

You will get more comfortable with the printf( ) function as you see it used in the example programs.

At this time compile the Factorial program and then execute it using the [image] toolbar button. You should observe in a new console-mode window the following result:

We are going to work through a number of step-wise refinements of this Factorial program, but first let's watch it run in the debugger.


( 13 )

Debugging

Up until now when I wanted you to execute a program I instructed you to click on the Execute button ( [image]) on CPPIDE's toolbar. I am now ready to introduce GDB, which is the Gnu debugger. This is a very powerful tool that allows you to single-step through your programs. You enter the debugger by clicking on the Debug button ( [image]) found on the CPPIDE toolbar. You can do this at any time after you have had a successful compile.

At this time load the Factorial2 example program and compile it. This program is identical to the Factorial program. I have merely deleted some of the comments so you can better see the code while single stepping with the debugger.

Once you have compiled the Factorial2 program you are ready to click on the Debug button ( [image]) to enter the debugger. FOLLOW MY INSTRUCTIONS EXACTLY in this section or you will probably get the debugger into a state that doesn't correspond to the following write-up, and then you'll be confused.

Once you are in the debugger the CPPIDE window changes to:

The Files listbox and the Source code editor panes are unchanged, although the Source code is now read-only so that you can't change it until you exit from the debugger. The bottom pane that used to show the compiler messages now shows messages from (and to) the debugger.

The main difference is the toolbar, which now looks like:

You have your choice of clicking on the toolbar buttons or typing commands directly to the GDB debugger. Every time GDB is ready for another command it prints the "(gdb)" prompt in the Debugger message area. Thus, this pane has become a two-way communication area, capable of both inputting and outputting text.

Anytime that you wish to exit from the debugger and go back to editing your source code you need only click on the Exit Debugger ( [image]) toolbar button.

Most of the toolbar buttons will be familiar to you from the 8051 IDE (integrated development environment). The main differences are that the Stop button ( [image]) will always be disabled and the breakpoint button ( [image]) has now been renamed the Show Breakpoint button since clicking on it no longer generates a dialog where you define a breakpoint, instead it generates a list of all the current breakpoints.

To set a breakpoint on an executable statement simply LEFT click on the gray margin at the far left-hand side of that statement. This will cause a stop sign icon ( [image]) to appear in this gray left-hand margin. To remove that breakpoint you simply need to LEFT click on the stop sign. Whereas with the 8051 IDE you were limited to a single breakpoint, CPPIDE allows you an unlimited number of breakpoints.

Scroll the source code pane until you find the breakpoint that already exists at the first executable statement of the main( ) function. CPPIDE automatically requests this breakpoint for you because it is so important that we can't afford to forget to request it.

The location of the breakpoint symbol (the stop sign) shows that the first executable statement is somehow the opening brace of the function. This is a bit surprising, but remember that the C compiler translates each C language statement into multiple assembly language statements. The opening brace of a function represents the entry into a new execution environment and this transition is accomplished by assembly language statements that don't directly correspond to any of the C language statements you typed other than the opening brace.

Every time you enter the debugger, CPPIDE issues a short list of commands to the debugger on your behalf. You can see these initial commands in the Debugger message pane. The command that caused the stop sign symbol to appear near the top of the main( ) function is the GDB "break" command:

b main

"b" is simply the allowed abbreviation for the break command. This particular break command creates a breakpoint on the first executable statement within the main( ) function.

When you first enter the debugger your program is not running. You cannot employ any of the Step... toolbar buttons until you have initiated your program with the Continue ( [image]) button. So the Continue button will usually be the first toolbar button you will click. Get in the habit of asking yourself before you click on the Continue button, "Do I have a breakpoint somewhere downstream so that I can regain control of my executable?". At the present time we know it is safe to click the Continue button since we know we have a breakpoint at the start of the main( ) function. So click on the Continue button ( [image]) at this time.

The Continue button causes your program to resume execution from wherever it last left off. In this case, your program will start from the beginning. And it will almost immediately come to a halt because it will hit the breakpoint at the start of the main( ) function, which is the start of your code.

If we didn't have this breakpoint then the Continue button would cause our program to run until it completed. The GDB debugger does not offer a way to interrupt a running program. This is why the Stop toolbar button is always grayed out and it is why it is so critical that we always have in place a breakpoint somewhere downstream of where we are going to start or continue.

In response to the "b main" command (the one that was automatically issued by CPPIDE when you entered the debugger) the debugger responds with:

Breakpoint 1 at 0x401305: file Main.cpp, line 14

which says, "breakpoint #1 has been placed in program memory at address 0x401305 which is where line 14 of the file Main.cpp resides".

When you clicked on the Continue button ( [image]) you initiated your program. This caused a new black console-mode window to open and obscure most of CPPIDE's window. DO NOT CLOSE THIS WINDOW as this is the Factorial2 program running! NEVER MANUALLY CLOSE THIS WINDOW by clicking on the X icon in the upper right-hand corner or else you will totally hose the debugger, which expects to be able to find this window (program) and control it! This window will close automatically when the console-mode program terminates or else when you exit the debugger.

Initially, there is nothing displayed in this console-mode window because the GDB debugger is controlling the execution of the Factorial2 program and it hasn't yet allowed the Factorial2 program to do anything. Use the mouse to click on the title (caption) bar of the CPPIDE window to bring the CPPIDE window back to the top of the stack of windows.

While your program is running the (gdb) prompt will be missing from the Debugger message pane. When the flow of execution reaches the breakpoint the (gdb) prompt will return and the CPPIDE application will look like:

The new [image] symbol you see in the left-hand margin of the Source code editor is known as the next statement marker. Thanks to the breakpoint, execution of the Factorial2 program halted just before this statement. If we now single step (step into or step over, it makes no difference here) this will be the one statement that will be executed. Anytime you have scrolled the Source code pane and want to again see where execution left off, simply click on the Show Next Statement button ( [image]) on CPPIDE's toolbar.

Go ahead and click on the Step Into button ( [image]) 3 times. The CPPIDE application will then look like:

You can observe that the Step Into ( [image]) button is equivalent to the "step" command (which can be abbreviated as "s") typed in the Debugger message area. The 3 issuances of the step command have caused the following 3 executable statements to execute:

  • opening brace
  • assignment of TheOperand to the value 5
  • assignment of TheFactorial to the value 1

Note that the 2 definition statements (that declare the TheOperand and the TheFactorial variables) are not executable statements.

The next statement marker automatically advances as we execute more of our program. This yellow arrow shows us that we have now reached the test of the while statement. Since we just started this program we know the current value of the TheOperand variable is 5. But we can ask the Gnu debugger to tell us this info. Simply click on the Debugger message pane (in order to give it keyboard focus) and then type:

   p TheOperand

Here you are issuing the abbreviated form of the "print" command. The general form of this GDB command is:

print <variable_name>

This command and the single-step buttons will become your good friends. In response to the "p TheOperand" command, GDB will print in the Debugger message area:

   $1 = 5

The $1 portion of this response is called the history number. The next time you employ the print command the response will start off with $2 to show that this is the second value you have requested. The Gnu Debugger offers you a way to recall earlier values you requested and this is why it is called the history number, but I don't find this feature very useful.

Continue single-stepping this program and watch the while loop repeat back to the top. Each time you are going through the while loop use the print command (which you can spell out as "print" or abbreviate as "p") to observe the value of both TheOperand and TheFactorial. Hopefully this process will make it crystal clear how this algorithm computes factorials. Single-stepping allows you to understanding even the most complicated algorithms.

Once you have single-stepped enough, execution will reach the

 printf( "5 factorial = %d\n", TheFactorial );

statement. When you single step this statement you will observe that the characters generated by printf( ) appear in the console window owned by the Factorial2 program.

You can exit from the debugger at any time by clicking on the Exit Debugger key ( [image]) on CPPIDE's toolbar. If, instead, you single-step past the end of Factorial2's main( ) function you will observe a message similar to "0x00401237 in __mingw_CRTStartup ( )" and then a subsequent message "Program exited normally." If you persist in attempting to single-step after your program has exited you will only generate "The program is not being run" messages.

At this time exit from the debugger. We are next going to work through a sequence of improvements to the Factorial example program. I would recommend that you run these programs using the Execute key ( [image]) rather than the Debug key ( [image]). A bit later I will give you another tutorial on the use of the debugger and then I'll trust you to make your own decisions about using this complicated tool.

But before I forget I want to mention one more thing about the debugger. You might recall that our HelloWorld2 program had a call to the _getch( ) function in order to hold the console window open until we explicitly dismissed it. If you were to debug the HelloWorld2 program and you single-stepped the _getch( ) statement you would discover that the (gdb) prompt goes away and doesn't come back. This is because the GDB debugger cannot proceed until the HelloWorld2 program completes this single step, and the call to _getch( ) cannot complete until you hit a key on the keyboard. This keystroke must be delivered to the console-mode window owned by the HelloWorld2 program. All you have to do in this situation is click with the mouse anywhere in the console-mode window in order to give this window keyboard focus and then type any character on the keyboard. Then you will observe the return of the (gdb) prompt in the Debugger message pane of the CPPIDE window.


Factorial3 Example Program

( 14 )

The Factorial 3 through 5 Example Programs

Our Factorial example program is pretty laughable since all it can do is compute the value of 5! Let's work toward a more general program that can compute any factorial. The first problem we must overcome is that the number 5 is hard coded in the format operand (that is, in the first parameter) passed to the printf( ) function:

  printf( "5 factorial = %d\n", TheFactorial );

In order to make the program more general we have got to get the number 5 out of the printf( ) statement. We want to instead pass 3 parameters to the printf( ) function in the following manner:

  printf( "%d factorial = %d\n", TheOperand, TheFactorial );

Notice that the first parameter passed to printf( ) now holds 2 format specifiers, both of which are %d. This indicates that there must be 2 more parameters passed to printf( ) and these must be variables of type int. When the flow of execution reaches this printf( ) statement the %d format specifiers will be replaced with the current values of these 2 variables.

This implies that we need TheOperand to still hold the value 5 when the program completes. This requires a change to our algorithm. In order to not disturb TheOperand we introduce a new variable, which we will call TheTemporary, and use it to control the while loop.

At this time load the Factorial3 example program and take a look at the changes. Besides the new TheTemporary variable (which is now used inside the test of the while loop) you will also see that I am now using 2 shorthand notations:

  TheFactorial *= TheTemporary;  // A *= B means A = A * B
  TheTemporary -= 1;             // A -= B means A = A - B

You will see these shorthand notations frequently because programmers already have enough typing to do.

Compile and execute ( [image]) this code to observe that we are getting closer to a more general program.

The next issue is the fact that we don't always want to compute 5! Sometimes we want to compute 4! or 6!, etc. We need a way for the human operator to specify the operand. This calls for the scanf( ) function, which is sort of the counterpart to the printf( ) function. Whereas printf( ) creates characters which the operator views, scanf( ) reads characters which the operator types. So printf( ) is an output function while scanf( ) is an input function. Here is the code fragment where we employ the scanf( ) function to read a number that the operator has typed:

    printf( "Enter a positive integer:\n" );
    scanf( "%d", &TheOperand );

Because the scanf( ) and printf( ) functions are counterparts, they employ the same format specifiers. Just as with the printf( ) function, the first parameter passed to the scanf( ) function holds format specifiers which specify the type of the values we want to read from the keyboard. The %d format specifier still indicates a signed integer value. The second parameter passed to the scanf( ) function indicates where this value should be placed. The value read from the keyboard should be placed into the TheOperand variable.

Now look closely at this second parameter passed to scanf( ). You will notice an ampersand &. Remember when I said that in a high-level language you give the compiler the job of choosing all data memory locations, but you could ask the compiler where it chose to put something. The ampersand is a special operator that signifies "the address of". So if I wanted to initialize the contents of an integer valued variable named TheAddress with the data memory address that the compiler had selected for the TheOperand variable then I could say:

    int   TheAddress = &TheOperand;

Without the ampersand the statement:

    int   TheAddress = TheOperand;

says to initialize TheAddress with the value of TheOperand. With the ampersand it says to initialize TheAddress with the address of TheOperand. We can put an address into an int variable because ints are 32 bits wide and addresses on x86 microprocessors are also 32 bits wide. I am fudging a little here since the compiler will actually complain about the statement:

    int   TheAddress = &TheOperand;

We will learn how to make this a legal statement when we learn about pointers later.

Earlier I said that normally you don't care about the data memory addresses that the compiler selects. But to employ the scanf( ) function you must provide the address of the passed parameter rather than the value of the passed parameter. Probably the most common bug in the C language is the statement:

    scanf( "%d", TheOperand );

Because the ampersand is missing, this statement tells the compiler to read an integer from the keyboard and then place it at that address that is the VALUE of the TheOperand variable. In our Factorial example program the value of TheOperand was 5. But we don't have any rights to place something into data memory at address 5. Attempting to do this almost always causes your program to crash due to an invalid memory access or a page fault. Since our program declared the integer valued variable TheOperand we do have permission to place an integer into data memory at the address where TheOperand resides and that is what is accomplished by the statement:

    scanf( "%d", &TheOperand );

At this time load, compile, and then execute ( [image]) the Factorial4 example program. You will observe that the program waits for you to type an integer. It will wait forever since computers are so gosh darn patient. We haven't yet learned how to protect our programs from bad input so don't play any games: when the program prompts you for a positive integer, type a legitimate positive integer.

You will also observe that we introduce another short-hand in the Factorial4 program:

  TheTemporary--;             // A-- means A = A - 1

When we ask the scanf( ) function to initialize an integer valued variable, it will either place an integer into the variable or else not touch it at all. For example, if the operator types "12haha" then TheOperand will get initialized to 12. But if the operator types "haha12" then TheOperand will not get touched at all. This is because scanf( ) must deal with the characters typed by the operator in order and it cannot get past the letters "haha" while looking for an integer.

The flow of characters typed by the operator is called a stream for this reason. Wherever in that stream of characters a call to the scanf( ) function leaves off, the next call to scanf( ) will resume. Thus everything the operator types must be dealt with in some fashion and in order.

But, as you hear so often in the software industry, "We can fix this with just a little bit of code!" All we have to do is to spy upon the human operator and respond accordingly.

At this time load the Factorial5 example program. You will see that we have added a few new printf( ) statements which can be considered debugging aids. They will identify the conditions of the TheOperand variable both before and after the call to scanf( ) so that we can observe exactly what scanf( ) does.

Furthermore you will observe a new control structure which is an if statement. An if statement is quite similar to a while statement in that it starts with a test enclosed in parentheses which is followed by an arbitrary block of code (the body) placed inside curly braces. If the test evaluates to true then the body is executed. The difference between the if statement and the while statement is that the body will be executed once at most. The body won't be executed at all if the test evaluates to false. No matter what, the if statement will not cause looping. We use the if statement in this program to qualify the correctness of the keystrokes typed by the operator. If we don't like the current value of TheOperand then we prematurely terminate the program by making a call to exit( ), which is another function that comes with the C/C++ compiler. This is seen in the code fragment below:

  if ( TheOperand <= 0 )
  {
      printf( "You don't follow instructions so well.\n" );
      exit( 0 );
  }

Go ahead and compile and then execute the Factorial5 example program. Experiment with various types of incorrect input. When I ran the program on my computer the initial value of TheOperand was always 4243460. Therefore, if the operator typed "-1" then TheOperand would become -1 and the program would complain about the intelligence of the operator. But if the operator typed "haha" then scanf( ) would not touch TheOperand, the if statement would do us no good (since 4243460 is a positive int), and the program would attempt to compute and display the factorial of 4243460. This is a problem because "haha" is not a reasonable way to request the factorial of 4243460 and, besides, the factorial of 4243460 is a ridiculously large number, much too large to fit within a 32 bit integer anyway. So even though the program goes off and does a whole lot of multiplications, it still doesn't correctly compute 4243460!

Remember when I said that scanf( ) would either place an integer into TheOperand or else not touch TheOperand? So where is the value of 4243460 (you may see a different value when you run the program) coming from?

I am hoping you realize that the 4243460 is an example of garbage. That is, the Factorial5 example program failed to initialize the TheOperand variable, and hence it still holds whatever was in that data memory location due to earlier events. Here is your first assignment: modify the Factorial5 program such that TheOperand is initialized to 0. This way, if the operator types "haha" and scanf( ) does not touch TheOperand, then TheOperand will still be holding an illegal value (the value 0) which the if statement will be able to spot. My solution to this assignment can be found in the "Solutions" folder.


Factorial6 Example Program

( 15 )

The Factorial 6 through 8 Example Programs

In the Factorial4 example program we accomplished a program that would prompt the operator to type any operand for which he wanted to see the factorial. In the Factorial5 example program we improved the robustness of the program by having it check for bad input typed by the operator. We now want to further improve the utility of the program by having it repeatedly offer to compute yet another factorial.

Basically, we want the program to keep returning to the top instead of exiting. In an RPN calculator program this need would be met with a GTO statement. In an 8051 assembly language program this need would be met with an LJMP statement. But it is considered very bad form in a high-level language program to jump about from place to place. This style of coding leads to buggy programs that are hard to follow.

High-level languages employ block structure where all statements are enclosed in blocks. You move between blocks rather than between individual statements. So to accomplish the Factorial6 example program we want to place the code from the Factorial5 program in a block that keeps repeating indefinitely. This can be accomplished using a while loop whose test is always guaranteed to evaluate to true. One such test would be:

   while ( 1 == 1 )
   {
       <place the body here>
   }

This may look like a typo, but you have to employ a double equals sign to compare the value of two items. A double equals sign is used as the equality operator because the (single) equals sign is employed as the assignment operator. The equality operator is another relational operator like the greater than operator. The statement "while ( 1 == 1 )" says to execute the body of the while loop whenever the constant 1 equals the constant 1 which, of course, will be forever.

An even easier way to cause an infinite loop is the following:

   while ( true )
   {
       <place the body here>
   }

Obviously the constant "true" will always evaluate to true.

At this time load the Factorial6 example program. Observe how the code from the Factorial5 program has been placed inside a new while(true) loop. This means we now have a nested loop which is a very common programming idiom. Each time the flow of execution reaches the closing brace of the outer loop, execution resumes at the opening brace since the test is always true. In effect we keep repeating the Factorial5 program over and over. At least, that was our goal. The Factorial6 program will compile without errors but it doesn't actually work. Go ahead and compile and then execute ( [image]) it for the 3 test cases of 5!, 4!, and 3! You will observe that the program does keep offering to solve for another factorial, but only the first result is correct. To kill the Factorial6 program you will have to click on the Close pushbutton ( [image]) found in the upper right-hand corner of the console window. When you do so you will be presented with the following dialog which you should respond to by clicking the "Yes" pushbutton:

The Factorial6 program claims that:

  5 factorial = 120
  4 factorial = 2880
  3 factorial = 17280

which is clearly wrong. Perhaps you can already guess the problem just from this sequence of test cases (programmers develop really excellent reasoning abilities, I happen to think they should be the only people allowed to vote!).

You have 2 choices when faced with a bug like this: single-step your program in the debugger or add printf( ) statements that will show you what is going on. We will use the debugger again in a later section so let's add debugging statements here.

At this time load the Factorial7 example program. The Factorial7 program is identical to the Factorial6 program except I have added a printf( ) statement inside the inner loop that will display the contents of the TheTemporary and TheFactorial variables during each loop iteration. When I ran the Factorial7 program on the 3 test cases 5!, 4!, and 3! I got the following results:

Enter a positive integer:
5
 When TheTemporary = 5 TheFactorial = 5
 When TheTemporary = 4 TheFactorial = 20
 When TheTemporary = 3 TheFactorial = 60
 When TheTemporary = 2 TheFactorial = 120  -----
5 factorial = 120                               | 120 * 4
Enter a positive integer:                       |  = 480
4                                               |
 When TheTemporary = 4 TheFactorial = 480  <----
 When TheTemporary = 3 TheFactorial = 1440
 When TheTemporary = 2 TheFactorial = 2880 -----
4 factorial = 2880                              | 2880 * 3
Enter a positive integer:                       |  = 8640
3                                               |
 When TheTemporary = 3 TheFactorial = 8640 <----
 When TheTemporary = 2 TheFactorial = 17280
3 factorial = 17280

I added the dashed lines seen above which make clear the problem: the Factorial6 and Factorial7 programs are computing a cumulative result. We can see that TheFactorial variable is not getting re-initialized back to 1 prior to each new iteration of the body of the outer while loop. That is, we need to move the statement that initializes TheFactorial = 1 inside the outer loop.

The Factorial8 example program has this fix in it. Load the Factorial8 example program at this time.

The Factorial8 program has another improvement: it offers the operator a way to terminate the program. This is accomplished with an if statement inside the outer loop. Should the if statement detect that the operator has specified a negative integer then we want to exit from the outer loop. But how can we get out of this while(true) loop since its test will always evaluate to true?

This is the purpose of the break statement. A break statement causes an immediate exit from a while loop. The break statement has this same effect inside a for statement and a switch statement but I haven't introduced these yet. The break statement causes execution to continue just past the end of whatever loop encloses the break statement.

There is also a continue statement which does not cause execution to leave the loop but instead causes the current loop iteration to skip the remainder of the body and return immediately to the test statement.

Take a look at the following if statement seen in Factorial8:

  if ( TheOperand <= 0 )
     break;

I have told you that the body of a while loop or an if statement must be enclosed in curly braces. But the curly braces are not required when the body consists of a single statement. I would recommend, however, that you do employ the braces even when the body is only a single statement. The reason is that it is quite common to find yourself adding statements to an existing body. Will you remember to add the braces when the body grows from 1 statement to 2? A very common programming bug is to type something like the following:

  while ( TheTemporary > 1 )
     TheFactorial *= TheTemporary;
     TheTemporary--;

It is clear from your indentation that you intend for this while loop's body to contain 2 statements. But the C/C++ compiler ignores white space which means it ignores indentation. To the compiler this is a while loop whose body has a single statement followed by another statement that is not in the body of the while loop. This bug will eat you alive but you can protect yourself by always typing the curly braces.

I have left the printf( ) debugging statement in the Factorial8 program. Take a look at the following code fragment:

  while ( TheTemporary > 1 )
  {
     TheFactorial *= TheTemporary;

     printf( " While TheTemporary = %d TheFactorial = %d\n",
             TheTemporary, TheFactorial );

     TheTemporary--;
  }

The body of this while loop consists of 2 executable statements and the printf( ) debugging statement (which is an executable statement as well but it is only a temporary statement). I had my choice of locating this printf( ) statement:

  1. before both executable statements,
  2. between the executable statements,
  3. after both executable statements.

I chose to place the printf( ) statement between the 2 executable statements because then the values that it printed would agree with my mental picture of how the loop operates. If I moved the printf( ) statement ahead of both executable statements then the value printed during each loop iteration for the TheFactorial variable would be an older value (since the first executable statement will have not yet changed the value of TheFactorial). If I moved the printf( ) statement behind both executable statements then the value printed during each loop iteration for the TheTemporary variable would be a newer value (since the second executable statement will have just changed the value of TheTemporary).

Hence the debugging messages generated by the printf( ) statement will depend upon where you place the printf( ) statement. This is a common dilemma for a programmer. There is no guaranteed right answer. But the good news is that in this case you would uncover the program's defect no matter where you placed the printf( ) statement.

Before leaving the Factorial 8 example program, I want to point out one more thing. Execute the Factorial8 program and when it asks you to type an integer enter the following string of characters:

5 4

that is, type a 5, a space, a 4, and then hit ENTER.

You will then observe that the program computes and prints 5! and 4! Although initially you might be impressed with this "two for the price of one" feature, this is actually a bug in our program. Remember when I mentioned that the scanf( ) function must deal with the stream of characters you type in the order you type them? In this case you typed a 5, a space, and a 4, and then asked scanf( ) to read an integer. scanf( ) has to stop when it comes to the space character and hence the int it returns is 5. The while(true) loop eventually brings us back to the call to scanf( ). Because there are still unused characters on the input queue from the keyboard, the scanf( ) function can immediately return the value 4. The while(true) loop again brings execution back to the scanf( ) function and this time scanf( ) is forced to wait until you type more characters.

This time try typing:

5haha

In this case scanf( ) reads and returns a 5. After 5! has been computed, the while(true) loop again calls scanf( ) which sees the remaining characters "haha". Because an int can't be extracted from these characters scanf( ) does not touch the TheOperand variable at all. We have a special if statement inside the while(true) loop waiting for just this situation and this if statement causes execution to break from the while(true) loop and hence the program exits.

It is actually fairly difficult (or at least tedious) to write a console-mode program that is tolerant of all the possible forms of bad input the operator might maliciously or carelessly type.


Factorial9 Example Program

( 16 )

The Factorial 9 through 10 Example Programs

Remember when we discovered a bug in the RPN Calculator example program named BadCombination.txt? This bug occurred because two different sections of our program thought they had exclusive use of a hardware resource (the stack) that was in fact shared by them both. I promised you that high-level languages would solve this problem. Functions are the solution. We will see that functions allow different sections of your program to be isolated from each other.

So let's write our first function from scratch [we have been writing main( ) functions but they have a fixed signature that we don't get to select]. We will name this function "ComputeFactorial". Pretty obviously we want ComputeFactorial( ) to take a single integer which is the operand and return another integer which is the result. Hence the signature or function prototype for the ComputeFactorial( ) function will be:

  int  ComputeFactorial( int ThePassedParam );

Once we have written this function we could call it like so:

  int  TheFactorial = ComputeFactorial( 5 );

or perhaps like so:

  int  TheOperand = 5;
  int  TheFactorial = ComputeFactorial( TheOperand );

Both of these invocations of the ComputeFactorial( ) function are correct because they both satisfy the signature: they pass in an int and receive back an int. In one case the int came from the constant 5 and in the other case it came from the value of a variable of type int.

I keep saying that C and C++ are incredibly expressive languages but I haven't really explained why. Okay, here it is:

  At any spot in your program where a certain
  data type is legal, any expression that
  evaluates to that type is also legal.

So anywhere an int is legal, an expression that evaluates to an int is also legal. This means we could call our ComputeFactorial( ) function in the following manner:

   int  SomeNumber = 3;
   int  TheAnswer = ComputeFactorial( 2*SomeNumber + 5 );

In this case the call to ComputeFactorial( ) would compute the value of (2*3 + 5)! = 11! = 39916800.

Here is yet another legal call to ComputeFactorial( ):

   ComputeFactorial( 5 );

In this case we are throwing away the value returned by the function. There would be no reason to call the ComputeFactorial( ) function if you don't want the value it returns, but there are other functions where this is common. For example, the printf( ) function that we have used actually returns an int which is the number of characters it has written to the screen. Typically you don't care to be informed of this number and hence toss the value returned by printf( ).

There is one formality the compiler will insist upon before it will allow us to make calls to a function: we must declare the function. This rule exists so that the compiler will have the evidence it needs to decide whether we are legally calling the function. The function prototype announces the legal data types for all the passed parameters and for the return value. This is the info the compiler needs before it encounters calls made to that function. Of course, eventually the compiler will need to encounter the function itself, but the body of the function can be located after the calls made to the function as long as the function has already been declared.

At this time load the Factorial9 example program and take a look at the code. Up near the top of the program you will see the function prototype for the new ComputeFactorial( ) function. Then inside the main( ) function you will see a call made to the function. Then after the main( ) function you will encounter the code for the ComputeFactorial( ) function.

Notice how the main( ) function has been simplified since a complicated loop has been moved to the ComputeFactorial( ) function. Some programming books recommend that you keep your functions to no more than a page or two of text. As a function grows bigger it keeps getting harder to understand so perhaps should be broken into bite sized chunks.

Go ahead and execute the Factorial9 program for a few test cases.

So how do functions remain isolated from each other? Don't they all have access to the same variables? No, they don't. Let's take a look at the ComputeFactorial( ) function from the Factorial9 example program:

int ComputeFactorial( int ThePassedParam )
{
    int   TheFactorial;

    int   TheTemporary = ThePassedParam;

    TheFactorial = 1;

    while ( TheTemporary > 1 )
    {
        TheFactorial *= TheTemporary;
        TheTemporary--;
    }

    return TheFactorial;
}

At the top of this ComputeFactorial( ) function you can observe definitions for 2 variables, TheFactorial and TheTemporary. This means that these 2 variables can be employed in expressions at any subsequent point inside ComputeFactorial( ). Furthermore, the ComputeFactorial( ) function is passed an int called ThePassedParam. This variable is also in scope within the entire function. No other variables are in scope inside ComputeFactorial( ). This concept of scope is the first mechanism whereby functions are isolated from one another.

The main( ) function in the Factorial9 program declares a variable called TheOperand. As an experiment, go ahead and add a reference to TheOperand inside the ComputeFactorial( ) function. For example, add the following call to printf( ) to some blank line in ComputeFactorial( ) in order to try to print the value of TheOperand from within ComputeFactorial( ).

   printf( "TheOperand = %d\n", TheOperand );

When the compiler comes upon this new statement inside ComputeFactorial( ) it generates the error:

'TheOperand' undeclared (first use this function)

You will also notice that this error message is printed in red in the Compiler message pane of CPPIDE. Whenever a compiler error message is printed in red you can RIGHT-click on the error message and be taken to the offending line in your source code (this feature was the big innovation introduced by integrated development environments). Alternatively, you can keep pressing F4 and be taken to the locations of your first 6 errors.

The compiler is telling you that TheOperand is not in scope at the location where you are trying to use it. TheOperand is only in scope inside the main( ) function because that is where it is declared. A variable that is not in scope is unavailable at that point in the code. The compiler is putting up walls around various sections of data memory in order to enforce discipline. There was no concept of scope in assembly language: any statement could touch any data memory location. High-level languages offer scoping rules in order to protect against accidental interference between different sections of the program. And this is just the first aspect of the isolation that exists between functions!

The concept of scope is closely related to the concept of lifetime. A variable is alive while it has a memory location. It might be alive and yet not be in scope, meaning that you can't reach it (not for reading or writing). Obviously, if a variable is not alive then it definitely is not in scope. Scope refers to the accessibility of a variable and lifetime refers to whether the variable currently exists somewhere in memory. This distinction is subtle, and related to the distinction between a variable definition and a variable declaration. A variable definition reserves memory for a variable while a declaration merely gives you access to a variable that was defined elsewhere. This distinction will become clearer when we get to classes (this distinction is also important with regard to the extern and static keywords).

At this time load the Factorial10 example program. The change that you will notice in Factorial10 is that the ComputeFactorial( ) function no longer bothers to define a variable named TheTemporary. Instead the ComputeFactorial( ) function now directly employs the passed parameter (cleverly named ThePassedParam) to control the while loop inside ComputeFactorial( ). This means that the value of ThePassedParam continually decrements while ComputeFactorial( ) runs.

But now look back at the calling code in main( ):

    TheFactorial = ComputeFactorial( TheOperand );

    printf( "%d factorial = %d\n", TheOperand, TheFactorial );

This program employs TheOperand as the passed parameter to ComputeFactorial( ). If ComputeFactorial( ) is decrementing TheOperand then the printf( ) statement that concludes the while(true) loop inside main( ) should print out a value of 1 for TheOperand, since by the time ComputeFactorial( ) returns to main( ) it will have decremented its passed parameter to 1.

Compile and execute the Factorial10 program to observe that this does not happen. This is because the C and C++ languages, by default, call functions by value. The phrase call by value indicates that the value of the passed parameter is copied into a new variable which is in scope inside the called function. The original variable used for the passed parameter is not in scope inside the called function and hence can't be affected by the execution of the function.

What this means is that when execution enters our ComputeFactorial( ) function, there will be a new variable named ThePassedParam which will be initialized with the value of TheOperand (since TheOperand was the passed parameter used in the call to the function). ThePassedParam is then in scope inside ComputeFactorial( ) but TheOperand is not. Changes made to ThePassedParam have no effect upon TheOperand because there is no linkage between these variables: they reside in separate data memory locations. One was used to initialize the other, but then they went their separate ways.

The call by value mechanism is the second aspect of the isolation between functions (the first was the scoping rule).


Call By Value

( 17 )

Call By Value and Call By Reference

The traditional way to demonstrate the significance of call by value is to write a Swap( ) function. So let's have at it. At this time load and compile the Swap example program.

Let's start with the code in the main( ) function. You will observe that main( ) defines and initializes two ints called TheFirstOperand and TheSecondOperand. The main( ) function prints the values of these 2 ints, uses them as the passed params to the Swap( ) function, and then prints their values again.

Now let's look at the code in the Swap( ) function. This function takes 2 passed parameters named Oper1 and Oper2 which are both ints. The Swap( ) function prints the values of these ints, swaps their values, prints their new values, and then returns to the caller.

The algorithm that accomplishes the swap is known as the three way swap. The heart of the algorithm is:

    int  temp = Oper1;   // we need a temporary holding place
                         // for Oper1's original value...
    Oper1 = Oper2;       // ...before we overwrite Oper1
    Oper2 = temp;

The three way swap is so named because it requires the use of a third data memory location. Consider the alternative where we attempt to make do without this temporary variable:

    Oper1 = Oper2;
    Oper2 = Oper1;

The code shown above would result in both Oper1 and Oper2 having the same value, that value being the original Oper2 value. Do you see why?

At this time run the Swap program and inspect the results printed to the console window. It is clear that by the end of the Swap( ) function the two ints have had their values swapped but when we return to the calling code in main( ) there has NOT been any changes made to the two variables which were used for the passed parameters.

This is the essence of call-by-value. To explain this situation I need to adopt more precise terminology. TheFirstOperand and TheSecondOperand are really the passed parameters because their values are given or "passed" to the Swap( ) function. The Oper1 and Oper2 variables seen inside the Swap( ) function are better called the formal parameters because they are distinct from the passed parameters. Some books refer to the passed parameters as the "actual parameters" to help distinguish them from the formal parameters.

The memory for Oper1 and Oper2 only comes into existence at the moment the Swap( ) function is called. This memory needs to be initialized, so Oper1 and Oper2 are initialized with the values of the passed params, TheFirstOperand and TheSecondOperand. Since the passed params and the formal params are separate, changes made by the Swap( ) function to the formal params have no influence on the passed params back in the calling code, which in this case is the main( ) function. The only linkage between TheFirstOperand and Oper1 is that the VALUE of TheFirstOperand is employed to initialize Oper1. Remember also that only Oper1 and not TheFirstOperand is in scope within the Swap( ) function. Similarly, only TheFirstOperand and not Oper1 is in scope within the main( ) function.

This technique is called call by value and it is used in the majority of high-level languages. The alternative to call by value is call by reference. FORTRAN is an example of a language that employs call by reference. In the FORTRAN language a called function is always given the address of the variables used for the passed params and hence the called function can always change these values.

Now since the C/C++ language is so powerful you might guess that it offers you a way to bypass both of the types of separation that exist between functions. And indeed it does. To bypass the scoping rules you can declare a variable as having global scope. Such a variable is called a global and is in scope throughout the program. To cause a variable to have global scope simply place its definition outside of any function. You won't find many examples of global variables in my example programs because globals are frowned upon for a number of reasons (they are especially troublesome when a team of programmers is working together on a common project). However I purposefully include a global variable in one of the upcoming example programs (and I then show you how to eliminate that global).

To bypass the other means of separation, call by value, you must employ the address of operator & which we have already seen in the context of the scanf( ) function. To accomplish this technique you must employ the address-of operator (&) in the calling code and you must declare the data type of the formal parameter as a pointer, which means it holds an address. Pointers are how a high-level language implements the type of indirect addressing we saw in 8051 assembly language. Remember earlier when I said the compiler would complain about a statement such as:

  int   TheAddress = &TheOperand;

The problem with this statement is that the right-hand expression is of the type "address of an int" while the left-hand variable has the data type int. These two data types are not compatible and hence the compiler complains about this assignment statement (the exact error message is "invalid conversion from 'int*' to 'int' "). We need to declare the TheAddress variable to have the "address of an int" data type. This is accomplished using the following notation:

  int   * TheAddress = &TheOperand;

The asterisk indicates that the variable named TheAddress has the data type of "pointer to an int" which you can also think of as "address of an int" since a pointer holds an address. The assignment statement is now legal since the data type of both sides of the equals sign is "address of an int".

Since pointers are data types, we can employ pointers as passed parameters to functions. For example, here is the function prototype for a function that takes 2 int pointers and returns nothing.

  void  Swap2( int * PointerToOper1, int * PointerToOper2 );

At this time open the Swap2 project and find this exact function declaration and the corresponding function.

In order to call this Swap2( ) function you must supply two passed parameters (actual parameters) which hold the addresses of ints. This is easily accomplished using the address of operator &. So you could call this Swap2( ) function in the following manner:

    int   TheFirstOperand = 1, TheSecondOperand = 2;
    Swap2( &TheFirstOperand, &TheSecondOperand );

Inside the Swap2( ) function, the PointerToOper1 and PointerToOper2 variables (formal parameters) have the data type pointer to int rather than int. You use the * (asterisk) notation as the dereference operator to get the value from the pointed-to address. For example, to print the int value pointed to by the PointerToOper1 variable you could use:

  printf( "Oper1 = %d\n", *PointerToOper1 );

This is how indirect addressing looks in most high-level languages. The expression *PointerToOper1 means, "Get the value of that int whose address is held in the PointerToOper1 data memory location". The content of PointerToOper1 is an address while *PointerToOper1 is an int.

The heart of the new Swap2( ) function is the following statements:

    int  temp = *PointerToOper1;
    *PointerToOper1 = *PointerToOper2;
    *PointerToOper2 = temp;

This is still a three-way swap, but now it involves 2 pointers and 1 int (the temporary is still an int). The statement:

    int  temp = *PointerToOper1;

means, "go to the address held in the PointerToOper1 variable and get (de-reference) the int stored there and use it to initialize the int variable named temp". The statement:

    *PointerToOper1 = *PointerToOper2;

means, "go to the address held in the PointerToOper2 variable and get the int stored there and use it to initialize the int variable located at the address held in the PointerToOper1 variable". The statement:

    *PointerToOper2 = temp;

means, "use the value of the int variable named temp to initialize the int variable located at the address held in the PointerToOper2 variable".

At this time compile and execute the Swap2 program. Observe that, unlike the results we saw with the original Swap program, the values of the variables in the calling code have been swapped by the time the Swap2 program terminates.

The concept of pointers will become clearer if you experiment with the Swap2 program using the GDB debugger. Anytime after getting a clean compile, enter the debugger by clicking on the "Enter the Debugger" toolbar button. Then follow my instructions exactly.

Upon entering the debugger, wait for the (gdb) prompt to appear in the lower pane. While you are waiting you will notice that some GDB commands are automatically issued on your behalf. One of these is the "b main" command which sets a breakpoint at the very beginning of your program. Once the (gdb) prompt appears click on the "Continue" toolbar button. This will cause an empty console window to appear but the breakpoint will cause the program to halt its execution at the first executable statement. Click on the "Step Into" toolbar button once so that you execute the following statement:

  int   TheFirstOperand = 1, TheSecondOperand = 2;

Because this statement defines two int valued variables, you can now use the GDB "print" command (which I like to abbreviate as "p") to observe not only the value of these variables but also the addresses where the compiler chose to locate them in memory. You accomplish this via the following commands:

  p TheFirstOperand
  p &TheFirstOperand
  p TheSecondOperand
  p &TheSecondOperand

You can see that I request the address of the TheFirstOperand variable via the GDB command "p &TheFirstOperand". When I performed these GDB commands on my computer I observed that:

  TheFirstOperand  = 1
  &TheFirstOperand = 0x74fde4
  TheSecondOperand  = 2
  &TheSecondOperand = 0x74fde0
Now click on the "Single Step" toolbar button exactly 2 more times so that you enter the Swap2( ) function. Then type the following GDB commands in the Debugger message area:
  p PointerToOper1
  p *PointerToOper1
  p &PointerToOper1

Since the PointerToOper1 variable is a pointer, it holds an address. On my computer, the address that it holds is reported by the "p PointerToOper1" command as being 0x74fde4. Remember that number? PointerToOper1 holds the address of the TheFirstOperand variable. This is because we employed the expression &TheFirstOperand as the passed parameter used to initialize the formal parameter PointerToOper1. GDB will report the pointed to value of a pointer if you request the "p *PointerToOper1" command. This result is 1 at the top of the Swap2( ) function (although it will become 2 just a few statements further ahead). Finally, since pointers are variables they have a location in memory just like any other variable. You can use the "p &PointerToOper1" command to learn where the compiler chose to locate this variable in memory. On my computer "p &PointerToOper1" returned 0x74fdc0.

These results are all summarized in the following illustration:

Hopefully this illustration makes it more clear how one memory location can hold the address of another memory location and hence point to it. You can continue to Single Step the Swap2 program and employ the various GDB "print" commands I have shown you to watch the 3-way swap take place. Eventually, it will be time to exit from the debugger. I will explain more of the debugger's capabilities very soon.

Now I don't want to give you the impression that the address of operator (&) causes the C language to stop using call by value and instead use call by reference. This is not true. The C language always use call by value. But when you employ the address-of operator, the VALUE that you are passing to the function is an ADDRESS. The formal parameters in the function are still separate memory locations from the passed parameters in the calling code. The formal parameters still come into existence only at the moment of the call to the function. And the formal parameters are still initialized with the VALUE of the passed parameters. But when that value is an address, the function can influence the memory owned by the calling code.

Think of it this way. If I hide some numbers in my house and then tell you their value, you can't change those numbers. But if I tell you the locations of those numbers, you can change their value. The C language always uses call by value which means that the formal parameters of all functions have unique memory locations from the passed parameters used in the calling code. But you, the programmer, have the choice of passing a value or an address via these parameters.

To be honest with you, I need to point out that I have said, "The C language always uses call by value." This is one of the few times where I need to differentiate between C and C++. The C++ language displays all the behavior that I have just explained, but also adds a mechanism that allows the programmer to select call by reference. This mechanism is known as references and the relevant keyword is again the ampersand (&) symbol. I am not going to teach you references because in my opinion the capability that they add to the language does not justify their complexity. I especially dislike them because they accomplish nothing you can't do with pointers, and yet they obscure the fact that addresses are being passed to functions. I like transparency in my programs: when I am passing an address I like the notation to remind me of this fact.

The introduction of pointers typically causes a lot of confusion in beginning C and C++ programming courses. Some computer science students even manage to graduate from college without feeling comfortable with pointers. There is no reason for this discomfort. Pointers are no more complex than the distinction between a value and an address. I am hoping that because I already introduced you to indirect addressing on the 8051 microprocessor that you are already comfortable with the idea of using one data memory location to hold a value and using another data memory location to hold an address. To get the value from a conventional variable, you just go to that address. To get the value from a pointer variable, you go to its location where you find an address which you must go to to get the value. The word indirect reminds you that it takes one more step to get the value from a pointer.

There is one thing that is confusing and that is the fact that the C/C++ language is employing the asterisk * for double-duty. In a variable declaration (including the declaration of the formal parameters used in a function) an asterisk indicates that the variable has the data type "pointer to" something. But anywhere else, an asterisk is the dereference operator which means "get the pointed-to value from this address". You can see these 2 uses of the asterisk notation in the 2 statements below:

   void  Swap2( int * PointerToOper1, int * PointerToOper2 )
   {
      ...
      int  temp = *PointerToOper1;
      ...
   }

Even though the Swap2 example program sends the address of the TheFirstOperand to the Swap2( ) function, the variable name TheFirstOperand is still not in scope in the Swap2( ) function. You would still get an error if you tried to employ the TheFirstOperand variable name within the Swap2( ) function. But who needs the variable name when you have the address?

Here's a final thing to consider. Did it bother you when I stated that functions can accept any number of passed parameters but can only return one value? Well think about the Swap2 program. Doesn't it really return new values for TheFirstOperand and TheSecondOperand to its caller? There is no big difference between returning values to the caller via passed parameters and returning a value via the one official return value of a function. So the use of pointers allows you to return an unlimited number of items from a function.

The three-way swap shows one of the reasons that pointers were invented. But pointers have 2 other very important uses. When we get to arrays we will learn that using a pointer as a passed param to a function avoids the need to copy an entire array into new data memory locations that are in-scope in the function. And pointers are also the solution to the very common situation where you are going to need to employ some memory when your program runs but you can't specify how much memory at the time you compile your program. This need is met with dynamic memory allocation which depends upon the use of pointers. The relevant C++ language keyword is new while in the C language you accomplish dynamic memory allocation by calling the malloc( ) function. We will see an example of the new keyword in our upcoming Breakout game.


Factorial11 Example Program

( 18 )

The Factorial 11 through 12 Example Programs

We are getting close to the point where I can introduce you to the concept of classes that was introduced in the C++ language. But since classes are defined in header files (.H files) I first have to acquaint you with how the C language employs header files.

I have already mentioned the 1 million lines of source code that Hewlett-Packard has accumulated for their LaserJet printers. It would not work to keep all of this code in a single file. For one thing, it would be impossible for more than 1 programmer to be modifying the code at any given time. When a programming project has been split into multiple files different team members can be working on the code simultaneously (as long as they work on different files). When you do split your program among different .CPP files, there are certain statements (usually declarations) that need to appear in many of the .CPP files. Header files were invented so that you wouldn't need to duplicate a bunch of typing in each of your .CPP files. I'll give you an example of this below.

At this time load the Factorial11 example program. For the first time you will observe that there are 2 filenames listed in the Files listbox. You switch between the 2 files by clicking on the filenames in the Files listbox. The code itself is no different than it was in the Factorial10 program except for the fact that the ComputeFactorial( ) function has been placed into a separate file called SecondFile.cpp. Compile and execute this code just to convince yourself that everything still works. Observe that during the compile the Compiler message window will now show the g++ compiler being invoked 3 times:

  • to convert Main.cpp to Main.o
  • to convert SecondFile.cpp to SecondFile.o
  • to convert Main.o and SecondFile.o to Factorial11.exe

Now let's take another step and introduce our first header file. At this time open the Factorial12 project. The Files listbox will now show 3 source files, 2 of which are .CPP files and 1 of which is a .H file.

In the C language, a header file is the place you put function declarations (also called function signatures or function prototypes). In the Main.cpp file of the Factorial11 example program we had 1 function declaration. In the Factorial12 example program this 1 statement has been moved into a new file called SecondFile.h . We name it thus because it holds the function declarations for all the functions in the SecondFile.cpp file. Note the pattern: XYZ.h holds the function declarations for the functions in XYZ.cpp. By clicking on the SecondFile.h filename in the Files listbox you will get a chance to observe that it consists of nothing but the 1 function declaration.

Then click on the Main.cpp filename in the Files listbox to find the corresponding change: where there used to be a function declaration there is now a #include statement. The #include statement simply includes the contents of the specified file at that point in the file. So after the preprocessor has done its thing on our Main.cpp file it will look exactly as it did back in the previous project.

"So why bother?", you ask? Well, consider the case where SecondFile.cpp holds 100 functions. Assuming Main.cpp held code that wanted to employ all 100 of these functions, then the person writing Main.cpp has 2 choices. He can either type out 100 function declarations in Main.cpp or else put these same 100 statements in a header file and then #include the header file in Main.cpp. That is, he can either type 100 statements or else 101 statements. And he will chose to type 101 statements. Why? Well now imagine that Main.cpp gets split into 2 files: Main1.cpp and Main2.cpp. To continue employing all 100 functions located in SecondFile.cpp in both Main1.cpp and Main2.cpp you either have to include all 100 function declarations in both Main1.cpp and Main2.cpp or else you have to put the #include statement in both Main1.cpp and Main2.cpp. So at this point the choice is between 200 statements or else 102 statements. See the advantage?

The SecondFile.h header file doesn't actually need to hold a declaration for every function in SecondFile.cpp. It just needs to hold a declaration for every function you wish to export from SecondFile.cpp. That is, header files describe the public interface to your code. Consider the situation where SecondFile.cpp holds 2 functions: Function1( ) and Function2( ). Function2( ) is a helper function that is only called from inside Function1( ). You want to allow code outside of SecondFile.cpp to be able to call Function1( ) but not to be able to call Function2( ). This can be accomplished by only listing the function declaration for Function1( ) in the SecondFile.h header file. We will see this concept again in the C++ language where the designer must select whether he wants to make public or keep private each of the functions he places in each of his classes.

Here's a test to see if you are the kind of detail oriented person that makes a good programmer. When we wanted to employ the printf( ) function in the HelloWorld program we had to first include this function's declaration and we did so via the statement:

  #include <stdio.h>

But now that we have written our own header file, SecondFile.h, we are employing it in Main.cpp via the following statement:

  #include "SecondFile.h"

See the difference? The difference between the <> notation and the double-quote marks involves the search path which is the list of locations where the compiler will search for the specified file. And the rule as to which notation to use is simple: if the header file came with the compiler use greater-than, less-than signs and if you wrote the header use double-quote marks and keep the .H file in the same folder that holds the corresponding .CPP file.


Debugging2

( 19 )

More About Debugging

Let's now learn a bit more about the Gnu GDB debugger. GDB offers a command-line interface using the letters "(gdb)" for the prompt. The manual for GDB can be found at: www.fsf.org/manual/manual.html.

At this time open the Factorial12 project (if you haven't previously). Compile this project and then enter the debugger using the Debug button ( [image]) found on the CPPIDE toolbar. Then follow my instructions exactly.

Whenever we enter the debugger, CPPIDE requests one breakpoint for us, located at the start of our code. You can observe in the Debugger message pane that this was accomplished using the GDB command:

   b main

You can request any number of additional breakpoints by LEFT clicking in the gray margin seen on the left-hand side of the Source code editor pane. Every breakpoint is identified by a red stop sign ( [image]) in the left-hand margin of the Source code editor pane. To remove a breakpoint you only need to left click on the stop sign in the gray margin. On CPPIDE's toolbar you can also find the Clear All Breakpoints button ( [image]) which removes all breakpoints. The Show All Breakpoints button ( [image]) lists the current collection of breakpoints in the Debugger message area.

Since we have requested a breakpoint at the start of the main( ) function, you might have expected to find the red stop sign on the source code statement:

   int   TheOperand, TheFactorial;

But this is only a variable definition and not an executable statement. That is, it does not correspond to any work by the computer at run-time. Instead its purpose is to provide info to the compiler at compile-time.

Click on the Continue ( [image]) button to initiate the program being debugged. The Factorial12 program will start and a console-mode window will open (since Factorial12 is a console-mode program). DO NOT CLOSE THIS WINDOW as this is the Factorial12 program running! NEVER MANUALLY CLOSE THIS WINDOW by clicking on the X icon in the upper right-hand corner or else you will totally hose the debugger, which expects to be able to find this window (program) and control it! This window will close automatically when the console-mode program terminates or else when you exit the debugger.

The console-mode window obscures a lot of the CPPIDE window. Click with the mouse on the caption (title) bar of the CPPIDE window to return CPPIDE to the top of the stack of windows.

You can think of the Continue button as the Go button if your program has never been started and the Continue button from then on.

The yellow arrow symbol ( [image]) you see in the left-hand margin of the Source code editor is called the next statement marker. It shows that we are about to execute the first executable statement within the while(true) loop. Normally, the test of a while statement will constitute an executable statement but the compiler is smart enough to know that it really doesn't need to employ an executable statement to accomplish a while(true) test. This is an example of what is called optimization by the compiler. The compiler is an incredibly complicated program that makes all sorts of adjustments to our code. In this case it has totally dropped the test of the while loop since there is no point in performing it. Compiler tricks like this allow your programs to run faster.

Just for practice, click on the SecondFile.cpp filename seen in the Files listbox. The Source code editor pane of CPPIDE will then display the contents of the SecondFile.cpp file. You will often wander about in your code trying to search down a bug. When you want to return to the spot in your code where execution has left off simply click on the Show Next Statement button ( [image]) on CPPIDE's toolbar.

Whenever your program is running the (gdb) prompt will be missing from the Debugger message pane. I wish GDB offered a way to interrupt a running program but it doesn't and that is why the Stop button ( [image]) is permanently disabled on CPPIDE's toolbar.

At this time click on the Show Next Statement button ( [image]) so you can see we are about to execute the statement:

    TheOperand = 0;

Single step past this statement so you come to the call to the printf( ) function.

Just for practice, issue the "p TheOperand" command by typing in the Debugger message pane and observe that TheOperand = 0.

Unless the next statement is a call to a function, the Step Into ( [image]) and Step Over ( [image]) buttons will behave identically. But when the next statement is a call to a function the Step Into button will proceed into the function so you can single-step its statements while the Step Over button will execute the entire function, treating the function call as a single statement.

So go ahead and click on the Step Into button and observe that I have lied. You will not get to observe the contents of the printf( ) function and will instead find that the Next Statement Marker is now positioned at the scanf( ) statement. This behavior isn't a bug, it's a feature. You see, the compiler is providing the printf( ) function and the code for this printf( ) function was not compiled to allow debugging. In order to allow a debugger (any debugger, not just GDB) to single-step through your source code you have to compile that code using the -g switch to the compiler. This is one of the many details that is taken care of for us by our makefiles. But the printf( ) function was not compiled in this manner and hence cannot be debugged. You will find that the Step Into button will not step into any of the printf( ), scanf( ), exit( ), etc. functions that are provided with the compiler. We will get a chance to observe the difference between the Step Into and Step Over buttons when execution reaches the call to the ComputeFactorial( ) function that we wrote (and compiled with the -g option).

Now single-step the call to the scanf( ) function. Notice how the (gdb) prompt does not re-appear in the Debugger message pane. This is because the single step has not yet completed and can't complete until we type a line of text. To do this we must give keyboard focus to the Factorial12 program's console-mode window and then type some text and then hit the ENTER key. Type a small positive integer since you will be single-stepping this code and you don't want the loops to take forever. When you hit the ENTER key to complete the line the (gdb) prompt will return.

Now repeat the "p TheOperand" command and observe that this variable now has whatever numeric value you typed.

Now single-step the if statement that qualifies the value of the TheOperand variable. Note how (assuming you entered a positive integer) the break statement is not executed because it is in the body of an if statement whose test evaluated to false.

We have now reached the statement:

  TheFactorial = ComputeFactorial( TheOperand );

This is a call to a function. Let's assume that we want to watch the ComputeFactorial( ) function do its thing. So click on the Step Into button ( [image]). You will observe that the Source code editor automatically stops displaying the contents of the Main.cpp file and instead displays the contents of the SecondFile.cpp file, since that is where the ComputeFactorial( ) function resides. Pretty neat, huh?

Continue single stepping and eventually you will single-step the return statement that brings us back out of the ComputeFactorial( ) function. You will observe that it takes an extra single-step after the return statement to make it past the closing curly brace of the ComputeFactorial( ) function. This is normal. This single-step corresponds to the overhead of returning from a function back to the calling code.

Continue single-stepping this program and watch the while loop repeat back to the top. During this next iteration of the while(true) loop employ the Step Over button ( [image]) to go right through the ComputeFactorial( ) function in a single step. See the difference between Step Into and Step Over?

Continue single-stepping back to the start of the while(true) loop. This time request that the program compute 10 factorial. Then single-step into the ComputeFactorial( ) function. Single-step through an iteration or two of the while loop inside ComputeFactorial( ). Let's now decide that there is nothing interesting going on here and we want to get beyond this while loop without having to keep single-stepping.

The first alternative in this situation is to employ the Run To Cursor ( [image]) button. Before hitting this toolbar button you must place the cursor (the vertical bar | that Windows calls the caret) on the source code statement where you want execution to halt. So scroll down in the ComputeFactorial( ) function and LEFT click with the mouse anywhere within the return statement that ends the ComputeFactorial( ) function. Once you can see a caret like this one | on this statement then click on the Run To Cursor button and observe that execution will continue until the point you indicated.

Your other alternative when you are getting tired of single-stepping is to employ the Step Out button ( [image]). When you click on this button execution will continue until the current function returns to its caller. Go ahead and try the Step Out button at some time when you are currently executing within the ComputeFactorial( ) function.

There are only a few more Debugger toolbar buttons to learn about. The Step Into N button ( [image]) is equivalent to 25 clicks on the Step Into button. If you don't like the number 25 you can change it by selecting View/Options from the CPPIDE menu. This will bring up CPPIDE's Options dialog which also has a checkbox allowing you to choose whether to see the 25 intermediate steps or make them run faster by not repainting the screen until the final step.

The Show Call Stack toolbar button ( [image]) will display what's called the function call stack. Click on this button at some time when you are executing inside the main( ) function and the result will be something like:

#0  main () at Main.cpp:30

Then click on the Show Call Stack button at some time when you are executing inside the ComputeFactorial( ) function and the result will be something like:

#0  ComputeFactorial(int) (ThePassedParam=6) at SecondFile.cpp:12
#1  0x0040132e in main () at Main.cpp:28

The line labeled "#0" indicates not only the name of the currently executing function but also the value of the passed parameters that were employed in the call to the function. In this case I called the ComputeFactorial( ) function to compute the value of 6 factorial. This same line also shows that execution is currently at line 12 of the SecondFile.cpp file. The line labeled "#1" shows that ComputeFactorial( ) was called from within the main( ) function. More specifically, from line 28 of the Main.cpp file. This kind of info is very useful when you are tracking down a bug. Incidentally, anytime you want to go to a specific line number you can employ the "Edit/Goto Line..." option in CPPIDE's menu.

You don't have to run a program to completion in the debugger. You can exit from the debugger using the Exit Debugger button ( [image]) at any time.

If you ever get the debugger (or the program being debugged) all fouled up, probably the best thing to do is to exit from the debugger and then re-enter the debugger.

There are still a lot of GDB commands that you can find listed in the GDB manual found at www.fsf.org/manual/manual.html. For example, you can ask GDB to print the value of the variable named TheFactorial in hex via the command:

   p/x TheFactorial

And there is a way to ask GDB to automatically execute a whole list of commands every time you single step or reach a breakpoint. But I have shown the essential commands that will get you started. You may now exit from the debugger in preparation for our next topic: the concept of classes that was added in the C++ language.


Classes

( 20 )

Classes and C++

There is still a bit more of the C language that I haven't shown you, but at this time I am going to go ahead and introduce classes, which are the big deal of the C++ language. Other than classes themselves, everything else that I show you (such as the for loop and the switch statement) will still be applicable to both the C and C++ languages. Take a break if you are feeling overwhelmed. The computer programming industry is now over 50 years old and it has had time to develop a lot of technology. Don't expect to become fluent in a programming language any faster than you could become fluent in a foreign language. I am purposefully skipping over a lot of detail that you will need to achieve certification as a programming guru. The book I learned the C++ language from had 1200 pages and didn't get around to introducing the class keyword until page 600!

I have prepared you for this moment by getting you used to the idea that the designer of a piece of code decides how his users will employ that code. They will make calls into the public interface of that code. The code may also require other helper functions but these functions should not be called by end users. So we see a distinction between public and private functions.

The other idea we need is the concept of an instance, which is a real, live, breathing example of a class. The class is the general category while the instance is the individual. For example, "rock bands" might be the class and "The Beatles" would be an instance of this class.

The concept of classes was introduced by the Simula language, which was created for the benefit of those who write software that models physical reality such as heat, acceleration, population growth, etc. The designers of this language wanted to be able to assign sections of code to mimic each object that they were trying to model. An example of an object might be an automobile. To model a car in software you would need to provide an Accelerate( ) function that increases the speed of the car and a Decelerate( ) function that does the opposite. You might also choose to provide functions such as GetDistanceTraveled( ), GetMilesPerGallon( ), and GetPollutionGenerated( ) that report on the current status of the car. Internal to the car model you would probably need variables that keep track of the weight of the car, the current velocity, the remaining amount of gas, etc.

Because all of these functions relate to the software model of an automobile they all belong in the same class. But because you might employ these same functions to model both an economy car and an SUV (sport utility vehicle) you need a way to distinguish between instances of the class. The Simula language never became widely used but all modern programming languages have adopted the concept of object-oriented programming involving classes and objects which are instances of the class.

So let's imagine that you are trying to determine whether SUVs are worse for the environment than lighter, smaller, more aerodynamic cars. You decide to have an object (instance of the car class) called TheSmartCar which will model the economy sedan and another instance of the car class called TheDumbCar which will model the SUV (I am being careful here to not bias the outcome of our experiment).

In programming terms, both TheSmartCar and TheDumbCar are just variables. But rather than having a simple data type such as int these variables will be instances of a complicated user-defined data type called cCar. cCar is the class and TheSmartCar and TheDumbCar are instances of this class. I like to name my classes starting with a lower case 'c' to help distinguish class names from variable names but some people prefer upper case C for this purpose.

Assuming the compiler has already seen the declaration of the cCar class, then we can instantiate (create) our 2 instances of the cCar class and an int variable as shown below:

   cCar   TheSmartCar, TheDumbCar;
   int    TheInteger;

I created the TheInteger variable just to show you that you define objects (instances of classes) just like you define any other variables.

When we create a class such as cCar you are effectively extending the language. Variables of type cCar are treated no differently than variables which are instances of the built-in types such as int.

Each car will need to keep track of its own internal state. The state of an object is the current value of all of its internal data variables. Think of the "State Of The Nation" speech where the President reports on the condition of the country. For software that models a car we would probably need to employ internal variables named Weight, CurrentVelocity, DistanceTraveled, etc. to remember the current condition of the car. Each instance of the cCar class will have its own copy of these internal variables. That is, the weight of the economy sedan might be 2000 pounds while the weight of the SUV might be 4000 pounds. Since TheSmartCar and TheDumbCar are both instances of the cCar class, they both have an internal Weight variable. But the value of this variable differs between the two cCar instances.

In a software model, state is implemented with variables while functionality or behavior is implemented with functions. So besides having the internal variables Weight, CurrentVelocity, etc., each car will also provide functions such as Accelerate( ) and GetPollutionGenerated( ). So we have ended up with a set of internal variables that remember the current state of the object and a set of functions which mimic the behavior of the object. The central concept of object-oriented software is that the state and behaviors of an object should be kept together in each instance of that object. This way the object can be a free-standing, self-sufficient entity much akin to a living organism. Anything related to the object is contained within either its current state or the collection of actions that it can perform.

The declaration of the cCar class might look something like:

   class  cCar
   {
   public:
      void  Accelerate();
      int   GetMilesPerGallon();
      int   GetDistanceTraveled();
      int   GetPollutionGenerated();
   private:
      int   m_Weight;
      int   m_CurrentVelocity;
      int   m_DistanceTraveled;
   };

The words class, public, and private are keywords of the C++ language. The fact that the functions are placed in the public portion of the class declaration means that these functions can be called by any code that contains an instance of the cCar class. The fact that the state variables are placed in the private section of the class declaration means that these are internal details not exposed or accessible outside the code that implements the class. You are allowed to place functions in the private section if you wish to prevent these functions from being called by any code that is not a part of the implementation of the cCar class.

In many of the example programs that I supply, you will observe that I use the keyword protected instead of the keyword private. These two keywords are synonymous except for the fact that protected has advantages when you are employing inheritance (which we'll study soon).

Take a look at the function declaration:

  void  Accelerate();

This declaration says that the Accelerate( ) function takes no passed parameters (because the parentheses are empty) and returns no value (because of the keyword void). The return statement at the end of this function would then have to look like:

  return;         // don't return anything

This class declaration would probably be put in a file named cCar.h. Any code (such as Main.cpp) that wanted to create variables of type cCar would then only need to #include "cCar.h" in order to include the class declaration. The code in Main.cpp is then ready to instantiate (create) variables of this class via a variable definition such as:

   cCar   TheSmartCar, TheDumbCar;

So now that we have our 2 objects we want to run our experiment by having both cars accelerate and drive around a track and then report how much gas they consumed and how much pollution they generated. So we will need to call the Accelerate( ), GetMilesPerGallon( ), GetPollutionGenerated( ), etc. functions declared in the cCar class. But how do we call these 2 functions for the 2 cars?

When you want TheSmartCar to report its miles per gallon you call the GetMilesPerGallon( ) function in the following manner:

   printf( "TheSmartCar is currently getting %d miles per gallon.\n",
           TheSmartCar.GetMilesPerGallon() );

Notice how the function name and the instance name are tacked together with a period. This period is actually a C/C++ operator called the dot operator. Notice also that since we are using the %d format specifier inside a call to printf( ) we must supply an integer value. The integer value that will be printed is the integer returned by the GetMilesPerGallon( ) function. Look again at the function prototype for GetMilesPerGallon( ) (found in the class declaration) to verify that everything is kosher.

So far we have only seen the declaration of the cCar class. But we must also provide an implementation for each of the functions declared in this class. If the class declaration was placed into a cCar.h file then the implementation of the Accelerate( ), GetMilesPerGallon( ), etc. functions would be placed into the cCar.cpp file. You must use a double colon, known as the scope resolution operator, when you implement these functions in the cCar.cpp file. A short excerpt from the cCar.cpp file might look like:

 int cCar::GetDistanceTraveled() // GetDistanceTraveled() is a class function
 {
    return m_DistanceTraveled;   // m_DistanceTraveled is a class data member
 }

The m_ prefix that you see on each of the cCar class's state variables is simply a naming convention that helps remind you that these variables belong to a class and aren't accessible outside the class implementation (assuming they were declared in the private section of the class). These are called data members or member variables. The functions defined in the cCar class are called member functions.

I don't want to overwhelm you with all the details of creating C++ classes. Creating a C++ class is much more involved than simply employing a class written by someone else. And that is what we are going to do. I am going to give you classes and you will make function calls on instances of these classes. So really the only thing new in your code is that instead of calling GetMilesPerGallon( ) you will now type TheSmartCar.GetMilesPerGallon( ) in order to call the GetMilesPerGallon( ) function on the instance of the cCar class known as TheSmartCar.

Don't bother looking for an example program that employs this cCar class. I have a much more interesting example program to introduce you to classes.


Using cScale

( 21 )

Using The cScale Class

We have been using the C++ compiler to prepare console-mode programs. But even though we have been using the C++ compiler, the code we have been writing has been legal C. Before moving on to Windows programs that offer a full graphical user interface (GUI) I want to demonstrate that you can employ the C++ language to create console-mode programs. The program I have in mind will play music (thus making a lie of my earlier statement that a console-mode program was one whose interactions with the operator were limited to text).

Some of you might be reading this while sitting in a classroom in front of a computer that does not have speakers. All hope is not lost. Your computer is probably still capable of generating sound. Your computer is equipped for sound if you can find a headphone speaker jack on the back panel. If you are really lucky this jack might even be on the front panel (but the headphone jack on the CD-ROM drive is not sufficient). If you can find such a jack then just take the headphones from your Walkman and plug them in.

The title of this page is "Using the cScale Class" and the purpose of this page is to demonstrate how easy it is to employ a class created by someone else. The title of the next page is "Implementing the cScale Class" and that is where we will see the implementation details. So for the moment, just accept that we have available the cScale class. To create instances (objects) of this class in a file such as Main.cpp, we merely need to place the following statement at the top of Main.cpp:

  #include "cScale.h"

Once the compiler has seen the declaration of the cScale class (thanks to its perusal of the cScale.h header file) it will allow us to create instances of this class.

A class is a general concept. Some authors describe it as a cookie cutter that allows you to construct cookies of a certain type. The cookie is the object that lives and breathes (better make it a gingerbread man) while the class is an abstract conceptualization of all the characteristics of all the cookies that could ever be made from that cookie cutter.

Our cScale class represents the knowledge of a musical scale. When we create an instance of the cScale class (that is, a variable or object whose data type is cScale) we will be able to request that it play any of the notes belonging to that scale. Hence we might instantiate (create) one instance of the cScale class known as Cscale in order to play a piece of music in the key of C. And we might create another instance of the cScale class, known as the Gscale variable, in order to play the second movement of the same piece which is written in the key of G. The variable definition statement that can produce these 2 instances of the cScale class is shown below:

  cScale   Cscale, Gscale;

Note how similar this is to:

  int      TheOperand, TheFactorial;

Some authors like to say that the class concept in C++ allows you to extend the language. That is, C++ comes with the knowledge of the int keyword and if you provide the definition for the cScale class then you can start employing cScale as just another language keyword.

Now there is actually something wrong with the variable definition statement:

  cScale   Cscale, Gscale;

The problem is that we have not differentiated between the 2 instances of the cScale class. That is, how is one of these objects supposed to know that it should play notes from the C scale and how is the other to know it should restrict itself to notes from the G scale? We need to embed this knowledge into each instance of the cScale class at the time we construct those objects. Hence we need to learn about constructors. But first, let's learn a bit about music.

A scale is a collection of notes that span an octave. An octave is a change in frequency by a factor of 2 (up or down). The human ear (or should I say brain?) hears sounds which are an octave apart as being very similar and hence both notes are assigned the same scale letter, such as G. Western music divides each octave into 12 "half-steps" (not the best name since there are 12 of them). It was the famous composer Bach who decided that each of these half-steps needed to be the twelfth root of 2 higher in frequency than the previous half-step. This was his compromise (known as the "even tempered scale") that allowed a single piano to play music written in any key. The twelfth root of 2 is around 1.05 so a half-step is a change in frequency of about 5 %. Every pair of adjacent piano keys has a pitch difference of 5 %.

A key or scale is just a collection of certain of the 12 half-steps in an octave. The major scale, which is employed for that perennial favorite, "Happy Birthday To You", is an 8 note scale that starts on the "tonic" (root note) and then climbs 2, 2, 1, 2, 2, 2, and 1 half-steps, thus having climbed 12 half-steps (1 octave) in all. The most common scale in rock and roll is the pentatonic minor scale which has 5 notes spaced apart by 3, 2, 2, 3, and 2 half-steps (again totaling to 12).

So when we construct an instance of the cScale class, we need to tell it what tonic frequency to use. And the cScale class that I have defined requires one more piece of information at the time of creation of each cScale object: the "tempo" or speed at which the notes should play.

At this time open the Scale example project. Click on the Main.cpp file listed in the Files listbox so you can peruse that file. The Main.cpp file once again holds nothing but a main( ) function. You will see that we instantiate a variable called MyFirstScale having the data type cScale via the following statement:

    cScale   MyFirstScale( 220, 500 );

This is a call to the constructor of the cScale class. It looks a little like a function call but constructors have their own set of rules and hence are not ordinary functions. But you get to design them and you get to decide what passed parameters they should accept. I decided that the constructor for the cScale class would take 2 passed parameters. The declaration for the cScale constructor is shown below:

 cScale( double dTonicFreqInHz, DWORD dwQuarterNoteDurationInMSecs );

So we can see that the main( ) function is providing a value of 220 for dTonicFreqInHz, the tonic frequency in Hertz, and is providing a value of 500 for dwQuarterNoteDurationInMsecs (msec is the abbreviation for milliseconds).

After you have called the constructor of a class, you have an instance of that class. Since instance variables are just slightly more complicated than variables of the data types built into the language (such as ints) they are usually referred to as objects rather than variables.

An object is a variable that has both state and behavior. The state of an instance of the cScale class that has been constructed to play the C scale is the knowledge that it should only play notes from the C scale.

Both of these class instances (objects) share the same behavior. That is, they can both play notes. Look again at the main( ) function in the Main.cpp file of the Scale project. We request some behavior from our MyFirstScale instance by calling the member functions of the class. To call a member function on an instance of the class you combine the object name with the member function name using the dot notation seen below:

  MyFirstScale.PlayNote( 0, 'q' );  // play the tonic of the scale

This statement requests that the MyFirstScale object play the tonic (root) note of the scale for the length of time appropriate for a quarter note. This is an example of behavior.

The function prototype for this PlayNote( ) function can be found in the cScale.h header file, and is repeated below:

  BOOL     PlayNote( int iNumHalfStepsAboveTonic, char chDuration );
                                  // chDuration can be 's', 'e', 'q',
                                  // 'h', or 'w' to request a
                                  // sixteenth, eighth, quarter,
                                  // half, or whole note

If we wanted to play the second note of the scale with a duration of a half note we would call:

  MyFirstScale.PlayNote( 1, 'h' );

The second passed parameter of the PlayNote( ) function has the data type char which means that it can hold any single letter of the alphabet (and any single numeral or punctuation symbol). You always place single quote marks (') around individual alphabet letters.

You can say that simple variables such as ints have state but they do not have behavior. The following variable:

  int   i = 7;

has the state (condition) of equalling 7 (at least until we change it with an assignment statement). But you can't call any member functions on this variable and hence it doesn't have behavior. Object-oriented programming languages attempt to allow you to structure your program in terms of objects that mimic the actual objects of our worldly existence.

A little later on in the main( ) function we are ready for a second instance of the cScale class. Since we construct this object differently:

  cScale   MySecondScale( 300, 500 );

it will maintain a different state from our first instance. Specifically, its tonic frequency will be 300 Hertz rather than 220 Hertz. This means that when we call the exact same member function on this second instance:

  MySecondScale.PlayNote( 0, 'q' );  // play the tonic of the scale

we will hear a difference. Since both MyFirstScale and MySecondScale are instances of the cScale class, they both support the same behaviors. Namely, they both provide a PlayNote( ) member function. But these two instances have different internal state (due to how they were constructed) that causes a different audible sound even though we call the PlayNote( ) function in exactly the same manner. On the next page when we study the implementation of the cScale class, we will learn that the cScale class uses internal variables to remember its state: one variable remembers its assigned tonic frequency and another variable remembers its assigned quarter note duration (tempo).

The calls to the cScale constructor might look a little funny to you. To make you feel more comfortable with this type of statement, allow me to point out that the C++ language allows you to replace the following variable declaration and initialization:

   int   iMyInteger;
   iMyInteger = 5;

with this equivalent statement:

   int   iMyInteger( 5 );

This is an example of syntactic sugar, meaning there are alternate styles of achieving the same result. I prefer the former style, but the latter looks more like how you create and initialize variables that are instances of classes.

At the bottom of the main( ) function in the Scale project you will find yet another C language keyword: the for loop. A for loop causes iteration (looping) and is more convenient than a while loop when you know exactly how many iterations you want at compile time (at the time you are typing your program). A for loop consists of 3 expressions within parentheses, followed by a body which is enclosed in curly braces. You can put anything you want in the body. The 3 expressions inside the parentheses are separated by semicolons. The first of these expressions is the initialization statement and will usually be an assignment. It is executed only once, at the very start of whatever looping is to follow. The second expression is a test statement. It is executed prior to each iteration of the loop. The body of the for loop is entered only if the test statement evaluates to true. The final expression is an increment statement that is executed at the conclusion of each iteration through the body.

As an example, the following code fragment prints out the numbers 1, 2, and 3:

  int   i;
  for ( i=1; i <= 3; i++ )
  {
     printf( "%d\n", i );
  }

We could re-code the above fragment to employ a while loop as shown below:

  int   i;
  i = 1;            // the initialization statement
  while ( i <= 3 )  // the test
  {
     printf( "%d\n", i );
     i++;           // the increment statement
  }

You can see that the for statement saves you typing when compared to the while statement.

It is instructive to realize that one way to create an infinite loop is the following for statement:

  for ( ; true ; )
  {
     <put the body here>
  }

When the computer sees this for statement is says, "There is nothing to initialize so I can proceed to the test. The test definitely evaluates to true so I enter and execute the body. At the conclusion of the body I would perform the increment but none was requested. I then return to the test which again evaluates to true. So I re-enter the body...". It is not obvious but the compiler will also accept the following shorthand for an infinite loop:

  for ( ; ; )
  {
     <put the body here>
  }

How can you get out of an infinite loop? Easy, use the break statement.


cScale Class

( 22 )

Implementing The cScale Class

Now that we have seen how easy it is to use a class that is provided to you, I am sure you are all motivated to learn how to write a class. The implementation of a class is split between a header (.H) file and a source (.CPP) file. For the cScale class, these are the cScale.h and cScale.cpp files.

You should already have the Scale example project open. The Files listbox shows you that this project consists of the 5 files Main.cpp, cScale.cpp, cScale.h, cTone.cpp, and cTone.h. Click on the cScale.h filename in the Files listbox so you can scroll through this file while I describe it.

At the top of the cScale.h header file you will observe:

#ifndef _CSCALE_H        // prevent multiple inclusion
#define _CSCALE_H

You should remember that commands that start with the pound symbol (#) are commands to the preprocessor, which is a program that manipulates our source code before passing it on to the compiler. Previously we have only seen the #include command to the preprocessor. The #define command is used to create macros which are a good way to place manifest constants in your programs. Each time the preprocessor finds a macro in your source code it substitutes the macro name for the replacement text given in the #define statement. An example of the #define command taken from the cTone.h file is:

#define  SAMPLE_RATE  11025

This statement simply tells the preprocessor to replace all occurrences of the text "SAMPLE_RATE" with the new text "11025". This is a convenient way to assign labels to special numeric constants. Otherwise, when you came upon the 11025 in the code you might wonder, "What the heck is this?" But when you come upon the text SAMPLE_RATE in the code you know you're looking at a number that describes a sample rate. It is a convention to only use upper-case letters for the names of the macros you create using the #define command.

The #define statement at the top of the cScale.h file creates a macro named _CSCALE_H which is equal to no characters. This may not seem useful but the key point is that the macro has been defined nonetheless. Take a look at the very bottom of the cScale.h file and you will find:

#endif // _CSCALE_H

Starting from the very top of the cScale.h file and continuing to the very bottom you are looking at a preprocessor if statement.

#ifndef _CSCALE_H        // prevent multiple inclusion
#define _CSCALE_H

      <the rest of the file>

#endif // _CSCALE_H

Although expressed in preprocessor language, this is equivalent to the C language if statement:

  if ( <some test> )
  {
      <the body>
  }

But the cute thing about the body of the preprocessor if statement is that it defines the _CSCALE_H macro. That means that once we enter the body of the preprocessor if statement we will never again enter the body since the test of this preprocessor if statement checks whether _CSCALE_H has been defined.

This probably seems confusing, but it solves a major problem when you create a large project with a lot of files. Let's suppose the cScale class that is declared in this cScale.h header file is immensely important to your project and is employed in lots of different .CPP files. Then each of those .CPP files will need to #include the cScale.h file in order to make sure the compiler has seen the declaration of the cScale class. The first time a .CPP file #includes cScale.h the C++ compiler will scan through the cScale.h file and find the declaration of the cScale class and say, "Okay, I've seen the cScale class get declared and he did a nice job." Then, when another .CPP file #includes the cScale.h file the C++ compiler will scan through it again and say, "Wait a minute, I have already seen something called the cScale class get declared and you aren't allowed to declare it again. This is an error."

By placing the entire contents of the cScale.h header file inside a preprocessor if statement we guarantee the compiler will scan this file only once, thereby avoiding this error. All the .CPP files that need to #include cScale.h can now do so without fear of generating a multiple declaration error. This trick is very common in C and C++ projects and you will probably see it used in any header file you come across. Note that the name of the macro (_CSCALE_H in this case) is not important to the functioning of the trick. The macro name merely needs to be unique from all your other macro names and hence a good convention is to derive it from the filename (cScale.h, in this case).

Okay, so getting back to the cScale.h file, the next thing we see is a bunch of #include statements that reference a bunch of header files that declare functions we plan to employ in the cScale class. If you remove any one of these #include statements you will get errors the next time you compile. I like to point out in a comment field the name of the function that forces me to #include the particular header file. Hence the line:

  #include <math.h>        // holds declaration for pow()
reminds me that I will get an error if I call the pow( ) function (which raises numbers to powers) unless I #include the math.h header file.

The angle brackets seen in the statement:

  #include <math.h>

tell the preprocessor that the math.h file can be found with the other header files supplied with the compiler. This is one of the few cases where the C/C++ compiler is sensitive to white space: you cannot include any space characters inside the angle brackets!

The double quote marks seen in the statement:

  #include "cTone.h"

tell the preprocessor that the cTone.h file can be found with the other source code files that make up this project. To keep this straight, just remember that we didn't write <math.h> but we did write "cTone.h".

The only other item in the cScale.h file is the declaration of the cScale class. Note that the declaration is the entire portion surrounded by curly braces after the class keyword. Inside these curly braces are 2 main sections: the public section and the private section. The code in the Main.cpp file of this project is going to define or instantiate a variable of the type cScale. This means that the code in Main.cpp will be able to call any of the functions listed in the public section of the class declaration. These functions are the public interface to this code. But the code in Main.cpp cannot call any of the functions listed in the private section (there actually aren't any functions listed in the private section of this particular class). Nor can the code in Main.cpp read or write any of the data variables seen listed in the private section. The private section holds implementation details that we don't want to expose to those who use this class.

This may seem like unnecessary formality. But let's again consider the million lines of LaserJet source code. Responsibility for this code was assigned to a team of programmers. Let's assume you had responsibility for the code that moved paper through the printing engine. And let's imagine that all the other programmers could make calls into all of your functions. This would mean they would have the ability to make paper move forward and backward by arbitrary amounts at random moments. Chaos would result. You would demand that everyone else stay the heck out of your code and only call the functions that were part of your official interface. This is exactly what is accomplished with the public and private sections of the class declaration.

Listed below are the entire contents of the public section of the cScale class declaration:

   cScale( double  dTonicFreqInHz, DWORD dwQuarterNoteDurationInMSecs );
                                     // the constructor
   virtual ~cScale();                // the destructor
   BOOL     PlayNote( int iNumHalfStepsAboveTonic, char chDuration );

At first these might look like ordinary function declarations but there is something wrong with the first two. Only the third statement is a legitimate function. It is called a member function since it belongs to a class. The first two statements are not legal function declarations because they don't list a return type. Note that these first 2 functions are named identically to the class. These two special functions are known as the constructor and the destructor.

In every C++ class you write you will usually want to define a constructor. In fact, you sometimes want to define multiple constructors. And if you don't provide a constructor the compiler will provide one for you (but you won't be able to see it). The constructor must be named identically to the class. The constructor can take whatever passed parameters you desire but it doesn't return a value. Instead it "returns" a fully functional instance of that class (hence the name constructor). When you create multiple constructors they must each take different passed parameters (this is how the compiler can tell them apart).

Furthermore, every C++ class can define a destructor named the same as the class but with the addition of a leading tilde (~). The destructor is responsible for whatever end-of-life clean-up an instance of the class needs. I think my typing fingers are going to drop off before I get around to explaining virtual functions so just accept the presence of the virtual keyword in the destructor. Get used to always including this keyword with any destructor you write even though you don't know why (it solves a sort of hole in the C++ language).

We have already seen how the code in the main( ) function employs the constructor to create two instances of the cScale class that have unique state: one plays a scale rooted on 220 Hertz while the other plays a scale rooted on 300 Hertz.

In the private section of the cScale class declaration we see a few data variables. These variables hold the state of any instance of the class. Two of these data members are simple variables (that is, their data type is a type that is built into the language) while the third is an instance of the cTone class. It is conventional to name class data members starting with the letters m_ to indicate that they are data members. You see, you will soon have so many variables floating around your code that it helps to choose really descriptive names for things. The m_ prefix is employed in most C++ textbooks but Microsoft has defined a whole bunch more prefixes which leads me into a discussion of variable naming.

One of the dominant computer operating systems is the UNIX operating system. Because UNIX was used at most universities that awarded computer science degrees, it became pervasive in industry. The UNIX style for variable names is to employ very short, highly abbreviated identifiers that only employ lower case letters. For example, the UNIX command that searches through files looking for a particular text string is the grep command (which stands for "gather and report"). The authors of the C language (Kernighan and Ritchie) employed this variable naming style in their seminal book on the C language. Typical variable names would be i for any small integer used for counting and len for a length. Typical function names would be getch( ) for "get character" and strcmp( ) for "string compare".

Microsoft came along after UNIX and implemented a different naming convention, that I find superior. A typical function name in the Microsoft WIN32 API is GetWindowRect( ) which returns the size of the rectangle that surrounds a window. I find that it improves readability to employ multiple words with a mix of upper and lower case letters. But the Microsoft naming convention goes further to something called Hungarian notation (because its inventor, Charles Simonyi, was Hungarian). All variables of a simple type are labeled with a lower-case prefix that identifies their type. Here are some examples of Hungarian notation:

  int    iResult;
  UINT   uiNumBytes;   // a UINT is an unsigned int
  DWORD  dwSize;
  float  fSum;
  double dCosine;
  char   chLetter;

You can see that I am employing Hungarian notation for both the data members of the cScale class and for the passed parameters to the member functions of the cScale class. Obviously, when you are working on code such as:

  DWORD   dwResult
  dwResult = 5;

where the use of the variable is very close to its definition then it is not hard to remember the data type of the variable. But at other times the use of the variable can be a great distance from its definition and I like that little reminder as to what kind of thing this "Result" is. dwResult is clearly a result that has the data type of DWORD.

Let' move on to the cScale.cpp file. Click on the cScale.cpp filename in the Files listbox so you can scroll through this file.

You will see that the cScale.cpp file #includes its own cScale.h header file. Without this statement, when the compiler comes scanning through the cScale.cpp file it will discover something called a cScale and say, "Hey, what's this? Where is this cScale class declared?" The only items in the cScale.cpp file are implementations of the 3 functions that are part of the cScale class. Note how the scope resolution operator, which is a pair of colons (::), must be employed in the .CPP file so that the compiler understands that these functions belong to the cScale class. The double colons separate the class name from the function name.

The first of these 3 functions is the constructor. It receives 2 passed parameters known as dTonicFreqInHz and dwQuarterNoteDurationInMSecs. The constructor uses these 2 passed parameters to initialize the object's data member variables m_dTonicFreqInHz and m_dwQuarterNoteDurationInMSecs.

cScale::cScale( double dTonicFreqInHz, DWORD dwQuarterNoteDurationInMSecs )
 : m_dTonicFreqInHz( dTonicFreqInHz ),
   m_dwQuarterNoteDurationInMSecs( dwQuarterNoteDurationInMSecs )
{
}

This is the first time we have seen a constructor so it is the first time we have seen an initializer list which is the stuff that follows the colon (:). I know your head is starting to hurt so I will rewrite this constructor in an equivalent and perfectly acceptable manner that does not make use of an initializer list:

cScale::cScale( double dTonicFreqInHz, DWORD dwQuarterNoteDurationInMSecs )
{
   m_dTonicFreqInHz = dTonicFreqInHz;
   m_dwQuarterNoteDurationInMSecs = dwQuarterNoteDurationInMSecs;
}

This should make it more clear that all we are doing is initializing our internal data variables to agree with the parameters passed to the constructor. The initializer list might appear to be just another example of syntactic sugar but actually, using initializer lists will make your programs run faster in certain situations.

Let me repeat that the purpose of any constructor is to create a working instance of the class. Until we get our m_dTonicFreqInHz and m_dwQuarterNoteDurationInMSecs data members initialized we obviously don't have a working instance of the cScale class. And once these data members are initialized the particular instance of this class knows its state.

The cScale destructor is completely empty. For this class there are no special activities we need to do at that solemn moment known as end-of-life.

Now look at the PlayNote( ) member function. I really should describe this as the cScale::PlayNote( ) member function. The first parameter passed to this function identifies the note of the scale that is being requested and the second passed parameter identifies its duration. The PlayNote( ) function is a blocking function which means that it will not return to the caller until this note has been completed. The alternative, which is called non-blocking, would be to return to the caller as soon as the requested note has been started. This would give the calling code an opportunity to do something while the note is still being played, which is an advantage in certain situations.

There are 2 new C language features seen in the cScale::PlayNote( ) function. The first is the if/else statement. Previously we have only seen an if statement with a single block (remember, any code surrounded by braces is a block). But else is also a language keyword and can be followed by another block. Only one or the other block will be executed, as seen in the example below:

  if ( iResult < 0 )
  {
      <deal with a negative number>
  }
  else
  {
      <deal with 0 or a positive number>
  }

You can even create an if/else if/else if/else statement of unlimited depth, as seen below:

  if ( iResult < 0 )
  {
      <iResult is a negative number>
  }
  else if ( iResult > 0 )
  {
      <iResult is a positive number>
  }
  else
  {
      <only 1 possibility left, iResult is 0>
  }

Sometimes you need so many "else if" clauses that a switch statement would be easier. You can observe the switch and case keywords in that section of the cScale::PlayNote( ) function that figures out a duration in milliseconds given a note name, such as 'e' for an eighth note or 'q' for a quarter note. The switch statement has a curious property that only sometimes comes in handy: the case statement decides where you enter the list of statements but it does not control where you exit. Execution will continue from wherever you enter until the very end of the switch statement, unless a break statement is encountered. This basically means you will always want to conclude the list of statements in each case section with a break statement, as you see in cScale::PlayNote( ).

To show you what happens if you leave out the break statements, consider the following code:

  int   i = 2;

  switch ( i )
  {
    case 0:
       printf( "i = 0\n" );
    case 1:
       printf( "i = 1\n" );
    case 2:
       printf( "i = 2\n" );
    case 3:
       printf( "i = 3\n" );
    case 4:
       printf( "i = 4\n" );
  }

Executing this code will result in the following print-out:

  i = 2
  i = 3
  i = 4

This is called falling through a switch statement. Usually you don't want this behavior so usually you have a break statement for every case statement.

You can use the default keyword in a switch statement to capture all of the other possibilities not already mentioned by explicit case statements. In the case of the cScale::PlayNote( ) function, I can't guarantee that the caller of this function will supply a legal note duration letter such as 's', 'e', 'q', 'h', or 'w'. I list all the legal values with explicit case statements and then I use a default statement to capture all other letters (such as 'r' or 'y'). If the PlayNote( ) function receives one of these wrong letters it just pretends it received a request for a quarter note. Take a look at my source code to see this.

The cScale::PlayNote( ) function ends with the statement:

  return m_Tone.PlayTone( dFreqInHz, dwDurationInMSecs );

You have probably forgotten what m_Tone is. Look again at the declaration of the cScale class:

class cScale
{
public:
 ...
private:
    double   m_dTonicFreqInHz;
    DWORD    m_dwQuarterNoteDurationInMSecs;
    cTone    m_Tone;
};

So m_Tone is a data member of the cScale class. m_Tone is an object and has the data type of "instance of cTone class". This means that we can call any of the public member functions of the cTone class on our instance m_Tone. So the statement at the end of cScale::PlayNote( ):

  return m_Tone.PlayTone( dFreqInHz, dwDurationInMSecs );

is a call to the cTone::PlayTone( ) function defined in the cTone class.

Let me review the various notations we have seen. There is a PlayNote( ) member function in the cScale class so we refer to it using the scope resolution operator as cScale::PlayNote( ). The scope resolution operator ties a member function name to its class. The scope resolution operator makes the PlayNote( ) function in the cScale class unique from any other PlayNote( ) function we might decide to employ in a different class.

This was a real problem with the C language. Consider again that million lines of source code owned by Hewlett-Packard for their LaserJet printers. Suppose you work for HP and one day you are writing some new code and you decide to name a function HandleError( ). How do you know nobody has ever used this function name before? You can either search through all the source code or just depend upon the compiler to give you an error message if someone else has already used this function name. In this scenario it is not such a big deal for you to change your function name. But suppose Hewlett-Packard purchases some small company in order to integrate that company's 100,000 source code statements into the LaserJet code base. If that company had used the HandleError( ) name for a function then there is going to be a conflict that is much harder to resolve.

These problems kept appearing in industry and that is why the C++ language invented something called namespaces. I don't bother to teach about namespaces because the encapsulation properties of classes has largely solved the problem by itself. There is no conflict between a HandleError( ) function in the cFont class (as an example) and an identically named HandleError( ) function in the cMovePaper class. The scope resolution operator allows the compiler to keep these separate functions separate.

Now let's return to our program. I was reviewing the various notations and have just covered the scope resolution operator. In the Scale example program we call the cScale::PlayNote( ) function on our MyFirstScale instance of the cScale class. You can find this call in the Main.cpp file:

    MyFirstScale.PlayNote( 0, 'q' );  // play the tonic of the scale

Here we see the dot operator, which is how you call a particular function on an instance of a class. We can see another example of the dot operator by looking at the very end of the cScale::PlayNote( ) function. There we see a call to the cTone::PlayTone( ) function made on an object named m_Tone. This call looks like:

    return m_Tone.PlayTone( dFreqInHz, dwDurationInMSecs );

Are you clear on the difference between the scope resolution operator and the dot operator?

I hope you can now read through cScale.h and cScale.cpp and understand all the code. I isolated the nasty stuff required to play sounds on a Windows computer and placed that code in the cTone.h and cTone.cpp files. You can look at those files if you have a high pain threshold. I could have hid this code from you but I didn't in case some of you want to adapt it for other projects (or just see what real Windows code looks like). But the cScale.h and cScale.cpp files should be easily digestible.

If I was going to sell the code for this cScale class commercially I would first flesh it out with every bell and whistle I could think of. I would probably add a new member function that reports the tonic frequency to the caller. To do this I would amend the class declaration in the cScale.h file to look like:

class cScale
{
public:

    cScale( double  dTonicFreqInHz, DWORD dwQuarterNoteDurationInMSecs );
                                      // the constructor
    virtual ~cScale();                // the destructor

    BOOL     PlayNote( int iNumHalfStepsAboveTonic, char chDuration );
    double   GetTonicFreq();
private:
    double   m_dTonicFreqInHz;
    DWORD    m_dwQuarterNoteDurationInMSecs;
    cTone    m_Tone;
};

Find where I have added the new member function:

    double   GetTonicFreq();

This function requires no passed parameters and returns a double (i.e., a floating point number).

The implementation of this new member function would be put in the cScale.cpp file and could look like:

double  cScale::GetTonicFreq()
{
    return m_dTonicFreqInHz;
}

If I had incorporated this new GetTonicFreq( ) member function then the code in main( ) could use it to interrogate the two instances of the cScale class that are created in main( ). A call to the GetTonicFreq( ) member function might look like:

    printf( "The root freq of the first scale is %lf Hz\n",
            MyFirstScale.GetTonicFreq() );

This call to the printf( ) function employs the %lf format specifier rather than %d because we want to print the value of a variable whose data type is double (i.e., a floating point number).

At this time open the Scale2 project. You will see that I have incorporated a call to the new GetTonicFreq( ) function in the Main.cpp file. But I have NOT made the corresponding changes in the cScale.h and cScale.cpp files. This means that the Scale2 project will not compile as things stand initially. Go ahead and perform the compile and you will get 2 error messages for the 2 spots where I attempt to call the GetTonicFreq( ) member function. The compiler complains that it cannot find a "matching function". Remember that you can RIGHT click on any error message painted in red in the Compiler message area of CPPIDE and be taken to the offending line of your source code. Alternatively, you can keep pressing F4 to be taken to the locations of your first 6 errors.

Now that you have seen the problem, modify the cScale.h and cScale.cpp files to eliminate the compiler errors. This merely requires you to complete the implementation of the new GetTonicFreq( ) member function. Try to do this without referring back to these notes. I have provided my solution in the Solutions folder, as usual.


Extending cScale

( 23 )

Extending The cScale Class

You have already extended the cScale class once when you added the GetTonicFreq( ) member function. But that time I spoon fed you the changes. So let's make another change and see if you can do all the steps yourself.

In the main( ) function used in the Scale project you may have noticed a call to the Sleep( ) function. This is a function that is provided with the compiler and hence all you need to do to employ it in your code is to #include <windows.h>. Windows.h is a monster header file that holds the declarations for most of Microsoft's WIN32 API (application programming interface) which is the set of functions that Microsoft provides for the purpose of creating Windows programs.

The argument to the Sleep( ) function is a delay in milliseconds. In order to make music with our cScale class we need to add the ability to play "rests", which are moments of silence. But we need these rests to be properly timed. There are eighth note rests, quarter note rests, etc. So we need to augment the cScale class with the following new member function:

  BOOL   PlayRest( char chDuration );

Note that only 1 passed parameter is required. I like to have most of the functions I write return a BOOL (Boolean) that reports to the caller whether the function succeeded. This is a common convention seen in both the UNIX and Windows camps. This particular function can't fail [since the Sleep( ) function can't fail] and hence the last line of the implementation of the PlayRest( ) function will be:

   return true;

At this time open the Scale3 project. This project, like the prior Scale2 project, will not initially compile because the main( ) function is calling the new PlayRest( ) member function that has not yet been declared in the cScale.h file or implemented in the cScale.cpp file. This is your assignment. To accomplish the delay you merely need to call the WIN32 Sleep( ) function from within the new PlayRest( ) member function. But first you will have to convert from a "duration character" such as 's', 'e', 'q', etc. to a time in milliseconds. You can copy and paste a similar section of code out of the PlayNote( ) function to hasten your work. When you get a successful compile you should then be able to run the program and hear a couple of great rock and roll riffs. If instead of music you hear some obnoxious squeaks then it is probably because when you copied code from PlayNote( ) you left in the call to cTone::PlayTone( ). In the new PlayRest( ) function we want to call Sleep( ) to accomplish the time delay that constitutes a musical rest.

The music you hear will sound a little cheesy, because it is being produced by a mathematically perfect sine wave. The kind of sounds that the human brain enjoys hearing in music are vibrations that have not only a fundamental frequency but also overtones which are higher frequencies that are mathematically related to the fundamental. Furthermore, the envelope of a vibration in a string or pipe shows 4 regimes known as the attack, decay, sustain, and release (ADSR). These 4 regimes can be seen in the middle picture below. The top picture below is the type of sound cScale generates while the bottom picture is a multi-frequency ADSR vibration.

You can hear each of the types of sounds pictured in the photo above by clicking here to hear the boring sound (such as cScale generates), here to hear an improved sound that is still a single frequency but at least has an ADSR envelope, and here to hear a mix of frequencies.

Feel free to modify the code in main( ) to play your favorite tune! For example, once you get a short riff working, you might like to hear it played back at two different tempos. You can do this by instantiating a second cScale variable with a different quarter note duration (this is one of the parameters to the cScale constructor).

As I was developing the riffs used in the main( ) function of the Scale3 project I realized I needed the ability to produce dotted quarter notes and dotted half notes, etc. A dotted quarter note is a quarter note that is half again as long, and hence equals a quarter note plus an eighth note. So let's consider how we might implement this new functionality. Since all notes can be "dotted", we might decide to add another passed parameter to the PlayNote( ) function. What data type should this passed parameter be? Musically, a note is either dotted or it is not dotted. So it wouldn't be a good idea to select the int data type for this new passed param since then the caller would have the freedom to supply too many possible values. When you design an interface to a section of code you want to help avoid mistakes by those who will use your code. So in this case it would be better to select the BOOL (Boolean) data type for this new parameter since then the caller can only set it to true or false. With this design the signature for the PlayNote function would look like:

  BOOL     PlayNote( int iNumHalfStepsAboveTonic,
                     char chDuration,
                     BOOL bDotted );

However there are always many ways to code something. An alternative design would keep the original passed parameters to the PlayNote( ) function and instead augment the legal range for the chDuration "duration letters". The letter 'q' indicates a quarter note and the letter 'r' is unused and follows 'q' in the alphabet so 'r' could be used to request a dotted quarter note. Similarly, 'f' could be a dotted eighth note, and 'i' could be a dotted half note. We are lucky that the characters we want are all available. With this design we do not need to change the signature of the PlayNote( ) function.

Yet another design possibility would be to change the data type of the duration letter passed parameter. Presently, this passed parameter has the data type of char, which means that it can hold only a single letter. If we change the declaration of the PlayNote( ) function to:

  BOOL     PlayNote( int iNumHalfStepsAboveTonic,
                     char * pszDuration );

then the pszDuration parameter can be a string of letters. We would request a regular quarter note by initializing pszDuration with "q". We would request a dotted quarter note by initializing pszDuration with "dq". Notice how we have to switch from single quotes (') to double quotes (") when we switch from a single letter to a string of letters. The asterisk (*) indicates a pointer which is a variable type that holds a data memory address. There is a lot of complexity involved in strings so we will give them their own section.


Error Messages

( 24 )

Compiler Error Messages

I've now got you started writing a bit of C++ which means you have probably seen a few error messages. Hopefully, the ones you have seen have helped you track down the problem. For example, if you compile the Scale2 project before you add the code for the new cScale::GetTonicFreq( ) function then the compiler will generate the following error message:

Main.cpp: In function `int main()':
Main.cpp:36: no matching function for call to `cScale::GetTonicFreq()'

This message can be translated as, "I was going through the code in the Main.cpp file and I was inside the function that has the signature "int main( )" when on line 36 I came upon a call to a cScale::GetTonicFreq( ) function which I can't match up with any of the member functions in the cScale class". Since this error message is painted in red you can RIGHT click on it and be taken to line 36 of the Main.cpp file which happens to be the 2nd line of the code fragment shown below:

    printf( "The root freq of the first scale is %lf Hz\n",
            MyFirstScale.GetTonicFreq() );

This example shows the C++ compiler at its best. Unfortunately, not all error messages are this helpful. Some of them are horrendously cryptic and others are downright misleading. I want to show you examples of these problems and then I will try to explain why very smart people have not been able to improve these error messages.

Together we are going to purposefully mutilate some working code in order to observe how well the compiler can tell us about the problem. Because we are going to be doing nasty things, I have prepared a special project that we won't miss if you forget how to repair the last change you made. This special project is named the Errors project, is located in the Errors folder, and is simply a copy of the Scale project.

So at this time open the Errors project. You will observe that it consists of the 5 files we have been studying: Main.cpp, cScale.h, cScale.cpp, cTone.h, and cTone.cpp. Compile this project before you do anything else so you can see that initially it compiles just fine.

Follow my instructions exactly, because I have arranged my examples in order from best (the compiler's error messages are great) to worst (the error messages are no help at all).

For our first contamination, edit the Main.cpp file and change the #include statement near the top of the file to the following:

#include "cSc2ale.h"

Obviously there is no cSc2ale.h file in this project. Now compile the project. You get the error messages:

initiating compile, be patient...
g++ -g -D_DEBUG -c  -o Debug/Main.o Main.cpp
Main.cpp:6:21: cSc2ale.h: No such file or directory
Main.cpp: In function `int main()':
Main.cpp:17: `cScale' undeclared (first use this function)
Main.cpp:17: (Each undeclared identifier is reported only once for each
   function it appears in.)
Main.cpp:17: parse error before `(' token
Main.cpp:19: `MyFirstScale' undeclared (first use this function)
Main.cpp:28: parse error before `(' token
Main.cpp:30: `MySecondScale' undeclared (first use this function)
Main.cpp:35: `Sleep' undeclared (first make had errors: RIGHT click any colored error line.

We see the g++.exe executable, which holds the Gnu C++ compiler, beginning to process the Main.cpp file into the intermediate object file Main.o (which is placed in a folder named "Debug"). But when the compiler gets to line 6 it cannot find a file or directory named "cSc2ale.h". You can right-click on the error message or select "Edit/Goto Line..." from CPPIDE's menu to go to line 6. Incidentally, the notation "6:21" seen in this error message says the problem began at character 21 of line 6 (that happens to be at the end of the line).

Even though it got this error, the compiler proceeds to examine the rest of the file so that it can tell you about as many problems as possible. On line 17 of Main.cpp the compiler generates a second error message that complains that cScale is an undefined identifier. That is not surprising, since when we corrupted the #include statement we prevented the compiler from finding the declaration of the cScale class. The compiler again proceeds forward in the file and generates a third error message when it comes to the opening parenthesis on line 17. This parenthesis is illegal because the compiler does not recognize cScale as a type. The compiler again moves forward and declares on line 19 that MyFirstScale is undeclared. This variable name is undeclared at line 19 because line 17 was defective.

So you see the general trend: one mistake usually causes a cascade of error messages. It is not uncommon to make a single change in a working program and then get hundreds of errors the next time you compile. Experienced programmers generally only worry about solving the very first error message because it is likely that the subsequent error messages will disappear automatically once the first error message is prevented.

Let's now skip over a few error messages and look at the error message that complains that the Sleep( ) function employed on line 35 is undeclared. The Sleep( ) function is part of Microsoft's WIN32 API and all we need to do to get the Sleep( ) function declared in our program is to #include <windows.h>. Where does that happen in our program? You guessed it: our #include statement is in the cTone.h header file which is not getting included because the #include "cTone.h" statement is in cScale.h which we are NOT including because we corrupted the "#include "cScale.h" statement to read "#include cSc2ale.h".

Repair the Main.cpp file and recompile so you can see that all is well again. Then remove the semicolon that terminates the statement in Main.cpp that instantiates a variable named MyFirstScale. The corrupted statement is shown below:

  cScale   MyFirstScale( 220, 500 )  // note missing semicolon

When you now compile you get an error message that complains there has been a parse error prior to the period on line 19. Parsing is the job of assigning meaning to each of the sections of your statements. For example, the compiler parses the statement:

  int  i = 3;

into 5 pieces, a data type (int), a variable name (i), an assignment operator (=), a number (3), and a statement terminator (;). Once it has recognized the pieces, the parsing process is able to infer that this is a statement that declares and initializes an int variable.

So the compiler has complained about line 19, but we know we corrupted the previous source statement. This is another common situation. There are many situations where it is legal to not end a line with a semicolon. So the compiler cannot declare an error on line 17. Instead the compiler must proceed forward and parse additional text. In this case when the compiler gets to the period in the next statement it can conclude that something is wrong and hence flags an error on this line. Experienced programmers know to treat the line number mentioned in an error message as the beginning line of a search that proceeds backwards through the source code. If the error message mentions line X, then perhaps 60 % of the time the error really is on line X, and 30 % of the time it is on line X-1, and 10 % of the time it is even further before line X.

Repair Main.cpp and recompile to show that you've got clean code once again. Then remove the closing parenthesis that is needed on the statement in Main.cpp that instantiates a variable named MyFirstScale. The corrupted statement is shown below:

  cScale   MyFirstScale( 220, 500 ;

In this case the compiler complains about a parse error before the semicolon on this very line. The compiler goes on to line 20 and complains:

  request for member `PlayNote' in `MyFirstScale(...)', which is of
   non-aggregate type `cScale ()(...)'

The manner in which we have corrupted Main.cpp does not prevent the compiler from reading the declaration of the cScale class. The compiler is aware of the cScale class and the compiler is aware of the PlayNote( ) member function in this class. This error message is not complaining about the class, it is complaining about the object (instance of the class) named MyFirstScale. Our corruption has the consequence that our program doesn't get the MyFirstScale object instantiated and hence when we try to call a member function on this broken object we cause this error message. But that wasn't very clear from the error message, was it?

Again repair Main.cpp. Now go find some Kleenex because I am going to make you cry. Edit the cScale.h file and remove the closing curly brace on the declaration of the cScale class. That is, the very bottom of the cScale.h file should look like:

private:
    double   m_dTonicFreqInHz;        // frequency of the scale's root note
    DWORD    m_dwQuarterNoteDurationInMSecs;  // time in millisec
    cTone    m_Tone;                  // an instance of the cTone class
;     // note missing }

When you now compile you get a single error message that complains about "parse error at end of input" and points you to the very last line of the Main.cpp file, which we know is no where near the problem!

Repair cScale.h but keep those Kleenex handy. This time edit the Main.cpp file and place a single letter 'a' as the very first character in this file. That is, the very top of this file should look like:

a
// This is the file Main.cpp which holds the main()
// function for this console-mode program that
// employs the cScale class.

#include "cScale.h"

When you now compile you get a long list of error messages. The first of these error message is:

In file included from C:/Computer Science Lab/CppIde/mingw/include/stddef.h:6,
                 from C:/Computer Science Lab/CppIde/mingw/include/stdio.h:42,
                 from cScale.h:13,
                 from Main.cpp:6:

This message means that on line 6 of Main.cpp there was a #include statement that included cScale.h. So the compiler started scanning that file, but on line 13 of cScale.h there was a #include statement that included stdio.h. So the compiler started scanning that file, but on line 42 of stdio.h there was a #include statement that included stddef.h. So the compiler started scanning that file. Line 203 of that file is a typedef statement that is supposed to define the size_t type. But the compiler experienced a parse error prior to the word "typedef" on this line. Because this statement in stddef.h could not be parsed the compiler then generates a bunch of additional error messages of the form "type specifier omitted for parameter size_t".

Clearly, this error message is not going to help you hunt down the letter 'a' at the top of Main.cpp. Also note that none of the error messages are colored in red because none of them references a line in one of your editable source files. All the error messages refer to files that come with the compiler and hence you should not touch.

I'll give you a few minutes to recompose yourself and throw away those wet Kleenex.

dum, dee, dum, dum...

I was just as depressed as you are when I first saw error messages produced by C or C++ compilers. Before learning C and C++ I had programmed on a more restrictive language. The compiler for that language could always point at exactly the right statement when there was a problem. I didn't initially understand why the compiler for the world's most powerful programming language couldn't do as much.

The answer was revealed years later when I took a course on compilers. The problem with the error messages stems from the fact that the language allows any expression of type X in any place where type X is legal. This is wonderful for the programmer, but murder on the compiler. In more primitive languages, when you want to call a function that takes an int you are only allowed to supply the name of an int variable or an integer constant. This means the compiler will only have to scan a few characters before either concluding that the statement is correct or defective. But in the C/C++ language you are allowed to supply any expression that evaluates to an int. Hence the compiler is forced to continue scanning through your source code for as long as there is any possibility that your statement will eventually result in a legal int. Since expressions can be of unlimited length, it could take the compiler quite some time to conclude that no way is this mess ever going to result in a legal int. By the time the compiler can definitively conclude there is a problem, it may be pages away from the true source of the problem. This may be hard for you to understand, because the error so stands out in your source code. But a compiler is a computer program and computer programs do not have near the intelligence of the human brain.

Here is the strategy that will keep you sane:

  • Only worry about the first 1 or 2 error messages. When you think you have them solved, recompile. [This is the reason the F4 keystroke only takes you to the sites of your first 6 errors.]
  • Search for the problem by working backwards from the line mentioned in the error message.
  • Compile often. If you have only added 10 lines since your last clean compile then you already have the error location narrowed down to 10 lines! As you gain experience you can go longer between compiles.


( 25 )

Strings

I now want to show you an example program which can serve as your introduction to strings and the many string manipulation functions offered in the C/C++ language.

We are going to write a program that knows a secret password, which is a string of characters. A C or C++ program can employ the char data type to hold a single letter of the English alphabet. A char is a byte and a byte is 8 bits which is large enough to hold 256 different values. This is sufficient as long as your alphabet has fewer than 256 letters (both upper and lower case) and punctuation symbols and numeric digits.

A string is a sequence of characters. You can probably guess that if I ask the computer to put the string "password" in memory then the letter 'p' will go in memory at some address X, 'a' will go in memory at address X+1, etc. This sequential nature is the fundamental property of an array. You could have an array of ints (which would be a good way to store test scores in consecutive memory locations) or an array of floats (perhaps to hold a pitcher's annual ERA stats). A string is an array of chars. Arrays are an important enough topic that I give them their own page.

You can use an ASCII table to determine that the numeric value of 'p' is 112 = 0x70. That is, the value 112 has been arbitrarily assigned to represent the letter 'p' according to the ASCII standard. The value of 0 does not represent any character in the alphabet nor any digit nor any punctuation symbol. Hence the value 0 is useful to indicate the end of a sequence of ASCII bytes, which is exactly what a string is.

So here's the crucial point regarding arrays of chars: when you request the 8 character long string "password" the compiler actually places 9 bytes into memory because all strings are NULL terminated. A NULL is simply the value 0, which does not correspond to any legal character.

Single quote marks are used to indicate an individual character while double quote marks are used to indicate a NULL terminated string. Do not confuse:

'x'

with:

"x"

The former is only 1 byte while the latter (because of the NULL termination) is 2 bytes.

There are two ways to represent in a C/C++ program a character with the value 0: NULL or '\0'. I usually prefer the latter since it looks more like a character (NULL I save for use with pointers that have the value 0). No matter which notation you choose, it is still called the NULL termination.

Somebody had to decide which of the 256 possible byte values to employ for the letter 'a', and what value for the letter 'b', etc. This is an example of standardization since it would not do the world any good to have a bunch of incompatible representations for the English alphabet. The C and C++ languages employ the ASCII assignments summarized in the table below:

Letter | Decimal | Hex
'\0' | 0 | 0x00
'\n' | 10 | 0x0a
' ' (space) | 32 | 0x20
'0' | 48 | 0x30
'1' | 49 | 0x31
... | ... | ...
'9' | 57 | 0x39
... | ... | ...
'A' | 65 | 0x41
'B' | 66 | 0x42
'C' | 67 | 0x43
... | ... | ...
'a' | 97 | 0x61
'b' | 98 | 0x62
'c' | 99 | 0x63
... | ... | ...

So, if your program includes the following statement:

  printf( "Hello World\n" );

then the compiler will place into memory the following 13 bytes:

'H' | 'e' | 'l' | 'l' | 'o' | ' ' | 'W' | 'o' | 'r' | 'l' | 'd' | '\n' | '\0'
0x48 | 0x65 | 0x6c | 0x6c | 0x6f | 0x20 | 0x57 | 0x6f | 0x72 | 0x6c | 0x65 | 0x0a | 0x00

These particular bytes would go into program memory rather than data memory because they do not represent a variable, they represent a constant. They are part of the printf( ) statement and will not need to change during execution of the program. But other strings are placed into data memory and hence are variables just like ints and floats. These strings can be initialized and re-initialized just like any other variable.

So why does the compiler add a NULL (zero valued) character at the end of all strings? Well, consider the functions that you might want to create to manipulate strings. A function that would convert an arbitrary string to all upper-case characters would be handy. As would a function that reverses a string, or searches the string for the presence of a substring (a smaller string), or searches the string for the first occurrence of a letter, etc. If you wrote these functions you would need one passed parameter that referred to the string and another passed parameter that stated the length of the string. The length is needed because once the English alphabet letters have been converted to ASCII they look just like all the other bytes in memory and hence it is not obvious where the string ends.

But one way to make the end of the string obvious is to employ a unique, recognizable termination character. All the functions I previously mentioned -- and many more -- have already been written for you and can be employed in your programs once you #include <string.h>. And all these functions avoid the need for a length passed parameter because they ASSUME the sequence of characters will end with a NULL character. Pretty clever, huh?

To reserve enough memory to hold a single character you can use a definition like the following:

   char   chLetter;

If you want to reserve enough space for a string of 10 characters you could specify:

   char   achLetters[10+1];

This is, of course, no different than typing:

   char   achLetters[11];

but I prefer the former notation because it reminds me that this char array has room for 10 characters plus the terminating NULL character. The Hungarian prefix "ach" indicates "array of characters".

The square brackets [ ] are a C/C++ operator that indicate an array. At the time you define a char array, you can also initialize it in the following manner:

   char   achLetters[10+1] = "Hi There";

If the initializer on the right-hand side of the equals sign does not specify enough characters to completely fill the memory space you have requested, the compiler places NULLs (zeros) into the extra locations. This is one of the few times the compiler initializes something for you.

You will get a compiler error if you attempt to employ the equals sign with a char array at any time other than during the initialization of the variable. That is, it is an error to attempt to change the contents of the achLetters array via a statement like:

    achLetters = "New content";

In the next section we will learn about the strcpy( ) function that serves this purpose. Similarly, you cannot employ the equality operator (==) to compare two strings. In the next section we will learn about the strcmp( ) function that serves this purpose. However you can employ the equality operator to compare individual characters from two strings. This is a subtle point: there is a difference between the entire string and a single character in that string. The former has the data type char array while the latter has the data type char.

You use the square bracket notation to refer to single characters in a char array, as seen below:

   printf( "The 3rd letter is %c\n", achLetters[2] );

There is not a typo in this last code fragment: the individual entries in an array are indexed starting from 0 and hence the 3rd entry is identified by the index value of 2. The %c format specifier is used to print a single character while the %s format specifier is used to print a NULL terminated string.

At this time open the Password project and read through the code. The intent of this program is to ask the operator to enter a string and then compare that string, character by character, to a password. The program uses the fgets( ) function that comes with the compiler to read the keystrokes typed by the operator and place these characters into the szGuess char array. "sz" is the Hungarian notation for NULL (zero) terminated string. If you look at Microsoft code you will see the sz prefix being used more often than the ach prefix, but they mean the same thing. One of the passed parameters to the fgets( ) function needs to be the dimensioned size of the destination char array so that fgets( ) can avoid overwriting the destination memory area set aside to accept the characters typed by the operator. Note that fgets( ) does not return until the operator has hit the ENTER key. The newline character ('\n') produced by this ENTER keystroke is included in the characters that fgets( ) places in szGuess.

The algorithm for comparing the 2 strings uses a BOOL (Boolean) variable that is initialized to true prior to a loop that checks the first 6 characters of each string. If any one of these first 6 characters does not match then the bMatched boolean is re-initialized to false and the break statement causes the loop to exit (why bother checking more characters?).

Now compile and execute this program. Try it for a few test cases. We will add more to this code in the next section. If you don't already understand the operation of this program then employ the debugger to single-step it.

Let me emphasize here that NULL terminated strings are the only type of array that employs the special zero valued entry to demarcate the end of the array. If you want an array of 10 ints then you want a definition statement like:

   int   iMyArray[10];

not like:

   int   iMyArray[10+1];


String Manipulation

( 26 )

The String Manipulation Library

Most books that teach beginning C or C++ programming stick to console-mode programs because they are simpler to create. And hence most of the programming examples in these books relate to text manipulation, since that is what console-mode programs do. There is so much material available in library books and on the Internet to teach you about these functions that I don't want to spend my time repeating it. So I am just going to show you the flavor of these programs and let each of you decide if this is something you want to dig into further.

At this time open the Password2 project and read through the code. The first change you will notice is the statement:

#define  MAXSTRINGLEN  100
which uses the #define preprocessor command to define a macro named MAXSTRINGLEN to represent the manifest constant 100. In the previous Password program the number 100 appeared on 3 different lines. If someday you decide to change this number will you remember to change it on all 3 lines? By using a macro you assign this burden to the preprocessor. In the new Password2 program you only have to modify 1 line (the #define statement) to change this value and the preprocessor takes care of the other lines. It is a convention to only use upper-case letters for the names of the macros you create using the #define command.

The second change seen in the Password2 program is the addition of the statements:

    if ( szGuess[strlen( szGuess )-1] == '\n' )
         szGuess[strlen( szGuess )-1] = '\0';

There is a surprising amount of complexity in these two statements so I want to make sure you understand them. Let's start by imagining a situation where you know that the szGuess variable (a variable of type string) holds 7 characters. Then we know that this string actually occupies 8 bytes in memory since there will be a NULL termination ('\0') after the 7 characters. If we wanted to know whether the final character held a newline character ('\n') then we could employ the equality operator == to construct the boolean expression used for the test of an if statement. This is shown below:

    if ( szGuess[7-1] == '\n' )

The reason we have to subtract 1 from the index used on the szGuess array (a string is an array of chars) is because the index for any array begins with 0. Hence the 7th character is szGuess[6].

Now in the general situation we will not know what has been put into szGuess since it depends upon what the operator types on the keyboard. So if we want to determine whether the final character is a newline character then we can't hard code a specific index in the if statement. But fortunately the C language allows us to use any expression that evaluates to an int in any location where an int is acceptable. This means that we can call a function that returns an int and use the return value from this function as the index into the array.

Every C and C++ compiler comes with just the sort of function we need. It is called the strlen( ) (string length) function and it returns the number of characters in the string. Hence szGuess[strlen( szGuess ) -1] will always be the final character in the szGuess string, no matter what the operator typed.

The first statement shown below:

    if ( szGuess[strlen( szGuess )-1] == '\n' )
         szGuess[strlen( szGuess )-1] = '\0';

is called a test for equality statement. The second statement shown above is an ordinary assignment statement. Note well the difference between these two statements! The accidental misuse of the == and = operators is one of the most common bugs in the C language.

Together these 2 statements replace the final character of the szGuess string with a NULL termination if that final character is a newline character. Since a NULL terminator signals the end of the string, this action will shorten the string. So these 2 statements shorten the szGuess string by removing the final character if that final character is a newline character.

These 2 statements massage the contents of szGuess into the form that we want to compare against the string in szPassword. This is necessary because the fgets( ) function includes the newline character in the string that it returns. So if the operator types:

c p p i d e

then the szGuess memory area will be initialized by fgets( ) with:

'c' | 'p' | 'p' | 'i' | 'd' | 'e' | '\n' | '\0'
0x63 | 0x70 | 0x70 | 0x69 | 0x64 | 0x65 | 0x0a | 0x00

Note that fgets( ) does return a NULL terminated string, it just happens to have a newline char immediately before the NULL termination.

We can deal with this behavior inherent to fgets( ) in two ways: we can either remove the '\n' at the end of szGuess or else we could include a '\n' character at the end of the password in szPassword. I chose to do the former.

The third change seen in the Password2 program is that the following line:

    for ( i=0; i < strlen( szPassword ); i++ )
has replaced:
    for ( i=0; i < 6; i++ )

We again see a use of the strlen( ) function which returns the number of characters in a string. This is different than the amount of memory that has been reserved for the string. Consider the following code:

   char  szGuess[100] = "cppide";
   printf( "strlen() returns %d\n", strlen( szGuess ) );

This statement reserves 100 bytes (characters) of memory yet the printf( ) statement will print the number 6 because that is how many characters currently reside in the memory area. Note that strlen( ) does not count the NULL termination (that's just how it was designed). The implementation of the strlen( ) function is easy: you just keep moving forward counting characters until you discover the NULL termination.

It is much better to employ strlen( ) in your program than hard-coding numbers such as 6. The reason is that someday you will change the password and forget that you have to look through the rest of your program and find all the other places (such as this 6) that are affected.

The fourth change seen in the Password2 program is that even if the first 6 characters of szGuess and szPassword agree, the two passwords are still not identical unless they have the same length. That is, we don't want the program to accept "cppidehaha" as a correct password (perhaps you didn't notice that the original Password program had this bug).

Compile, execute, and test the Password2 program.

Next open the Password3 project. You will see that we have simplified the code because the use of the strcmp( ) (string compare) function allows us to remove the for loop that compared individual characters. Instead we merely need the 1 call to strcmp( ), which returns the value 0 if the two strings are identical.

Compile, execute, and test the Password3 program.

Now let's have some fun with text manipulation. You might like to see how you can color the characters that you produce via calls to the printf( ) function. This merely requires that you call the SetConsoleTextAttribute( ) function prior to the call to printf( ). You can change the color as often as you like but you are limited to the 16 colors that you request via all possible combinations of the following arguments passed to SetConsoleTextAttribute( ):

   FOREGROUND_RED
   FOREGROUND_GREEN
   FOREGROUND_BLUE
   FOREGROUND_INTENSITY

You combine these flags using the bitwise OR operation. In 8051 assembly language we learned that the ORL instruction mnemonic causes the 8051 microprocessor to perform the bitwise OR operation. In the C and C++ languages you request the bitwise OR operation via the vertical bar symbol |. Hence, in order to produce olive colored text you would request:

  SetConsoleTextAttribute( hStdout, FOREGROUND_RED | FOREGROUND_GREEN );

while to produce yellow text you need to request:

  SetConsoleTextAttribute( hStdout,
                           FOREGROUND_RED
                         | FOREGROUND_GREEN
                         | FOREGROUND_INTENSITY);

Remember that the compiler ignores white space and hence I have chosen to format this last call to SetConsoleTextAttribute( ) such that it extends over 4 lines. The hStdout argument that you see is something called a handle. In this case it is the handle to "standard output", which is the output stream connected to the console window. You get this handle from an earlier call to GetStdHandle( ). All of this is demonstrated in my ColoredText example program so open, compile, and run that project at this time.

Now for an assignment. I have provided you some skeleton code in the CenteredText project folder. By "skeleton" I mean that this code is incomplete and needs to be "fleshed out". The intent is to write a program which will center whatever text the operator types. In order to accomplish this you need to know that the console window is 80 columns (characters) wide. With this knowledge, and knowledge of the strlen( ) function, you should be able to fix the code I have given you in the CenteredText project such that it will center a string of text of arbitrary length. You will see that the skeleton code uses a for loop to output 10 blank characters before it outputs the text typed by the operator. You need to change this section of the code so that it outputs a number of blank characters that has been computed based on the amount of text typed by the operator. If you get stuck you can find my solution in the Solutions folder. One thing to notice in my solution is that it correctly handles the situation where the operator types more than 80 characters.


( 27 )

Static Variables

It is time for us to learn another language keyword. But before I can explain this keyword I have to teach you a bit more terminology. The variable i that you see used in the following function is called a local variable:

void  PrintTenInts()
{
    UINT   i;

    for ( i=0; i < 10; i++ )
    {
        printf( "i = %u\n", i );
    }

    return;
}

The phrase local variable is supposed to remind you that this variable is only in scope within the PrintTenInts( ) function. Remember: a variable is in scope from its definition to the closing brace of whatever block it is defined in. So in the case of the i variable seen in the prior code snippet, this local variable is in scope throughout the body of the PrintTenInts( ) function.

Incidentally, the opposite of a local variable is a global variable which is in scope throughout a program. Excessive use of global variables has sunk many programming teams and hence rather than teaching you how to employ global variables I have an example program coming up that teaches you how to avoid using global variables.

You know that any variable must live somewhere in memory. And you know that when you choose to program in a high-level language you are entrusting the compiler to select the actual memory location. This memory location must be reserved for the variable throughout the variable's lifetime. In the case of the i variable seen in the prior code snippet, the variable comes into existence at the start of the PrintTenInts( ) function and goes out of existence at the end of the PrintTenInts( ) function.

So the compiler is reserving and then freeing a particular memory location every time the flow of execution enters and leaves this PrintTenInts( ) function. And all of this happens without any particular effort on your part. For this reason, local variables such as the i variable are also called automatic variables.

Imagine for a moment that you must write the compiler that will select the memory location for an automatic variable. Your first attempt at a solution might be to invent a counter that is incremented every time you need a new automatic variable. The counter's value is used for the address of the new automatic variable. This solution would work, but would also waste memory since once a particular memory address was employed it would never again be employed. This is wasteful because most local variables are only needed for a very short time.

As a second attempt you might decide to invent a free list which is a list of all memory addresses that are currently available. Every time you need a new automatic variable you grab the next address off the free list and use it for the memory address of the new variable. At that later time when the automatic variable is no longer needed you return its address to the free list.

But free lists are subject to a problem known as memory fragmentation which occurs when memory becomes fragmented which means that it becomes littered with lots of memory allocations that are so scattered there are no longer any large sections of free memory. This can cause the computer to crash since each memory allocation must be made in a single piece. Even in a situation where there is lots of available memory in many small pieces, the computer may not be able to satisfy the need for a new large memory allocation.

Fortunately some bright minds have long before figured out a clever solution to this problem. The solution is to employ a stack for all automatic variables. A stack is simply an area of memory that you agree to access only via an associated top of stack pointer. Each time you add another item to a stack, the stack grows, Each time you delete an item from the stack, the stack shrinks.

Each time your program enters a new function, the compiler has prepared a stack frame for that function which holds all of the local variables employed in that function. In fact, the stack frame also holds the passed parameters to the function and the return value from the function. When the flow of execution leaves the function, the associated stack frame is torn down, thus freeing all of the memory. Since all of the local variables are allocated in memory simultaneously and removed from memory simultaneously, there can be no memory fragmentation.

The automatic nature of automatic variables is usually exactly what you want for your local variables. But sometimes it's not. Occasionally it is useful to have a local variable retain its value between invocations of a function. An automatic variable cannot retain any prior value because the stack frame is entirely rebuilt every time the flow of execution enters the function.

So if you want a local variable to retain its value then it cannot be an automatic variable. The C language provides the static keyword for just this situation. The term static is intended to suggest that this variable has more permanence than an automatic variable. The compiler does not locate static variables on the stack, but instead locates them in a different section of memory that remains intact throughout the program's lifetime.

To give you an example of the use of static variables I have provided the StaticVariables example program. At this time open this project and read through the code. Then compile and execute the StaticVariables program. I have also included a bug in this program just to help cement the difference between automatic and static variables (which are the only 2 flavors of local variables). When I ran this example program on my computer I got the results:

in UninitializedVariable(), iUninit = 5505024
in AutomaticVariable(), iAuto = 0
in StaticVariable(), iStatic = 0

in UninitializedVariable(), iUninit = 1
in AutomaticVariable(), iAuto = 0
in StaticVariable(), iStatic = 1

in UninitializedVariable(), iUninit = 1
in AutomaticVariable(), iAuto = 0
in StaticVariable(), iStatic = 2

in UninitializedVariable(), iUninit = 1
in AutomaticVariable(), iAuto = 0
in StaticVariable(), iStatic = 3

You can see that the automatic variable iAuto is always re-initialized to 0 whenever execution enters the AutomaticVariable( ) function. Hence the "iAuto++" statement seen in this function is pointless.

In contrast, the initialization of the iStatic variable occurs only the first time execution enters the StaticVariable( ) function. Therefore the "iStatic++" statement seen in this function does cause the iStatic variable's value to constantly increase.

The UninitializedVariable( ) function has a bug in that it employs a variable that has never been initialized. The value 5505024 is an example of garbage. During later calls into the UninitializedVariable( ) function the iUninit variable has the value of 1. This may look nicer but is actually every bit as much garbage as the earlier 5505024 value. The value of 1 just happens to be left in the particular memory location on the stack used for the iUninit variable, but that is happenstance and nothing that can be depended upon.

Incidentally, the Show Call Stack button seen on CPPIDE's toolbar while you are debugging a program will print the complete collection of stack frames for that moment in the execution of the program.


( 28 )

Command Line Arguments

We are going to look at one more console-mode program before moving on to true Windows programs that offer a graphical user interface (GUI). The usual way you initiate a program in the Windows operating system is by double-clicking its filename (.EXE name) when that filename is shown in a Windows Explorer window. This technique works for both console-mode programs and Windows programs. But it does not offer a way for you to pass parameters (arguments) to the program that is being initiated.

Both console-mode programs and Windows programs can accept parameters. These are called command-line arguments because you need to initiate your program from the command line in order to get a chance to specify these parameters. In a Windows computer, the command line is another name for the DOS prompt that you observe in a DOS box. You open a DOS box window by selecting "Start/Programs/MS-DOS Prompt" from the Start Menu (in Windows NT/2000/XP this is renamed "Start/Programs/Command Prompt").

To initiate a program from the command-line you just type its filename. You can add the .EXE suffix or skip it when you type the filename. To pass parameters to this program you just list those arguments after the filename, separated by spaces. For example, if the C:\Test directory held an executable named MyProgram.exe, then you could initiate this program by opening a DOS box window and then typing:

  cd \Test
  MyProgram

The cd command is the "change directory" command which gets you located in the directory (folder) that holds the executable you want to initiate.

To pass the 2 parameters "Hi" and 2.4 to this same program you would instead type:

  MyProgram Hi 2.4

Note that 2.4 is not passed to the program as a number, but rather as a string; that is, as the three characters '2', '.', and '4'.

In order for your program to actually receive these parameters you must change the function declaration for the main( ) function from what I have been showing:

  int   main()

to:

  int   main( int argc, char * argv[] )

Remember that the operating system initiates your program by calling your main( ) function. If you prototype your main( ) function as shown above then you will receive the 2 passed parameters argc and argv.

argc is a simple integer equal to the number of arguments specified on the command line, where the .EXE filename counts as the first argument. Hence, if you initiate a program by typing:

  MyProgram Hi 2.4

then argc will be equal to 3.

argv is an array of strings. Since it is an array, you access the individual entries using the square bracket notation. For example, the .EXE filename will be located in argv[0]. Remember that the index for all arrays begins with the value of 0 not 1.

At this time open and compile the CommandParams example program. This program does nothing other than use printf( ) to display all of the strings received in argv. In order to print the value of a string using the printf( ) function you need to employ the %s format specifier.

When you initiate a program via the "Execute" button seen on CPPIDE's toolbar, you don't get an opportunity to specify any command line arguments. However CPPIDE does offer this ability. Before initiating a program that requires command line arguments, open the Options dialog (View/Options) and then enter the command line arguments in the edit control labeled "command line arguments". Note that these command line arguments will continue to be sent to whatever programs you initiate with CPPIDE until you remove them.

Practice sending some command line arguments to the CommandParams.exe executable once you have compile it. Note that after changing the command line arguments sent to CommandParams.exe, you don't need to recompile, you merely need to re-execute the program.

You will observe that argv[0] holds not only the .EXE filename, but also its complete path. Assuming you accepted the default installation options for CPPIDE, then this complete path to the CommandParams.exe file will be:

C:\Computer Science Lab\CppIde\CommandParams\Debug\CommandParams.exe

You can verify this fact by using Windows Explorer to inspect the contents of this folder. The executable is placed in a "Debug" folder below the CommandParams project folder because, by default, CPPIDE performs a debug build. A debug build is the type of compile that retains the information needed by the debugger. If you want to create a smaller .EXE file and don't mind not being able to debug it, then you can use the Options dialog (View/Options) to request the alternative, which is called a release build.


( 29 )

Language Review

or "The Calm Before The Storm"

We are about to head into true Windows programs, which involve a huge infrastructure known as Microsoft's WIN32 API (application programming interface). So this is an appropriate time to make sure you are comfortable with the basics of the C and C++ languages.

You have heard me use the words declaration and definition a number of times but you may still be fuzzy on the distinction between them. When you place statements such as the following in a .H header file we say that you are declaring a class:

class  cExampleClass
{
public:
    cExampleClass( double dValue )   // the constructor
    {
        m_dValue = dValue;
    }
    ~cExampleClass() { };            // the destructor

    double  GetDoubleValue()
    {
        returns m_dValue;
    }
private:
    double   m_dValue;         // gets initialized in the constructor
};

These statements provide a blueprint to the compiler. The compiler demands to see your blueprint before it allows you to create any objects based on those plans. If you should later request an object of this type then you are instantiating an instance of this class. We say that you are defining an object, which in this case is an instance of the cExampleClass class. Any time after the compiler has seen your declaration for the class (probably thanks to a #include "cExampleClass.h" statement) it will allow you to define an instance via a statement such as:

   cExampleClass  MyClassInstance;

The difference between a declaration and a definition is that the former does not cause any consumption of program memory space while the latter does. When you provide a blueprint (declaration) to the compiler it remembers your plans but none of this knowledge goes into your actual executable (.EXE) file unless somewhere in that program you define an instance of that type. Once the compiler has seen your blueprint, it will allow you to create any number of instances of that class. Effectively, you have added a new keyword to the language.

Even though this page is labeled a review, I slipped in something new. If you look again at the constructor for the cExampleClass class and at the declaration for the cExampleClass::GetDoubleValue( ) function, you will see that I have placed the function body in the declaration. That is, I have placed the body in the .H header file. Previously, we have only seen these function bodies placed in .CPP implementation files. Either method is legal, but the distinction is more significant than just a matter of typing style.

When you provide the function implementation (body) in the declaration you are telling the compiler to inline the function. This means that the code is not really a function at all, but instead the statements between the braces are copied into each location in the program where the function call would otherwise take place. This avoids the overhead of the function call and return. When we were studying the 8051 microprocessor we learned about the assembly language statements that accomplish a function call and a function return. We also learned that the parameters passed to functions are provided on the stack and consequently any function call involves some stack preparation and any function return involves some stack clean-up. This overhead is avoided if you inline the code rather than employing a function call.

In-lining speeds up your program but also makes your program larger. Instead of jumping to a section of code multiple times, you are making multiple copies of that section of code. This is an age old tradeoff known as the speed/size tradeoff. There are quite a few things the compiler can do to reduce the execution time of your program, as long as it is allowed to make the program bigger (that is, consume more program and data memory). Alternatively, you can instruct the compiler to minimize the memory footprint of the program, and this will usually cause the program to run slower. At various times, one of these concerns trumps the other. If you have plenty of memory available then you would, of course, go for maximum speed. But in many situations (such as a hand-held battery-operated device) you may not have plenty of memory.

So when should you inline? The general rule of thumb is that if your constructor, destructor, or class member function is only a few statements long, then it makes sense to inline it.

This is a good time for me to return to the concepts of state and behavior. To do this, I want to first introduce the sizeof keyword, which returns the size of a variable or data type (including a user-defined class) in bytes. For example, if you ran the following code snippet:

   int    i  = 2;
   double d  = -1.3;
   char   ch = 'a';

   printf( "sizeof( i ) = %u\n", sizeof( i ) );
   printf( "sizeof( d ) = %u\n", sizeof( d ) );
   printf( "sizeof( ch ) = %u\n", sizeof( ch ) );

you would learn that every int variable is 4 bytes long, every double variable is 8 bytes long, and every char variable is 1 byte long. Note that the printf( ) statements seen above use the %u format specifier in order to print the unsigned int value returned by sizeof( ). It is also legal to say something like:

   printf( "sizeof( int ) = %u\n", sizeof( int ) );
   printf( "sizeof( cExampleClass ) = %u\n", sizeof( cExampleClass ) );

where we are using language keywords rather than variable instance names.

So what is the size of an instance of the cExampleClass class that we defined above? Well, it turns out that "sizeof( cExampleClass )" returns 8. Hence the size of any instance of the cExampleClass class is 8 bytes. The size of an object (an instance of a class) will always be the sum of the sizes of all the data members of the class. In our particular cExampleClass class, there is only 1 data member, m_dValue, which is a double and hence 8 bytes large.

The reason that every cExampleClass instance consumes 8 bytes is because every cExampleClass instance has its own m_dValue data member. This is how every cExampleClass instance is able to maintain a unique state. The state of an object is held in the values of its data members. One cExampleClass instance might have m_dValue = -1.3 while another cExampleClass instance could have m_dValue = 77.2.

Based upon these experiments with the sizeof keyword, you might conclude that the class member functions do not consume any memory. This is not quite the right conclusion. The class member functions are just like any other functions in that they reside somewhere in program memory. However, every instance of the cExampleClass class shares the same member functions. This is how every instance of a class can display the same behavior. The behavior must be identical because all instances are employing the same code. And because this code is not replicated for each instance, the sizeof keyword does not charge this memory consumption against any one instance.

As additional review, I would like to expand upon my previous comments about assignment statements. I have said that an assignment statement requires a variable name on the left-hand side of the equals sign and an expression on the right-hand side. Hence a legal assignment might look like:

    int   i1 = 2;
    int   i2;

    i2 = 3 + i1;

Only the third statement shown above is an assignment statement. The first two statements are variable definitions. The first statement looks like an assignment statement is but is actually a variable definition which includes an initialization. This statement would look less like an assignment statement if I used the alternative notation:

    int  i1( 2 );

The expression "3 + i1" consists of an addition operation on two integers. Hence it is pretty clear that the result will be an integer, and this result is a legal value to assign to a variable whose data type is int.

But things get more complex when the expression consists of members having heterogeneous (differing) data types. As an example, what should the compiler make of the following statements:

    double  d = -1.3;
    int     i;

    i = d;

The expression on the right-hand side of the equals sign has the data type of double. Is it legal to assign a double to an int? One of the things that I like about C and C++ is that the compiler will warn me if I am doing something potentially stupid. In this case it generates the warning:

   warning: assignment to 'int' from 'double'

This warning serves to remind me that not all legal doubles can be represented as ints, and hence this assignment might not accomplish what I intend. However, suppose I happen to know that the floating point value at a certain point in my program is 3.0 ? This is a perfectly fine value to place into an int and hence the compiler ought to let me do it.

Another thing I like about C and C++ is that the compiler offers me a way to insist that a suspicious action be allowed to take place. Way back at the beginning of this tutorial when I listed some jokes about various programming languages, I mentioned that some computer programming languages refuse to allow you to perform suspicious activities. It must have seemed like a good idea to the designers of these languages to refuse to participate in activities that could generate bogus results. But the history of the computer programming industry has shown that it is better to give the designer (the programmer) the leeway to accomplish whatever it is he wants to accomplish. If he writes a program that generates bogus results, then the guilt is upon his head. But a language that straight-jackets the designer is a language that won't get used in the real world.

One of the ways that the language allows me to insist upon my peculiar manipulations is the cast. When you cast a value you are forcing the compiler to treat the value as a different data type. For example, you can cast a double to an int. You do this via the following notation:

      double d = -1.3;
      int    i;

      i = (int)d;

The portion "(int)" is the cast. This was the original notation used in the C language and the C++ language has added an alternative notation which looks like:

  i = static_cast<int>(d);

When the compiler sees that you have explicitly used a cast, it will not issue the warning about assigning a double to an int. The presence of the cast is your authorization that you know what you are doing. You should try to minimize your use of casts, since they are only necessary for suspicious activity. Some books refer to the use of casts as coercion; i.e., you are coercing a double into an int.

The final concept I want to discuss is promotion. The C and C++ compilers are willing to automatically perform certain manipulations that don't have any chance of loss of numerical accuracy. For example, you will not get a warning if you use an int to initialize a double, since any int value can be represented without loss of accuracy as a double. You don't need to employ a cast in the following assignment statement:

   int     i = -22;
   double  d;

   d = i;

because the compiler automatically promotes the data type of the expression on the right-hand side of the equals sign from int to double in order to make it compatible with the data type of the left-hand side. Whenever you have a binary operator, the compiler will automatically promote the data type of one of the operands to match that of the other operand. The "lower" operand is promoted to match the "higher" operand. For example, in the expression "11 + 2.3" we see a binary + operator and two operands, one of which is an int and the other is a double. The compiler will automatically promote the int to a double and hence the data type of the entire expression will be double.

The general rules for promotion are:

  • If either operand is a double, the compiler will convert the other operand to a double.
  • Otherwise, if either operand is a float, the compiler will convert the other operand to a float.
  • Otherwise, if either operand is an int, the compiler will convert the other operand to an int.


( 30 )

The New Project Wizard

It is entirely possible that some of you would rather not proceed into the complexity of Windows programs just yet. I plan to continue teaching you aspects of the C and C++ languages as we learn about Windows programs, but if you would rather continue practicing with console-mode programs then I can suggest some good alternative curricula.

You can find numerous C++ programming books in your local library or bookstore. Most of these books will demonstrate console-mode programs exclusively (the early introduction of Windows programs is one of the unique aspects of my curriculum). Many recent books come with a CD-ROM to make it easier for you to experiment with the example programs shown in that book. Those books that omit the CD-ROM usually offer a web site where you can download the source code.

All it takes to use CPPIDE to compile and execute these programs or ANY original program you wish to write is for you to create a project (.PRJ) file and a makefile (.MAK) for each program and place these 2 items in a new folder with your source code (.C or .CPP) and header (.H) files. And you don't even have to manually prepare the .PRJ and .MAK files since CPPIDE offers a wizard (automation tool) that performs these steps for you.

Let's see how this works. Let's initially imagine that you want to write a brand new console-mode program of your own creation. To get started you want to either select "Project/New Project..." from CPPIDE's menu or else click on the CPPIDE toolbar button that looks like a blank sheet of paper. This will initiate CPPIDE's New Project Wizard.

The New Project Wizard, like all wizards, is nothing but a sequence of dialog boxes that leads you through your choice of options. There are 5 steps in the New Project Wizard and hence 5 dialog boxes, but only 3 of these 5 are relevant to console-mode programs. The first dialog box is shown below:

You use this dialog to name your project, declare whether it will be a console-mode or Windows program, and whether -- if it is a Windows program -- it will employ my CSL class framework. And finally, this dialog allows you to select either the C or C++ language. Only after you enter a legal project name (which will also be used for the name of your executable file) will the "Next" pushbutton on the dialog box become enabled, thereby allowing you to proceed to the next step.

You generally will not need to type in the edit control labeled "Project Location" because the wizard will automatically construct a location based upon your project name. The project location is just the name of the folder that holds the project (.PRJ) file and the makefile (.MAK file) and whatever .C/.CPP and .H source files the project employs.

Whenever you ask for a console-mode program the next step will be labeled Step #4 simply because Step #2 and Step #3 are not relevant to console-mode programs. Step #4 of the New Project Wizard is shown below:

In Step #4 you list the names of all of your .C/.CPP files in the left hand column and the names of all of your .H files in the right hand column (note that console-mode programs do not employ .RC files). For the simplest console-mode programs you won't need any header (.H) files and can put all of your code in a single .C or .CPP file (depending upon whether you have chosen the C or C++ language). If you name this .C/.CPP file "main.cpp" or "Main.cpp" then the New Project Wizard will be able to infer your intentions and will place some useful boilerplate code into this file for the main( ) function which is where all console-mode programs must begin. Of course, if you don't like any of this boilerplate you are free to change it.

Things to remember when selecting file names:

  • Do not employ space (blank) characters in your file names,
  • Employ lower case file name suffixes such as .cpp rather than .CPP,
  • Specify .c file name suffixes if you requested the C language and .cpp file name suffixes if you requested the C++ language.

When you click on the "Next" pushbutton you will proceed to Step #5 of the New Project Wizard which is shown below:

Only advanced programs have any need of the options offered in this fifth step. If you are writing programs that need these options then you may need to learn the language used in makefiles. I offer a short introduction to the language of makefiles in an appendix of this on-line help document.

Based upon the information you provide, the New Project Wizard will then construct a project (.PRJ) file and a makefile (.MAK) as well as the .CPP and .H files that you requested. Of course, the .CPP and .H files will be mostly empty, but you wanted something to do, didn't you?

Now let's consider the situation where you want to compile a console-mode program you found in a book or on the Internet. In this situation you employ the New Project Wizard just as I have described except you specify the exact .CPP and .H file names that you observe in the program. Then AFTER the New Project Wizard finishes constructing the project folder you use Windows Explorer (Start/Programs/Windows Explorer) to overwrite the empty .CPP and .H files with the corresponding files from the book. The advantage of this procedure is that the wizard constructs the .PRJ and .MAK files for you using the correct file names.

In the example programs that I distribute with CPPIDE I have been hiding the .PRJ and .MAK files. However when you use the New Project Wizard you will discover that the .PRJ and .MAK files appear in the Files listbox on the left-hand pane of CPPIDE. This means you can inspect these files and even edit them from within CPPIDE. You won't need to interact with the .PRJ and .MAK file unless you need to add or delete some file from your project. The structure of a .PRJ file is very obvious so you don't need to be scared to edit it. After making any change to your .PRJ file you merely need to re-open that project. The makefile (.MAK) is a bit scarier but if you know what you are doing you can edit this file. In this case there is no need to re-open the project as your makefile changes will become active the next time you compile.

Be aware that if you need to add or delete a source file from a CPPIDE project you will need to edit both the .PRJ and the .MAK file.

Even though it only takes a few seconds to answer the New Project Wizard's questions, this can still get tedious if you want to construct a CPPIDE project for every example program in a large book. Therefore I have done a lot of this work in advance as a favor.

I have already prepared all the CPPIDE infrastructure necessary for all of the example programs illustrated in the following three books, which happen to be some of the best selling C++ programming books:

  • C++ How To Program by Deitel and Deitel
  • Thinking In C++ Volume 1 by Bruce Eckel
  • Thinking In C++ Volume 2 by Bruce Eckel

There are a total of 754 short console-mode programs illustrated in these three books. All of this code can be obtained without cost from the author's web sites. And I provide the .PRJ and .MAK files needed to compile these programs from within CPPIDE. You can read about the details of the Deitel and Deitel book from the following world-wide-web page:

http://www.computersciencelab.com/Deitel.htm

or from the following file on the Computer Science Lab CD-ROM:

D:\Deitel and Deitel\Deitel.htm

You can read about the details of the Eckel books from the following world-wide-web page:

http://www.computersciencelab.com/Eckel.htm

or from the following file on the Computer Science Lab CD-ROM:

D:\Eckel Thinking In C++\Eckel.htm

Note that I am assuming that drive letter D: corresponds to your CD-ROM drive.

Best of all, Mr. Eckel has allowed me to place not only his source code but also the full text to his two C++ books on my Computer Science Lab CD-ROM. It is not that I am so special: it just happens that Mr. Eckel makes electronic versions of his books, and their source code, freely available even though they are published works that you can find being sold in bookstores. Read the Eckel.htm document mentioned above to get the full explanation.

A final free resource that I can mention is the series of tutorials on the C language available on the "How Stuff Works" web site at the following URL:

http://computer.howstuffworks.com/c.htm


( 31 )

Windows Programs

The C and C++ languages were designed by engineers whose mental picture of a running computer program was what we now call a console-mode program. One advantage of a console-mode program is that since it employs nothing more sophisticated than a textual user interface, it is easier to port (move) these programs to multiple operating systems such as MS-DOS, MacOS X, UNIX, and others.

The designers of the C and C++ languages had perhaps never even seen the type of graphical user interface (GUI) pioneered at Xerox PARC (Palo Alto Research Center) and later adopted by Apple and then Microsoft. It is a testament to the flexibility of these languages that they could continue to be employed even after such a fundamental paradigm shift. But the adoption of a GUI did require the addition of a lot of new infrastructure, both in the operating system and in the programs that run on the operating system. In fact, so many changes are required that when you first see a Windows program (meaning a GUI program) it may take you a minute to convince yourself that this program really is written in a language you know. And unfortunately, this new infrastructure has not been standardized so once you have added the code necessary for the Microsoft GUI you will no longer be able to run your program on an Apple or LINUX computer.

The standard reference book on Windows programming is Charles Petzold's Programming Windows Fifth Edition. This book is aimed at experienced C language programmers and yet needs 1450 pages to explain the additional infrastructure necessary to accomplish a Windows GUI. So don't expect to become a Windows programming expert overnight.

All the code in the Petzold book is in the C language. Petzold assumes you already know the language and just need to learn how to employ it to create Windows programs. Petzold's example programs are all pretty short, as they are each intended to demonstrate only one aspect of the WIN32 API (application programming interface), which is the infrastructure you must employ to create a Windows program.

One of the nice features of the CPPIDE integrated development environment is that it can compile, execute, and debug the 145 example programs shown in Petzold's book. To accomplish this you merely need to execute my PetzoldConvert.exe program (which you can find on my CD-ROM) after you have installed the CD-ROM that came with Petzold's book. You can find complete instructions for this procedure here.

My first attempts at creating Windows programs used the C language in the style of Petzold. Things were fine while the program remained small. But when the code necessary to accomplish the Windows GUI had grown to many thousands of lines I began having trouble remembering where things were. This was when I really began to appreciate the innovations in the C++ language.

So I started all over using the C++ language and discovered that the encapsulation properties of classes allowed me to segregate the code having to do with the toolbar into a cToolBar class, and the code having to do with the status bar in a cStatusBar class, etc. At any given time I was either improving the toolbar or the status bar and only had to keep that smaller amount of code in my aching head. Today, Microsoft employs C++ for all their killer apps (applications) such as Word, Excel, Power Point, etc.

There are a lot of books and web pages that offer instruction in the C and C++ languages. You should have no trouble augmenting the instruction that I provide in these help pages. But you can be an expert in C and C++ and still not have the faintest idea how to create a Windows program. We are finally ready to dive into this additional infrastructure.

In order to write a program which will offer a graphical user interface on a Windows computer, you must adopt a very specific type of programming style, and your program must partner with the operating system to accomplish all the graphical features such as the menu, toolbar, dialog boxes, etc.

Let's start with the terminology of Windows programs. The first point of confusion is the difference between Windows and windows (thanks Microsoft). When I type Windows with a capital W I am referring to the operating system and when I type windows with a lower-case w I am referring to a rectangular section of the video display. Typically you have multiple windows open simultaneously when working with the Windows operating system.

Let's now dissect the window of a typical Windows program. For purposes of illustration, we will consider the window produced by this very CPPIDE program.

What you see in the last photo is referred to as a top-level window, meaning that there is no larger portion of the video display screen dedicated to this particular program. But there are smaller portions. This is because the top-level window is composed of numerous smaller child windows which employ the screen real-estate of the top-level window to display their contents.

The most fundamental split in the top-level window occurs between the client window portion and the non-client window portion. These areas are marked in the following photo:

The non-client area is the portion that Microsoft draws for you. Your program specifies the items you want in this area of your window, but your code is not responsible for drawing in this area. The client area is the portion that your program does draw. These names assume that you (or your program if you don't like to anthropomorphize) are a client of Microsoft's Windows operating system. You might be surprised to learn that the toolbar and status bar are within your portion of the window. Conceptually, they should be part of the non-client area since it is Microsoft's code rather than yours that draws these items. However, Microsoft had not yet invented the toolbar and the status bar when the concept of client area was formalized.

I have purposefully drawn the client area just a little bit narrower than the non-client area above it. This is because Microsoft is responsible for a border all the way around the window. This border is only a few pixels wide. The operator can click and drag with the mouse when the mouse cursor is over this border in order to stretch the dimensions of the top-level window. When this happens, all the child windows that comprise the top-level window must adjust their sizes proportionately.

Like so many other things in Windows programs, this resizing operation requires close cooperation between the code you write and the code that Microsoft has already put into their Windows operating system. Microsoft's code takes care of detecting when the mouse cursor is over the border and automatically changes the mouse cursor to a new image that signifies, "You can now adjust the window dimensions if you so desire." Should the operator click and drag on the border then Microsoft's code will draw a ghost outline of the new window border. When the operator releases the mouse button Microsoft's code informs your code that it must now redraw the entire client area. Your code must respond and accomplish this.

I identify the subdivisions of the non-client area in the photo below:

The first row of the non-client area is known as the title bar or the caption bar. You are allowed to request that Microsoft draw a particular icon at the far left-hand edge of the title bar. You can also request that Microsoft draw the standard minimize, maximize, and close buttons at the far right-hand edge of the title bar. You also get to specify the text that will be displayed on the title bar. You do NOT get to specify the color of the title bar because Microsoft selects this to match up with all the other windows displayed on the desktop, which is the entire video display screen. The owner of the computer (as opposed to the programmer) can select this title bar color by requesting a particular desktop theme. Microsoft actually uses 2 colors for the title bar. One color (usually blue) indicates that the window is currently active and hence has keyboard focus. Another color (usually dark gray) indicates that the window is inactive. The operator can click and drag on the title (caption) bar in order to reposition the top-level window without changing its size. Obviously, the child windows then have to be redrawn in the new location of their parent.

The next row of the non-client area is known as the menu bar. You get to specify the text seen in this area, as well as the text seen when the operator opens each column of the menu. Microsoft takes care of drawing all this text, but you must provide the code that responds when each menu item is selected.

Moving down into the client area, we come to the toolbar:

You get to specify the buttons that you want on your toolbar. Microsoft provides you some standard images such as the manila folder and the floppy disc, and you can provide additional images by using any program (such as Microsoft Paint) that can construct 16 color images that are 16 pixels wide and 15 pixels tall. The 2 toolbar buttons at the extreme right are currently disabled. This means the program is currently refusing to respond to them, and so they are drawn grayed out to advertise to the operator the fact that they are inactive. In between some of the toolbar buttons are vertical lines called separators. Again, you request these and Microsoft draws them.

You are not limited to just buttons on your toolbar. On this particular toolbar I have placed what Microsoft calls a single-line EDIT control. Obviously you have to tell Microsoft the order of the toolbar buttons and how much toolbar area should be dedicated to the non-button controls such as this EDIT control (which I use to provide a find function).

At the very bottom of the window is the status bar:

You are not required to have a status bar (or toolbar) but it is handy for showing the current state of the program since it is one of the few places you can place text. This may sound confusing since the CPPIDE top-level window displays all sorts of text. But open the Microsoft Paint program (Start/Programs/Accessories/Paint) and look around on this window for places that Microsoft can display a status message.

One nice feature of Windows programs is that the status bar is bipolar. When the menu is not open (that is, when the operator is not browsing through the various levels of the menu) the status bar can display the normal status of your program. But when the menu is open the status bar can switch its nature and instead display a single line of explanation for each menu choice. If you have never noticed this, try it now on the CPPIDE or Microsoft Paint programs.

The far right-hand portion of the status bar has a diagonally hatched area which is supposed to suggest to the operator that he can click and drag there to resize the window. But this is kind of silly since the operator can actually click and drag at any point on the border.

So far, I have described the window areas that are pretty easy to support, since Microsoft does most of the work. The remaining portions of the client area require far more code since your program must accomplish the painting of these areas.

For my CPPIDE application, I found it convenient to subdivide the remaining client area into 3 child windows, as identified below:

You can see that I got lucky in that I could employ code written by others for these areas of my CPPIDE window. But I still had to provide thousands of lines of code to integrate these separate child windows into a cohesive application.

Microsoft provides a lot of controls that you can employ in the client area of your windows. A control is just a window that presents a user interface so that the human operator can interact with it. Just like you use a physical (real world) button or knob to control something, you use these window areas to control the activities of the computer program. I have a habit of capitalizing the names of the standard controls such as EDIT control, LISTBOX control, COMBOBOX control, etc. that Microsoft provides. An example of a control that I had to create for myself would be the tape deck pushbuttons we used with the peak detector example programs:

Shown below is a visual compendium of the standard Microsoft controls. Be aware that an UP/DOWN control is also known as a SPINNER control and a SLIDER control is also known as a TRACKBAR control.

You can use these controls as child windows on your top-level window or for controls on a dialog box. A dialog box is simply a window that lacks a menu and lacks a toolbar and is used by the operator for only a short period of time for the purpose of choosing some settings for the program. All of my applications offer a View/Options menu choice that leads to a dialog box. Microsoft also provides pre-built dialog boxes for some common activities, such as the Open common dialog box used to select a file on the hard drive.

Obviously it is easier to employ a Microsoft provided control than it is to construct your own control. But you will still need to write lots of code just to interact with a standard control. For example, the LISTBOX control is a medium complexity control and yet Microsoft's WIN32 API offers 51 different functions (more properly called messages) just for interacting with a LISTBOX control. And there are 16 manifest constants used to specify the visual style of the LISTBOX control.

Whenever you invent a new, original control you must provide the code that paints this control on the screen. The easiest solution here is to employ a bitmap for the control's image. A bitmap is just a .BMP file such as you create using Microsoft Paint. But bitmaps have fixed dimensions which means your control will not be able to change size as the top-level window is stretched by the operator. Remember, you can't know at compile-time the size that your top-level window will be at run-time because that is entirely up to the operator.

If you want your control to adjust its size to always fit the available screen real-estate, then you will have to construct it at run-time using a sequence of very primitive painting operations. The WIN32 API offers a LineTo( ) function which can draw a straight line of arbitrary color between any two points on the screen. The WIN32 API also offers a Rectangle( ) function which can paint a solid rectangle of arbitrary color and size at a location that you specify.

Whenever you are employing these (and the many other) painting functions, the Microsoft operating system clips (limits) your drawing at the borders of the current window. Hence even if your program has a bug that causes it to attempt to paint beyond your window, the pixels that are supposed to appear on your top-level window are prevented from bleeding out or smudging something else on the desktop.

The WIN32 API holds an immense amount of infrastructure that you can employ in your programs. You can convince CPPIDE to open a window holding Microsoft's documentation for these functions by selecting the "Help/WIN32 API" menu choice. This window looks a bit different from my own on-line documentation (what you are reading now) because it employs WinHelp which was Microsoft's prior on-line Help methodology. WinHelp has been supplanted by HTMLHelp which employs the same HTML mark-up language used for world-wide-web pages. Another place you can find documentation on the WIN32 API is at the Microsoft web site. They are constantly changing their URLs but you can try clicking here.


Events

( 32 )

Event Driven Programs

On the previous page I described how a Windows program requires cooperation and linkage between code written by Microsoft and code that you provide. As an example, your program can request that Microsoft draw the usual minimize, maximize, and close buttons ( [image]) at the right-hand edge of the title bar. If that is all your program does then these buttons will be visible but non-functional. Whenever the operator clicks on one of these buttons the Microsoft operating system will inform your program but take no other action.

The way Microsoft attempts to alert your program is by sending it a message. Windows is often called a message driven operating system because it is structured such that messages are employed for most activities. When the operator uses the mouse to select an item from the menu, Microsoft's code sends your program a message. When the operator uses the mouse to drag the window to a new location or size, Microsoft's code sends your program a message. Microsoft even allows you to define your own messages and send them from one of your child windows to another.

So what is a message? I have not been hiding some key aspect of the C and C++ languages that allows these mysterious messages to travel between sections of code. Therefore, you should be able to guess that this business of issuing messages must be accomplished with function calls, since in the C and C++ languages all code must reside in functions. And that is correct. Microsoft could have invented a unique function for each unique message. Perhaps that was even considered. But there are thousands of unique messages and hence that would have been impractical. So Microsoft went to the other extreme and decided that all messages intended for a particular window would be delivered to a single function. This function is called the WndProc( ) function which is pronounced "wind prock".

So we see that the Windows operating system sends a standardized sequence of messages regarding the movement of the mouse and the keystrokes typed on the keyboard to the windows (rectangular screen areas) on the display. But because each of these windows can have a unique WndProc( ) function, they can each respond differently to this stream of messages from the operating system. This is how Excel can act differently than Word and PowerPoint, even though they are all being fed the same messages.

When Microsoft provides a standard control such as an EDIT control or a LISTBOX control, they write the WndProc( ) function. When you create an original window you must write the WndProc( ).

Now if one function is to receive thousands of different messages, then you should also be able to guess that these messages are nothing more than different values for a passed parameter sent to the receiving function. And that is correct. Microsoft chose to use an unsigned 32 bit integer to identify each message. Most of these messages are defined in the WINUSER.H header file. I list and explain the most important of these messages in the table below:

msg name | msg purpose
WM_CREATE | This message is usually the first your program must respond to. It indicates your window is going to be drawn on the screen soon so you should now accomplish all of your initialization.
WM_PAINT | Microsoft sends you this message every time you need to repaint your portion of the screen. This might be because your window is just now opening or because some other window that had previously obscured your portion of the screen has now been moved aside.
WM_SIZE | Receipt of this message indicates that it is time for your window to change to a new size, probably because your parent window is changing size.
WM_CHAR | The operator has just typed a particular character on the keyboard.
WM_LBUTTONDOWN | The operator has just depressed the left mouse button.
WM_MOUSEMOVE | The operator has moved the mouse cursor to a new location.
WM_VSCROLL | The operator has just moved the vertical scroll bar.
WM_PASTE | The operator is attempting to paste something from the clipboard (not necessarily text) into your program. Your program gets to decide if it can accept the current contents of the clipboard.
WM_SETFOCUS | The Windows operating system is now giving your program the keyboard focus meaning that it is the only window that will be receiving notice of the keystrokes typed by the operator.
WM_KILLFOCUS | Your window is now losing keyboard focus.
WM_TIMER | This message is received by those programs that request a periodic interrupt. You can specify an arbitrary time delay between these messages.
WM_DESTROY | Time to go bye-bye.

As you can see from this short list, most Windows messages are accompanied by other info. For example, the WM_SIZE message is accompanied by the new dimensions for the window. The WM_CHAR message is accompanied by the keystroke character that was just typed. The WM_MOUSEMOVE message is accompanied by the new coordinates of the mouse cursor.

Microsoft provides a mechanism for arbitrary data to accompany the Windows messages by including 2 additional passed parameters to the WndProc( ) function. These passed parameters are known as the WPARAM and the LPARAM and they are both 32 bits wide. But what they are depends upon the particular message. For example, when the message is WM_SIZE the new width for the window is packed into the least significant 16 bits of the 32 bit LPARAM and the new height for the window is packed into the most significant 16 bits of LPARAM. This type of conservation of memory was very important in the early days of the Windows operating system when the computers it had to run on were little more than toys. When the message is WM_MOUSEMOVE the LPARAM is used to hold the new X and Y coordinates for the mouse cursor.

So you have to write the WndProc( ) function that will receive these messages. In fact, you have to write a different WndProc( ) for every different type of window you desire. In my CPPIDE application, the client area is divided into the toolbar, the status bar, the LISTBOX control, the Scintilla text editor control, and the Microsoft multi-line EDIT control. Because there are 5 child windows, there must be 5 WndProc( ) functions. As the human operator moves the mouse among these 5 child windows, the Windows operating system sends a continual stream of WM_MOUSEMOVE messages to whatever window currently hosts the mouse cursor. When you write a WndProc( ) you get to choose how to respond to each message. Most windows ignore the WM_MOUSEMOVE message unless the operator is also clicking with the left mouse button.

A typical WndProc( ) function is shown below. The first passed parameter has the data type HWND ("window handle") which is just the numeric identifier for the window. Every window has a WndProc( ) function (which you are going to write or else Microsoft has already written) and a unique numeric identifier known as the HWND. You can send an arbitrary message to any window for which you know the HWND. The WIN32 API provides the SendMessage( ) and PostMessage( ) functions for this purpose.

After the HWND parameter you see the msg passed parameter which indicates the particular message. The body of the WndProc( ) function is nothing more than a giant switch statement that does different things depending upon the particular message that was just received. Depending upon the msg passed param, the WPARAM and LPARAM passed parameters are unpacked and employed in various ways. This particular WndProc( ) farms out the work of responding to the WM_SIZE message to a helper function called OnSize( ). Inside this OnSize( ) function you would find the logic that decides how to compress or expand the window's contents to fit the new size. A typical window is nothing but a collection of child windows. Hence the typical WndProc( ) function upon receiving the WM_SIZE message merely subdivides the available screen real-estate among his children and then commands each of them (via a second round of WM_SIZE messages) to adopt the new size.

LRESULT CALLBACK WndProc( HWND   hWnd,
                          UINT   msg,
                          WPARAM wParam,
                          LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            return 0;     // nothing needs to be done
        }
        case WM_SIZE:
        {
            UINT    uiSizeType = wParam;
            UINT    uiClientWidth  = LOWORD( lParam );
            UINT    uiClientHeight = HIWORD( lParam );

            OnSize( uiSizeType, uiClientWidth, uiClientHeight );

            return 0;
        }
        case WM_PAINT:
        {
            OnPaint( hWnd );

            return 0;
        }
        case WM_LBUTTONDOWN:
        {
            UINT      uiKeyFlags = wParam;
            POINT     pt;

            pt.x = MAKEPOINTS( lParam ).x;
            pt.y = MAKEPOINTS( lParam ).y;

            OnLButtonDown( uiKeyFlags, pt );

            return 0;
        }
        case WM_MOUSEMOVE:
        {
            UINT      uiKeyFlags = wParam;
            POINT     pt;

            pt.x = MAKEPOINTS( lParam ).x;
            pt.y = MAKEPOINTS( lParam ).y;

            OnMouseMove( uiKeyFlags, pt );

            return 0;
        }
        case WM_DESTROY:
        {
            PostQuitMessage( 0 );   // creates a WM_QUIT message

            return 0;
        }

        default:                    // catches all other messages

            break;

    } // end switch( msg )

    return DefWindowProc( hWnd, msg, wParam, lParam );

}  // WndProc()

Near the bottom of this WndProc( ) you can observe a call to the DefWindowProc( ) function, which is a function provided by Microsoft as part of their WIN32 API. You can pass any messages that you don't want to handle on to Microsoft's default window procedure, which will handle them in some safe manner.

I have shown you a simple WndProc( ) function just to better explain how the Windows operating system keeps everything running by constantly bombarding each window with an endless series of messages. Every window (and remember that most top-level windows actually consist of numerous child windows) receives its own stream of messages from the operating system. Microsoft has defined over 1000 different messages and every programmer is allowed to add to this list. Learning to respond to all these messages successfully takes lots of practice and even more patience. I am not going to ask you to write a WndProc( ) function because that would be too difficult for your current proficiency. But you will be writing Windows programs nonetheless, because I provide a class framework that hides this complexity from you. In order to construct a child or top-level window with my CSL framework you only need to instantiate an object (an instance of a class) and call its Create( ) member function. You will see this code in our next example program.

When you finish my curriculum I hope you'll feel ready to learn more about WndProc( ) functions and Windows messages. If so, you should definitely get one of Petzold's books: either Programming Windows Fifth Edition or the older Programming Windows 95. These books might even be available from your local library (note that many libraries will purchase a book they don't have if someone requests it). Petzold's books come with the source code for a very large number of example programs that each illustrate one aspect of a Windows program. The book assumes you have purchased Microsoft's Visual C++ 6.0 compiler which costs about $100 in its least expensive version. But you don't need to worry about that expense since CPPIDE can compile, execute, and debug all 145 of the Petzold programs using nothing more than the CD-ROM that comes with Petzold's book. I have already prepared the 145 project (.PRJ) files and the 145 makefiles (.MAK) that make this possible. Full instructions for how to employ CPPIDE for the Petzold programs can be found at the following world-wide-web location:

http://www.computersciencelab.com/Petzold.htm

This same write-up can also be found on the Computer Science Lab CD-ROM in the following file (assuming that drive letter D: corresponds to your CD-ROM drive):

D:\Petzold\Petzold.htm

If you do decide to step up to a professional software development tool you will appreciate that Microsoft's Visual C++ employs the same basic layout of panes as CPPIDE and the same toolbar buttons. This will help you to feel at home, but be warned: the Visual C++ compiler is a very complicated tool.

If you are not ready to dive into the Petzold book, another alternative is to read tutorials that have been posted on the world-wide-web. One such tutorial on the WIN32 API can hopefully still be found at:

http://www.winprog.org/tutorial

There is also a nice sequence of ten tutorials written by Joseph Farrell and available on the GameDev.net web site. The first of these tutorials can hopefully still be found at:

http://www.gamedev.net/reference/programming/features/gpgenesis1.

The URL for the next tutorial in this series is:

http://www.gamedev.net/reference/programming/features/gpgenesis2.

etc.


Maze Example Program

( 33 )

The Maze Solver Example Program

So let's get started with our first true Windows program. Let's use the C++ language to drive the robotic mouse through the maze that we first saw in the 8051 microprocessor simulator.

At this time load the MazeSolver project. You will observe that this project consists of 5 files: AppMain.cpp, cMazeDbg.cpp, cMazeDbg.h, and cMazeDbg.rc, and cMazeDbgResource.h. There is a lot of code in these 5 files. And you are only seeing the tip of the iceberg because this code rests upon my CSL Framework which is many more thousands of C++ statements. Don't panic! I have transitioned from console-mode programs to Windows programs so that you can have more, not less, fun while learning how to program. I will be pointing out the sections of this code that I want you to study. By the end of this curriculum you should be able to come back and study all of this code.

The .RC file is our first example of a Windows Resource (.RC) file. Microsoft invented .RC files to hold the stuff that didn't match up so well with the C (and C++) language. The C language can only express executable statements but a Windows program needs to be able to specify the contents of the menu, the button images for the toolbar, the accelerator keys (which are a fast way into the menu), the icons and other bitmap images that adorn child windows, etc. This type of stuff is called resources and the compiler places it into a special section of the .EXE file that the compiler creates. Microsoft defined the language used in .RC files and it is a pretty straightforward language. I describe the contents of .RC files in an appendix but for now I recommend that you ignore this file entirely.

Given the fact that this project includes a cMazeDbg.cpp and cMazeDbg.h file, you can guess that it employs a cMazeDbg class. The cMazeDbg.h file is the header file that holds the declaration of the class and the cMazeDbg.cpp file holds the implementation of the member functions in the class. We will look at these files in just a moment.

Now inspect the AppMain.cpp file. Don't search too long for the usual main( ) function where all C and C++ programs must start. You won't find it because Microsoft needs a chance to execute some initialization code before transferring control to the code you write. So Microsoft claims the main( ) entry point in all Windows programs. The Microsoft code that initializes the Windows message passing environment then calls your code via a function you must name WinMain( ). So the initial entry point (at least from your perspective) into any Windows program is a function called WinMain( ).

The function prototype for this WinMain( ) function is shown below:

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR pszCmdLine, int iCmdShow );

Don't worry about the parameters that you can observe being passed to the WinMain( ) function since they are rarely used. The pszCmdLine argument is how a Windows program receives any command line arguments that are specified when you initiate the program. But, as I said, this is rarely done. Instead, you typically tell a Windows program what to do by manipulating its menu and toolbar. The data type of the pszCmdLine argument is PSTR which is just a typedef invented by Microsoft:

  typdef  char *  PSTR;

Hence a PSTR is just a pointer to a char, hence a string.

The Petzold book is the best reference for learning how to write Windows programs that depend upon nothing but Microsoft's WIN32 API. And the most common complaint you hear about the Petzold example programs is that they are big. Even the "Hello World" program takes 45 statements when written as a Windows program rather than a console-mode program.

But Petzold does not employ the C++ language in his book. The class feature of the C++ language allows a lot of detail to be (at least temporarily) hidden inside various objects (class instances). For example, all of the complexity involved in dealing with the toolbar can be encapsulated in a cToolBar class. And all of the complexity of displaying the picture in a .BMP file can be hidden in an instance of the cBitmap class. The availability of the various classes allow you to conceptualize the working of your program on a higher level of abstraction.

You are free to construct such classes from scratch. CPPIDE will be able to compile and execute any program you write. But since we are just learning the C++ language, I am going to provide you with a collection of finished classes. I provide a cToolBar class, a cStatusBar class, a cBitmap class, and quite a few other classes. We will be learning how to employ these classes as we proceed forward. And I will be showing you how to create new classes that can interact with the ones I provide. The standard terminology for inter-related classes such as these is to call them a class framework. I have provided you with a class framework that drastically simplifies the job of creating Windows programs. I call this the CSL Framework (Computer Science Lab Framework).

My CSL Framework resides in a library file named libCSL.a. When you choose to base a program on the CSL Framework it means that you will be linking the libCSL.a library file with whatever additional code you write. Linking is simply the final step in compiling. Don't worry about this linking business, our makefiles will make the process invisible to you.

Library files are a big deal so I will say a few more things about them. There are two kinds of library files: static link libraries and dynamic link libraries. A dynamic link library file uses the .DLL filename suffix and hence is called a DLL. The contents of a static link library are incorporated into the executable (.EXE file) whereas a dynamic link library remains separate from the .EXE file. Consequently an executable that depends upon a DLL must be able to find that DLL each time that executable is run. Perhaps you have seen a Windows program refuse to run because a needed DLL file either couldn't be found or was the wrong revision, etc.

Microsoft based its Windows operating system upon dynamic link libraries because in the early days of Windows the typical home computer had almost no memory and DLLs conserve memory by allowing different executables to share the same DLLs. The Windows operating system itself is just a collection of DLL files.

The advantage of a static link library is that since it is incorporated into the .EXE, there is no way for it to get lost. If you have the .EXE file then you can definitely run that program. Therefore I have chosen to distribute my CSL Framework as a static link library. Microsoft's Visual C++ compiler uses a .LIB filename suffix for static link libraries while the Gnu C++ compiler (used by CPPIDE) uses a .A ("archive") filename suffix.

Here is what the WinMain( ) function for a typical Windows program that employs my CSL Framework will look like:

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR pszCmdLine, int iCmdShow )
{
    // Ensure that the common control DLL is loaded.

    InitCommonControls();     // InitCommonControlsEx() for the newer controls

    // Create an instance of the cWinApp class.  Every program
    // that employs the CSL Framework requires this object
    // to provide infrastructure.

    cWinApp      theApp( hInstance, pszCmdLine, APPNAME );

    // Instantiate an instance of whatever class provides your
    // intended main window.  The window will not be visible
    // until you call the class's Create() member function.

    cTopLevelClass    MyTopLevelWnd;

    MyTopLevelWnd.Create();

    // We now enter the message loop.

    return theApp.Run();

    // cWinApp::Run() does not return until a WM_QUIT message is received.

} // WinMain()

Now that's pretty clean: only 5 statements!

In the particular Windows program that we are currently studying, the MazeSolver example program, the following 2 statements from the typical WinMain( ) function shown above:

    cTopLevelClass    MyTopLevelWnd;

    MyTopLevelWnd.Create();

must be changed to:

    cMazeDbg    maze;

    maze.Create();

since the cMazeDbg class is the class that encapsulates the top-level window for this particular program.

It is my convention to always place the WinMain( ) function in a file named AppMain.cpp. And usually the AppMain.cpp file needs to hold nothing but the WinMain( ) function. The other code is placed in the .CPP implementation files for whatever classes the program employs.

I now going to walk you through the AppMain.cpp file for the MazeSolver example program. The purpose of the AppMain.cpp file is to hold the WinMain( ) function which is where all Windows programs start.

After some comments at the top of the AppMain.cpp file, you come to the preprocessor statements:

#include "cWinApp.h"
#include "cMazeDbg.h"

All Windows programs are required to #include the Microsoft provided <windows.h> header file and a few other header files. This is so common that I save myself from typing all these #include statements by placing this repetitive code in a cWinApp.h file. Then I just need the one #include "cWinApp.h" statement in the AppMain.cpp file of all projects that employ my CSL Framework. You can look at the cWinApp.h file if you want but it is pretty complex so I am not recommending that you look at it now (it will be in the CppIde\include folder, the exact location depends upon the choices you made while installing Computer Science Lab).

The cWinApp.h header file holds the stuff common to all Windows programs. The stuff specific to the Maze is placed into the cMazeDbg.h file, which holds the declaration of a cMazeDbg class. This cMazeDbg class is the heart of this Maze Solver application, so let's spend some time getting to know it. The cMazeDbg class is an example of the Windows infrastructure that your programs can tap into merely by employing my CSL Framework. The infrastructure provided by this framework is documented in a sequence of .H header files that you can find in the CppIde\include folder.

Rather than studying the actual cMazeDbg.h file, which is rather complex, let's just consider the following simplified declaration for the cMazeDbg class:

class cMazeDbg : public cBaseWindow
{
public:
    cMazeDbg();          // constructor
    virtual ~cMazeDbg(); // destructor

    // Interface
    BOOL           Create();
    BOOL           RestartHunt();
    BOOL           LeaveBreadCrumbTrail( BOOL bLeaveTrail );
    BYTE           GetNeighboringWalls();
    BOOL           MoveMouse( UINT uiDirection );
    BOOL CDECL     printf( char * pszFormat, ... );
private:
    BOOL           Destroy();
    void           OnPaint();
    void           OnSize( UINT nType,
                           UINT uiClientWidth, UINT uiClientHeight );
    static LRESULT CALLBACK MainWndProc( HWND hWnd, UINT msg,
                                         WPARAM wParam, LPARAM lParam );
    // Child windows.
    cToolBar       * m_pToolBar;        // each of these instances is
    cStatusBar     * m_pStatusBar;      //   allocated in the cMazeDbg ctor
    cMaze          * m_pMaze;           // child window holding maze
    cEditBox       * m_pEditBox;        // child window for printf() statements
};

The statement:

class cMazeDbg : public cBaseWindow

starts the declaration of the cMazeDbg class. The colon (:) indicates that this class inherits from the cBaseWindow class. Inheritance is one of the hallmarks of all modern object-oriented languages. It simply means that any instance of the cMazeDbg class inherits all the functionality of the cBaseWindow class. Thus an instance of the cMazeDbg class can do anything an instance of the cBaseWindow class can do, plus some more.

Inheritance is a convenient way to write code once and then use it again and again. I wrote the cBaseWindow class to take care of all the details that must be arranged for any Windows window. I then have my cToolBar class, my cStatusBar class, my cMazeDbg class, etc. all inherit from this base class. The compiler then automatically includes the contents of the cBaseWindow class (its functions and its state data) into every instance of each of these other derived classes. You will get a chance to do some inheriting in a later example program. But for now, just treat this as an implementation detail that you don't need to be concerned with.

The body of the cMazeDbg class declaration has both a public and a private section. In the public section you find the public interface offered to the outside world. Any code that instantiates (creates) an instance of the cMazeDbg class can then call any of the functions listed in the public section of the cMazeDbg class.

Let's jump from the cMazeDbg.h file back to the AppMain.cpp file to prove this last statement. In the WinMain( ) function you can find the code:

    cMazeDbg    maze;

    maze.Create();

    maze.RestartHunt();
    maze.LeaveBreadCrumbTrail( true );

    maze.printf( "Starting into maze...\n" );

The first statement in this code snippet creates a new variable named "maze" of type cMazeDbg. Hence maze is an object (that is, an instance of a class). Since a new variable of type cMazeDbg is coming into existence, you know that the cMazeDbg constructor will be called at this time to initialize the new instance. Once you have an instance, you can use it to call member functions of the class. We can see that the code in WinMain( ) is making calls to the member functions cMazeDbg::Create( ), cMazeDbg::RestartHunt( ), cMazeDbg::LeaveBreadCrumbTrail( ), and cMazeDbg::printf( ). You will be able to find each of these member functions listed in the public section of the cMazeDbg class declaration (do it!).

You might recall that we already saw a call to a constructor in the earlier Scale project. That time, the main( ) function instantiated an instance of the cScale class named MyFirstScale via the following statement:

cScale   MyFirstScale( 220, 500 );

In the current Maze program, the WinMain( ) function is instantiating an instance of the cMazeDbg class named maze via the following statement:

cMazeDbg    maze;

You might be bothered by the lack of symmetry between these two code snippets. One statement needs parentheses but the other doesn't. That's correct and is merely a consequence of how the constructors for the two classes were defined. When you create a class you get to choose your constructor. The only rule is that the signature for a constructor lacks a return value. Beyond that, you have free choice. The constructor for the cScale class is declared (in the cScale.h header file) as:

class cScale
{
public:
    cScale( double dTonicFreqInHz, DWORD dwQuarterNoteDurationInMSecs );
...
};

and hence needs two passed parameters. But the constructor for the cMazeDbg class is declared as:

class cMazeDbg : public cBaseWindow
{
public:
    cMazeDbg();
...
};

and hence needs no passed parameters. Consequently there are no parentheses in the statement:

cMazeDbg    maze;

The code in WinMain( ) employs the new maze object to initiate the search. First it calls the cMazeDbg::Create( ) member function via the statement:

    maze.Create();

This is an example of calling a member function on an instance of a class. You should refer back to the declaration of the cMazeDbg class and observe that there is a member function named Create( ) in the public section (otherwise, this code would not compile).

Inside the cMazeDbg class declaration (found in the cMazeDbg.h header file) the Create( ) function is described as just Create( ). But in the cMazeDbg class implementation (found in the cMazeDbg.cpp file) we must refer to this function as cMazeDbg::Create( ) using the scope resolution operator (::) that combines the class name with the function name. This is necessary because many different classes might all have a Create( ) member function. And when we have an instance of the cMazeDbg class and we want to call the Create( ) member function on that instance we employ the dot operator as seen in the statement:

    maze.Create();

Reread this last paragraph if you are confused about when to use each of these 3 notations. Since I am not going to be asking you to create your own C++ classes, you will primarily be employing the dot operator.

So the statement:

    maze.Create();

creates a maze. This means that a maze will become visible in a child window of the Maze Solver application. Note the distinction: the cMazeDbg constructor is responsible for creating a variable while the Create( ) member function is responsible for creating a visible window.

Since your program begins in the WinMain( ) function, this is where you always need to create a top-level window. The code in the cMazeDbg::Create( ) function creates a top-level window consisting of a few child windows. Let's take a look at this top-level window now:

We can see that this top-level window includes not only a maze pane (child window) but also a text pane. You see, I have created (in my CSL Framework) both a cMaze class that is the maze by itself and a cEditBox class which holds a text editor. The cMazeDbg class incorporates an instance of the cMaze class and an instance of the cEditBox class and thereby can offer a top-level window that displays both a maze and a text area. We will find the text pane useful for printing debugging messages that explain why our maze solving algorithm is failing.

The top-level window offered by the cMazeDbg class includes not only a maze and a text pane, but also a toolbar and a status bar. However the toolbar and status bar are not currently being displayed.

Are you ready to learn how a class representing a top-level window can manage to hold instances of other classes representing child windows? Then look in the private section of the cMazeDbg class declaration (in the cMazeDbg.h header file) and you will find the four following class data members:

private:
    // Child windows.
    cToolBar       * m_pToolBar;        // each of these instances is
    cStatusBar     * m_pStatusBar;      //   allocated in the cMazeDbg ctor
    cMaze          * m_pMaze;           // child window holding maze
    cEditBox       * m_pEditBox;        // child window for printf() statements

Because every instance of the cMazeDbg class has these data members, it can offer the functionality of a toolbar, a status bar, a maze pane, and a text pane. Putting together a top-level window using my CSL Framework is sort of like ordering at a restaurant a la carte: you just pick some items from a list of possibilities. These items are placed into the private section of the class declaration because the code that employs an instance of the cMazeDbg class doesn't need to worry about them. They are just hidden infrastructure.

This is the encapsulation that I was talking about before. The code in WinMain( ) does not need to deal with a text pane or a toolbar, etc.; it just deals with the cMazeDbg variable (class instance) it has created. The only thing that code [such as WinMain( )] that employs an instance of the cMazeDbg class need worry about are the member functions declared in the public section of the cMazeDbg class declaration. I list below the 3 most important member functions offered by the cMazeDbg class:

    BYTE           GetNeighboringWalls();
    BOOL           MoveMouse( UINT uiDirection );
    BOOL CDECL     printf( char * pszFormat, ... );

These 3 member functions are the heart of the cMazeDbg class. To explore the maze we repetitively call the GetNeighboringWalls( ) function, decide upon a new direction to move, and then accomplish that move via a call to the MoveMouse( ) function. You can expect to find calls to these functions inside some sort of loop. The loop will need to continue iterating until the mouse arrives at the cheese. Study the WinMain( ) function until you can identify this loop [hint: it is a while(true) loop]. During each iteration of this loop we ask for the walls that surround us and then make a move. The logic that decides which move to make is accomplished with an if/else statement.

The printf( ) member function of the cMazeDbg class works just like the printf( ) function we employed in our console-mode programs, except the characters it generates appear in the text pane of our top-level window. You are free to add whatever calls to this printf( ) function you would like. You can see that my code makes two calls to printf( ) during each iteration of the while(true) loop. The first call to printf( ) prints out the BYTE value returned by the cMazeDbg::GetNeighboringWalls( ) member function. The second call to printf( ) prints out a message that documents the movement we choose to make. We will see these printf( ) statements in action when we run the program.

The cMazeDbg::GetNeighboringWalls( ) function returns a BYTE, which is an unsigned 8 bit value. I place the value returned by the call to GetNeighboringWalls( ) into a variable named jWalls (the Hungarian notation for the BYTE data type uses a "j" prefix because the b prefix has already been taken by the Boolean data type). I now need to split this BYTE into its individual bits since 4 of the bits represent the walls and the MSBit (most significant bit) represents the cheese as documented below:

 MSBit  7     6     5     4     3      2      1      0   LSBit
     cheese  not   not   not   rear   right  front  left
             used  used  used  wall   wall   wall   wall

The way to isolate individual bits is to use the bitwise AND operation. I explained this in the 8051 microprocessor portion of this curriculum so I am only going to summarize the method here. Let's imagine that the jWalls variable holds the value 0x82 which is 10000010 in binary. This means we are on the cheese and there is a wall in front of us. The algorithm in the WinMain( ) function first checks whether the mouse has landed on the cheese. To learn this it needs to isolate the MSBit of jWalls. To do this it ANDs jWalls with the value 0x80 = 10000000. Because we AND seven of the bits in jWalls with zeros, the corresponding bits in the result are guaranteed to be zero. Thus there are really only two possibilities: the jWalls value is 1xxxxxxx or 0xxxxxxx where I use an x to indicate a bit whose value doesn't matter. If we AND 1xxxxxxx with 10000000 we will get 10000000. If we AND 0xxxxxxx with 10000000 we will get 00000000. So the result of the AND will consist of all zeros if we have not landed on the cheese and it will be a non-zero value if we have landed on the cheese. This works out great because the C language treats any zero value as FALSE and any non-zero value as TRUE.

In 8051 assembly language we learned that the ANL instruction mnemonic causes the 8051 microprocessor to perform the bitwise AND operation. In the C and C++ languages you request the bitwise AND operation via the ampersand & symbol. Search through the code in WinMain( ) until you have found the locations where we perform the following bitwise AND operations:

  jWalls & 128     // 128 = 0x80 = 10000000
  jWalls &   8     //   8 = 0x08 = 00001000
  jWalls &   4     //   4 = 0x04 = 00000100
  jWalls &   2     //   2 = 0x02 = 00000010
  jWalls &   1     //   1 = 0x01 = 00000001

The result of the bitwise AND operation is a BYTE, not a Boolean. Yet you can see that the WinMain( ) code employs this value as the test of an if statement. We know that the test statement of an if or while statement must be a Boolean; i.e., something that is either TRUE or FALSE. So why does this code work? It works because the C language states that any integer (of any width) that equals 0 will be treated as the value FALSE when this integer is employed as a Boolean. And any non-zero integer will be treated as TRUE. So the statement:

   if ( jWalls & 128 )  // we found the cheese!
      break;            // stop looping

is a legal if statement. The test of the if statement will be true only when jWalls has the MSBit (the bit worth 128) set and this means we will break from the while(true) loop only when we have landed on the cheese. Are you with me?

Good, because it gets worse. Look at the next if statement in WinMain( ):

   if ( ! ( jWalls & 2 ) )  // if there is no wall in front...
      maze.MoveMouse( 1 );  // ... then move forward

In this if statement you see the bang (!) or NOT operator (it's known to English teachers as the exclamation point). In the C language the NOT operator logically negates a Boolean value, meaning that a TRUE becomes a FALSE and a FALSE becomes a TRUE. Hence in this if statement, the NOT operator negates the Boolean value of ( jWalls & 2 ) which is TRUE only when there is a wall in front of the mouse. Why do we want to negate this? We want to move forward only when there is NOT a wall in front of us. We want to put some code in the body of the if statement which will cause us to move forward. Therefore we want the test of the if statement to be true when there is NOT a wall in front of us. You could say that we need to employ the NOT operator because the if statement happens to be defined such that the body is executed only when the test evaluates to TRUE. If the C language offered some other kind of if statement which would execute its body when the test evaluated to FALSE then we would not need to employ the NOT operator with this other kind of if statement. Got that?

I keep saying bitwise AND because there is also a logical AND operation. The logical AND operation uses 2 operands which are both Booleans. The logical AND result will be TRUE only if both operands are TRUE. The C and C++ languages employ a double ampersand && to represent the logical AND operation. An example of the logical AND operation would be:

    if ( bWeHaventFoundTheCheese && bTimeIsUp )
        break;

I use the Hungarian b prefix to indicate variables that are of the Boolean (BOOL) data type. So the logical AND operator && operates on Booleans while the bitwise AND operator & operates on numbers. The full table of logical and bitwise operators is shown below:

Syntax | Operation | Operands
& | bitwise AND | numbers
| | bitwise OR | numbers
~ | bitwise COMPLEMENT | numbers
&& | logical AND | Booleans
|| | logical OR | Booleans
! | logical NOT | Booleans

I want to reiterate here because the difference between the logical operators and the bitwise operators is subtle. A bitwise operator (be it an AND, OR, or COMPLEMENT operator) works upon the individual bits of the operand. As an example, the bitwise COMPLEMENT of the BYTE value 0x55 is 0xAA, as shown below in binary:

  0x55 = 01010101

Hence:

  ~0x55 = 10101010 = 0xAA

This demonstrates a bitwise operator applied to a BYTE variable, but they can also be applied to WORD (unsigned 16 bit), short int (signed 16 bit), DWORD (unsigned 32 bit), and int (signed 32 bit) operands.

Now consider the logical NOT operator applied to a BYTE value. There are a total of 256 different BYTE values, since a BYTE is 8 bits. The C/C++ language treats 255 of these 256 patterns as representing logical TRUE and the language treats the 1 remaining pattern (the all zeros pattern) as representing logical FALSE. Therefore:

  !0x00 = !(FALSE) = TRUE
  !0x01 = !(TRUE)  = FALSE
  !0x02 = !(TRUE)  = FALSE
  !0x03 = !(TRUE)  = FALSE
      ...
  !0xFF = !(TRUE)  = FALSE

The compiler won't catch your mistake if you accidentally employ a bitwise operator in a spot where you meant to use a logical operator, or vice versa. The compiler doesn't detect anything wrong because both are legal. To avoid mistakes you just need to keep asking yourself, "Is this a Boolean (i.e., a true/false concept) or is it a number?"

At this time compile and execute the program. You will observe that initially the mouse seems to head toward the cheese but then he gets stuck in a vortex. The problem is not in our code, but in our algorithm. Since we studied this very same maze in the 8051 microprocessor section of this curriculum I assume you remember the fix: re-arrange the algorithm such that it does what you would do if you stuck out your left hand and moved in such a way as to keep your left hand always pressed up against the left wall. But I don't want you to make this change. Instead, we will use the problem to learn some more about C++. Your assignment comes up on the next page.

But first let me say a few closing words. While the mouse is caught in the vortex you will have time to observe that the menu choices in the Maze Solver application are non-functional (you can use the Close button in the upper right-hand corner to kill the program). The menu is non-functional because we haven't yet structured our code as required for a Windows program. We will be learning how to make the menu functional in later example programs. In the meantime I will only say that the following statements seen in the WinMain( ) function:

   if ( ! theApp.PumpMessages() )
      break;

are necessary to pump the messages (such as WM_PAINT) that the Windows operating system continually routes to all Windows programs. Any Windows program must include a certain amount of code to even receive these messages. Luckily, my cWinApp class (which is part of my CSL Framework) provides infrastructure such as the PumpMessages( ) function seen above that makes this easy.

If you would like to momentarily halt the Maze Solver program so you can read the printf( ) messages in the text pane, simply click and hold on the caption (title) bar.

Finally, the fact that the C and C++ languages are willing to treat 0 valued integers as the Boolean FALSE value is another example of the permissive nature of these languages. A language like Pascal insists upon a strict separation of concepts: an integer is a number and a Boolean is either true or false but it is not a number. But this rigidity is kind of stupid since all computers really only know how to process numbers and hence under the covers the Pascal language does employ some number to represent true and some other number to represent false. Since this is so obviously useful, why deny this trick to the programmer? The C and C++ languages were written by working programmers rather than academics living in ivory towers.


( 34 )

Debugging Windows Programs

When we execute the MazeSolver.exe program we see that the mouse gets trapped in a vortex and endlessly spins in a tight loop consisting of 4 consecutive left turns. The code in WinMain( ) never exits from the while(true) loop because the mouse never reaches the cheese. And the text pane of the cMazeDbg window keeps scrolling with new messages stating that we are electing to turn left yet one more time. When you are tired of watching this you can kill the program using the Close button found in the upper right-hand corner of the window.

We use the cMazeDbg::printf( ) member function to produce debugging statements that allow us to observe the execution of our algorithm. But we call the printf( ) function so many times that the interesting statements produced near the start of the program scroll out of the window and cannot be inspected.

So let's fix this. I want you to modify the code in WinMain( ) such that it will detect 4 consecutive left turns and then exit from the while(true) loop. Obviously you will need to place inside the while(true) loop an if statement such as the following:

   if ( uiNumConsecutiveLeftTurns >= 4 ) // we're stuck in a loop
   {
      maze.printf( "Giving up\n" );
      break;     // leave the while(true) loop
   }

Obviously, you will have to declare and initialize this new uiNumConsecutiveLeftTurns variable. The Hungarian notation prefix "ui" tells you that I selected the UINT (unsigned integer) data type for this variable.

Now here is a trap you might fall into. You might think that you need to increment uiNumConsecutiveLeftTurns each time you decide to make a left turn. But this is not right and would cause your program to terminate way too soon. The problem is that you are incrementing uiNumConsecutiveLeftTurns for all left turns. You need to increment this variable only upon consecutive left turns. How can you accomplish that?

It is very common in program loops to need to remember some decision made during a prior iteration of the loop. In this case we need to remember the direction we turned during the prior iteration of the loop. We then want to increment uiNumConsecutiveLeftTurns when we decide to turn left AND we also decided to turn left during the previous iteration of the loop. Hopefully you can see that we will need a new variable to remember the decision we made during the last loop iteration.

The following code -- not related to our maze -- uses such a variable to remember the prior value of uiSomeNumber:

   UINT  uiSomeNumber = 0;
   UINT  uiLastSomeNumber;

   while ( true )
   {
      uiLastSomeNumber = uiSomeNumber;
      uiSomeNumber++;
      printf( "uiSomeNumber = %u, uiLastSomeNumber = %u\n",
              uiSomeNumber, uiLastSomeNumber );
   }

Notice how inside the while(true) loop we have to re-initialize uiLastSomeNumber BEFORE we re-initialize uiSomeNumber or else the old value of uiSomeNumber will not be available when we need to re-initialize uiLastSomeNumber. So the order of the 2 statements inside the while(true) loop is critical.

Now back to the maze. We need to remember the direction we decided to move during the last loop iteration, and since directions are indicated using a UINT we can solve this problem by declaring a new variable:

   UINT  uiLastDirection;

That is all the hints I am going to give you. I predict that you will struggle getting this task accomplished. Give yourself time for plenty of attempts before proceeding with my explanation. You should only need to make changes within the WinMain( ) function located in the AppMain.cpp file.

Remember, you goal is to halt the program as soon as 4 consecutive left turns have been detected. Now normally, when faced with a tough programming assignment that you just can't seem to get right, your salvation would be to sprinkle lots of calls to the printf( ) statement all through your code so you can see what's going on. But if your program is not halting then these printf( ) statements will just keep scrolling by too fast to read. One thing you can do is to click and hold on the Maze program's caption bar in order to momentarily halt the flow of printf( ) messages. Better yet, click and hold on the text pane's scroll bar in order to momentarily halt the messages and be able to scroll back to the earliest messages.

a while later...

So perhaps you have arrived at this point with a working (that is, halting) program. If so, congratulations. You're doing real programming now. If you haven't been able to get your program working, don't fear. We still have our secret weapon: the debugger! This is our first time to use the debugger on a Windows program and hence I need to explain the procedure.

There is a new complication from the fact that the program we are trying to debug is also responsible for repainting its window whenever Microsoft's Windows operating system tells the program that it is time to repaint. This interaction can get in the way of our debugging. To prevent trouble you need to guarantee that the CPPIDE window and the window of the program being debugged DO NOT OVERLAP AT ALL. If there is any overlap between the windows of these two programs then every time we switch from the debugger to the program being debugged, the Windows operating system will first demand that the program being debugged repaint its window. This activity can't happen since the debugger is controlling the execution of the program being debugged. The problem is bigger than the fact that the window won't get repainted. The message driven nature of the Windows operating system causes an endless procession of WM_PAINT messages that change the flow of the program being debugged. This means that we may be prevented from studying the section of the program we are interested in observing because the program keeps jumping to that section where it responds to the endless stream of WM_PAINT messages. In short, you won't be having any fun if this is happening.

But the solution is simple: whenever you debug a Windows program you must not allow the CPPIDE window (holding the debugger) to overlap any portion of the window of the program being debugged. I have designed my CPPIDE application to take care of this automatically but you could still screw it up if you use the mouse to drag either window to a new size or position.

You also can't allow any overlap between the window that displays CPPIDE's on-line help documentation (this is the window you are reading right now) and the window of the program being debugged. Therefore I recommend that you finish reading this page and then close the Help window before entering the debugger. You can re-open the help window (using the Help/Help option on CPPIDE's menu) after you again exit from the debugger.

At some time when your attempt at the solution to the MazeSolver assignment compiles without errors, enter the debugger by clicking on the Debug ( [image]) toolbar button. You will observe that CPPIDE resizes itself to take no more than half of the area of your video display. We haven't seen this before because it isn't necessary when debugging console-mode programs. CPPIDE will either take the right hand half or the bottom half, as specified by the [Window Placement] line in the .PRJ file. You can override this default window placement via an option found on the View/Options dialog.

The program being debugged is told to occupy the other half of the video display. Initially, this other half of the video display will be empty because the debugger has not yet started the program being debugged. But when it does, these two windows will not overlap and that is very important. Any overlap will cause a flurry of WM_PAINT messages which the program being debugged can't respond to since the debugger is controlling its execution and hence you will probably see visible defects in that window. Do not use the mouse to reposition or resize the window of either CPPIDE or the program being debugged!

Now that the debugger is running, scroll the source code pane to find the breakpoint that CPPIDE automatically requests on your behalf. You will see that since this is a Windows program, this automatic breakpoint has been placed on the WinMain( ) function rather than the main( ) function. You don't get to even see the main( ) function in any Windows program.

Since a breakpoint is in place you are ready to click on the Continue ( [image]) button which will cause your program to run until it reaches the first executable statement in WinMain( ). From then on you probably want to single-step and use GDB's print command to observe the current value of the variables such as uiNumConsecutiveLeftTurns. I would recommend that you use the Step Over ( [image]) rather than the Step Into ( [image]) button so that you don't enter either the cMazeDbg constructor or the cMazeDbg::Create( ) member function.

As soon as you single step the statement:

    maze.Create();

you will observe the Maze Solver application's top-level window appearing in the left half of your video display. CPPIDE has arranged to give both programs the maximum possible screen real estate while not allowing any overlap between their windows. If your video display card is configured for 800 by 600 pixels or, even worse, 640 by 480 pixels, your screen will look crowded. You should determine whether your computer can be set for a higher resolution such as 1024 by 768 pixels. To do this, exit from CPPIDE and then right click on an empty portion of the desktop. Then select "Properties" from the pop-up menu, go to the "Settings" tab, and adjust the "Screen Area" setting.

After seeing this crowding it will be obvious why professional programmers own very large video display units (these are also known as CRTs or monitors). In fact, some programmers own 2 video display units which sit side by side. One unit displays the program being tested and the other displays the debugger and printf( ) messages.

One thing I have done to improve this crowding problem is to equip both CPPIDE and the MazeSolver application with splitter bars. This means you can rest the mouse cursor on the dividers between the panes and then click and drag to re-apportion the relative sizes of the panes. Perhaps you want to give the printf( ) message area more room at the expense of the maze, or vice versa. Unfortunately, the splitter bar in the MazeSolver application will not be functional while you are single stepping because it depends upon the delivery of Windows messages. But it will be functional while the program is running.

You can also experiment with locating the program being debugged on top of or to the left of the CPPIDE window. You control this setting via the Options dialog that you get from the View/Options menu choice. If you change this setting when you are already in the debugger it won't take effect until the next time you enter the debugger.

Hopefully with the aid of the debugger you will be able to spot the problem which has prevented your program from halting after four consecutive left turns. Remember that to edit your program you must first exit from the debugger. Please make a valiant effort to accomplish this assignment. Try a number of alternatives before looking at my solution (in the Solutions folder). It is the sweat off your brow that shows new abilities are being pushed into your brain. As the exercise video says, "Feel the burn".

When you exit from the debugger, CPPIDE's window will automatically return to its former size.


( 35 )

Enums And Macros

You will notice that I didn't give you the assignment of re-arranging the maze solving algorithm such that it does what a human would do if he stuck out his left hand and explored the maze while keeping his left hand on the left wall. This is such a trivial change that I didn't want to insult your intelligence. I have made these changes and placed them in the Maze Solver 2 example program. But I also made a few other changes that I now want to discuss.

At this time load the MazeSolver2 project. To begin with, note that the code in AppMain.cpp defines macros to stand in for the manifest constants such as 128, 8, 4, 2, and 1. The two statements:

#define  CHEESE  128
if ( jWalls & CHEESE )

are exactly equivalent to the single statement

if ( jWalls & 128 )

By using macros you get the opportunity to label your manifest constants with their purpose, which is a great idea.

The other change you will notice in my solution is in the parameters I pass to the cMazeDbg::MoveMouse( ) function. I show below the implementation of this function as taken from the cMazeDbg.cpp file:

BOOL cMazeDbg::MoveMouse( UINT uiDirection )
{
    return m_pMaze->MoveMouse( (cMaze::Direction)uiDirection );
}

Inside this function you can observe that we are calling the cMaze::MoveMouse( ) function on an instance of the cMaze class called m_pMaze. This cMaze instance was one of the private data members of the cMazeDbg class, as seen in the following section of the cMazeDbg class declaration:

private:
    // Child windows.
    cToolBar       * m_pToolBar;        // each of these instances is
    cStatusBar     * m_pStatusBar;      //   allocated in the cMazeDbg ctor
    cMaze          * m_pMaze;           // child window holding maze
    cEditBox       * m_pEditBox;        // child window for printf() statements

So the cMazeDbg::MoveMouse( ) function is simply deferring its work to the cMaze::MoveMouse( ) function. But there is one difference. The passed parameter to the cMazeDbg::MoveMouse( ) function is declared as having the data type UINT while the passed parameter to the cMaze::MoveMouse( ) function is declared as being an enum (enumerated) data type. We have not seen an enum previously.

It is quite common in C/C++ programs to need to employ a short list of integers to represent a short list of possibilities. For example, the cMaze class needs a way to allow callers to its MoveMouse( ) function to request a movement in one of four possible directions. So the cMaze class declares the following enum:

class cMaze : public cBaseWindow
{
public:
    enum Direction { eLeft, eForward, eRight, eRearward };
                     // these directions are from the point of view of the mouse

You can find this exact code in the cMaze.h header file (found in the CppIde\include folder). The statement:

    enum Direction { eLeft, eForward, eRight, eRearward };

defines a new data type known as "Direction" that can have the 4 possible values eLeft, eForward, eRight, and eRearward. The e prefix is just a convention that reminds us that these values relate to an enum. The compiler assigns integers to these possibilities in the order they are listed and hence:

   eLeft     = 0
   eForward  = 1
   eRight    = 2
   eRearward = 3

In my original version of WinMain( ) I employed the integer values 0, 1, 2, and 3 in my calls to cMazeDbg::MoveMouse( ). But it would be easy to make a typing mistake and specify a wrong value. For that matter, who can remember what the value 2 signifies? So it is better to employ the actual enum values. In the new version of WinMain( ) in the MazeSolver2 project you can observe calls such as:

   if ( ! ( jWalls & FRONT_WALL ) ) // check for wall in front
   {
      maze.MoveMouse( cMaze::eForward );
   }

This not only reads better but also is safer since the compiler will complain if we use an illegal value.

I wanted to wait to introduce the enum data type and hence I had to write the original version of WinMain( ) without it. This meant that in my implementation of cMazeDbg::MoveMouse( ) I needed to convert between a UINT and a Direction enum. This is accomplished using a cast which is an instruction to the compiler to treat a variable as having a new data type. To cast a variable you place the new data type surrounded by parentheses in front of the variable's name. You can observe this cast in the implementation of cMazeDbg::MoveMouse( ) function shown below:

BOOL cMazeDbg::MoveMouse( UINT uiDirection )
{
    return m_pMaze->MoveMouse( (cMaze::Direction)uiDirection );
}                              |                |
                                ----------------
                                 here's the cast

In this situation I am required to cast the uiDirection parameter, which has the data type UINT, or else the compiler won't allow me to pass it along to the cMaze::MoveMouse( ) function which takes a different data type. This is a good thing: the compiler watches to make sure I supply the correct data type for all passed parameters. But in that rare situation where you need to say, "It's all right compiler, I know what I am doing" the cast is how you get the job done. You should employ casts as rarely as possible because you are basically asking the compiler to momentarily turn its head.

If you are paying attention, you will have noticed another new feature in the cMazeDbg::MoveMouse( ) function shown above. Previously I have said that when you need to make a member function call on an instance of a class, you combine the variable name with the function name using the dot operator. Hence you would expect to see:

    return m_pMaze.MoveMouse( (cMaze::Direction)uiDirection );

but instead you see:

    return m_pMaze->MoveMouse( (cMaze::Direction)uiDirection );

We have to use the new notation -> because the variable m_pMaze is a pointer rather than a conventional instance variable. The notation -> is called the arrow operator. It is not hard to decide when to use the dot operator and when to use the arrow operator. If the declaration for the instance variable did not include an asterisk then that variable is not a pointer so you would call member functions on that variable using the dot operator. If the declaration does include an asterisk then the variable is a pointer and hence you will need to use the arrow operator. If you get this wrong, the compiler will tell you. It is one more reason I like to use Hungarian notation since that little p always reminds me which of my variables are pointers and which are not.

We will learn more about pointers in an upcoming section but for now let me just point out that the arrow operator is really just another example of what is called syntactic sugar, meaning a convenience notation. You could always avoid using the -> notation if instead you first dereference the pointer using the dereference operator and then employed the dot operator. That is, you could replace:

    return m_pMaze->MoveMouse( (cMaze::Direction)uiDirection );

with:

    return (*m_pMaze).MoveMouse( (cMaze::Direction)uiDirection );

The parentheses seen in the last statement are critical. The following:

(*foo).bar()

is equivalent to:

foo->bar()

while:

*foo.bar()

is an error. This is because of the precedence of the operators.

The term "precedence" describes how the compiler will interpret ambiguous expressions such as 4*5+3. Are you intending this to mean (4*5)+3 or 4*(5+3)? The compiler will interpret 4*5+3 to mean (4*5)+3 because multiplication has higher precedence than addition. Every C/C++ operator is assigned a precedence as shown in the table below. The precedence drops as you move down the table. Operators on the same row of the table have identical precedence. To override the precedence rules you merely need to employ parentheses to force a different interpretation for an expression. This is why we need the parentheses to make (*foo).bar( ) equivalent to foo->bar( ).

Operator Precedence | Associativity
( ) [] -> . | left to right
! ~ ++ -- + - * & (cast) sizeof | right to left
* / % | left to right
+ - | left to right
<< >> | left to right
< <= > >= | left to right
== != | left to right
& | left to right
^ | left to right
| | left to right
&& | left to right
|| | left to right
?: | right to left
= += -= *= /= %= &= ^= |= <<= >>= | right to left
, | left to right

The associativity rules disambiguate (don't you love that word?) between operators of the same precedence. For example, the expression 6*4/3 will be evaluated as (6*4)/3 rather than 6*(4/3) because the multiplication and division operators have equal precedence but the associativity rule says to evaluate the expression from the left to the right. Now I know your math teacher has claimed that you get the same answer no matter which way you do the math but he/she obviously hadn't been exposed to integer math. The integer expression 4/3 is equal to 1 because there is no way to express the fractional remainder using integers. So, on a computer doing integer math, the expression (6*4)/3 equals 8 while the expression 6*(4/3) equals 6. To ask the computer to employ floating point math to compute this result you merely need to make sure at least one of the operators is a floating point number. A decimal point in a number makes it a floating point number. So 6*(4.0/3) is evaluated as 6.0 * (4.0 / 3.0) = 8.0 As long as one of the operands in an expression is a floating point number, the compiler promotes all of the operands to floats, as we learned here.

Before we leave the MazeSolver2 program, you might enjoy removing the call to the Sleep( ) function found inside the while(true) loop in WinMain( ). The only purpose of this statement is to slow things down enough that we can watch the mouse moving. Without it you will observe the true speed of your computer.


( 36 )

Arrays

Your head might be hurting about now since I had to throw a lot at you to introduce Windows programs. They are complex little buggers. I had been programming for a few decades before I learned about Windows programs and I was absolutely dumbfounded by the amount of new stuff I had to learn. I came to the conclusion that the reason that Microsoft came to dominate the word processor, spreadsheet, business presentation, etc. markets during the Windows era when they hadn't dominated these markets during the earlier MS-DOS era was because they had managed to create something so complex (the Windows operating system) that only they could deal with it. If you have ever used some of these Microsoft products (Word, Excel, PowerPoint, etc.) you'll undoubtedly be aware that even Microsoft has trouble producing bug-free Windows programs due to this very same complexity.

The good news is that all of this new infrastructure required to accomplish a Windows program stays pretty much the same from program to program. So as I walk you through a few more examples you will get more comfortable because you will be seeing the same infrastructure over and over again. And what's even better is that my CSL class framework (i.e., cToolBar, cStatusBar, cMaze, etc.) hides as much of this complexity as it possibly can.

So let's dive into another Windows program. Whereas the cMazeDbg class created a top-level window that offered both a maze and a text pane, we are now going to use the cSignboardDbg class that creates a top-level window that offers both a scrolling electronic signboard and a text pane. The text pane will again be used for whatever printf( ) statements we desire. And we will scroll banner type signs across the signboard (some people call these signs "reader boards").

At this time load the Signboard project. You will observe that this project consists of the 5 files: AppMain.cpp, cSignboardDbg.cpp, cSignboardDbg.h, cSignboardDbg.rc, and cSignboardDbgResource.h. Here is the condensed form of the declaration for the cSignboardDbg class that can be found in the cSignboardDbg.h header file:

class cSignboardDbg : public cBaseWindow
{
public:
    cSignboardDbg();             // constructor
    virtual ~cSignboardDbg();    // destructor

    // Interface
    BOOL           Create();
    BOOL           DefineColumn( UINT uiColumnIndex, UINT uiColumnData );
                                               // uiColumnIndex = 0 is the
                                               //   left-most column
    BOOL           RotateLeft( UINT uiColumnData );
                                               // passed param describes new
                                               //   right-most column
    BOOL           Erase();
    BOOL CDECL     printf( char * pszFormat, ... );

private:
    BOOL           Destroy();
    void           OnPaint();
    void           OnSize( UINT nType,
                           UINT uiClientWidth, UINT uiClientHeight );

    static LRESULT CALLBACK MainWndProc( HWND hWnd, UINT msg,
                                         WPARAM wParam, LPARAM lParam );
    // Child windows.
    cToolBar       * m_pToolBar;        // each of these instances is
    cStatusBar     * m_pStatusBar;      //   allocated in the cSignboardDbg
    cPaintGrid     * m_pPaintGrid;      //   constructor
    cEditBox       * m_pEditBox;        // child window for printf() statements
};

We see again that the cSignboardDbg class inherits from the cBaseWindow class. Every time I do this I save myself some typing (and every time I avoid typing I avoid typing mistakes). We also see that the public interface of the cSignboardDbg class consists of the following 5 functions:

   BOOL           Create();
   BOOL           DefineColumn( UINT uiColumnIndex, UINT uiColumnData );
   BOOL           RotateLeft( UINT uiColumnData );
   BOOL           Erase();
   BOOL CDECL     printf( char * pszFormat, ... );

These are the functions that will be called by whatever code creates instances of this class. Hence you can expect to see these functions called in the WinMain( ) function found in the AppMain.cpp file.

The private portion of this class consists of some helper functions and some state variables. The OnPaint( ) helper function is called by the WndProc( ) each time a WM_PAINT message is received from the operating system. The OnSize( ) helper function is called each time a WM_SIZE message is received from the operating system. These are private functions because they do not need to be called from code that merely employs an instance of the cSignboardDbg class, instead they are only needed inside the implementation of the cSignboardDbg class.

The Create( ) member function creates the top-level window. Near the top of the WinMain( ) function you can find a call to this member function just after we create an instance of the cSignboardDbg class:

   cSignboardDbg    signboard;
   signboard.Create();

The code in WinMain( ) then makes one call to cSignboardDbg::printf( ) and 16 calls to the cSignboardDbg::RotateLeft( ) member function.

We have seen this signboard before in the 8051 microprocessor section of this curriculum. As was the case then, we have available a font that employs 8 columns to produce each letter of the alphabet. So 16 calls to RotateLeft( ) is sufficient to scroll the two letters in the message "HI" onto the signboard. The WinMain( ) function then enters the cWinApp::Run( ) function which processes all the messages that the Windows operating system decides to send to our window.

Returning to the declaration of the cSignboardDbg class in the cSignboardDbg.h file, we can see the following private data members:

private:
    // Child windows.
    cToolBar       * m_pToolBar;        // each of these instances is
    cStatusBar     * m_pStatusBar;      //   allocated in the cSignboardDbg
    cPaintGrid     * m_pPaintGrid;      //   constructor
    cEditBox       * m_pEditBox;        // child window for printf() statements

Because the cSignboardDbg class has a data member which is an instance of the cToolBar class, it can offer a toolbar on its top-level window (it doesn't happen to do this, but the infrastructure is there, ready to go). And because the cSignboardDbg class has a data member which is an instance of the cEditBox class, it can offer a text pane for printf( ) messages. Are you starting to get the feel for my CSL class framework?

The code in the cSignboardDbg.cpp file is pretty complex, but when you have had more experience you should compare it to the code in the cMazeDbg.cpp file. You will discover that these two files are amazingly similar. The reason they can be so similar is that the complexity resides in the classes such as cToolBar, cStatusBar, etc. that implement the child windows. The ability of the C++ language to encapsulate complex functionality allows you, the programmer, to think conceptually on a higher level. You just type statements to create an instance of the cToolBar class and, presto, it's there. Believe me, it's not this easy when you write your program to directly employ the low-level functions provided in Microsoft's WIN32 API.

The reason I present you with this new Signboard application is so that we can learn more about arrays which are nothing more than collections of identical elements stored consecutively in memory. As a simple example, if we have a need for 5 integers then we can define an array to hold them using the statement shown below:

   int   aiMyArray[5];

This statement alerts the compiler that it should set aside 5 integers worth of consecutive data memory locations to store the array called aiMyArray. The Hungarian prefix ai indicates "array of ints".

Once we have defined the aiMyArray array, we can legally access its contents using square brackets as seen in statements such as:

   int   iResult = aiMyArray[0] * aiMyArray[4];

However this code probably has a bug because I am employing something I haven't initialized. You can initialize the 5 array entries individually:

   aiMyArray[0] = 10;
   aiMyArray[1] = 11;
   aiMyArray[2] = 12;
   aiMyArray[3] = 13;
   aiMyArray[4] = 14;

or -- as part of the definition statement -- you can initialize the array contents en masse:

   int   aiMyArray[5] = { 10, 11, 12, 13, 14 };

Note that curly braces must enclose the list of initial values. When you supply a list of initial values you can omit the number inside the square brackets as the compiler is smart enough to figure it out for you:

   int   aiMyArray[] = { 10, 11, 12, 13, 14 };

Remember that the index used to access the contents of any array begins with the value 0. That is, when the aiMyArray is declared as aiMyArray[5], then the 5 ints are stored at aiMyArray[0] through aiMyArray[4].

Look at the very top of the AppMain.cpp file and you will see the mother of all arrays, something called FontData which holds bytes. These bytes are the column values we need to issue to the signboard in order to form the image of alphabet letters. [If you want to see how these values were derived, refer back to the on-line help documentation for the 8051 simulator.] You can observe that I initialize the FontData array en masse with 8 * 27 = 216 bytes (27 because I include the upper case letters and the space character). Remember that the C/C++ compiler ignores white space and hence I can use fancy formatting to make it more obvious where one letter ends and the next begins.

Well it's time to talk about scope again. Whenever you define a variable inside a block of code, that variable ceases to exist when you leave that block. As an example consider the following:

int  main()
{
   int  i = 0;

   if ( i > 2 )
   {
      int  j = i + 1;
   }
}

The integer named i was defined inside the block that is the body of the main( ) function. Therefore the integer named i remains alive and in scope all during the execution of the main( ) function. There is no place inside the main( ) function where we could not legally access the i variable. But the integer named j comes into existence only when we enter the block that is the body of the if statement. The integer named j goes out of scope and its memory is re-used for something else when execution leaves the body of the if statement. You will get a compiler error if you attempt to access the j variable anywhere except within the body of the if statement.

It is possible for a variable to remain alive even though it has gone out of scope (become inaccessible). One way this can be accomplished is using the static keyword.

Scan the AppMain.cpp file to determine in what block the FontData array is defined. Confused? You should be, because it is not defined in any block. I have said that the C and C++ languages require that all executable code be placed in functions. But I never said that all variables had to be defined in functions.

Variables (such as FontData) that are not defined within functions are called global variables because they are alive throughout the program's execution. Furthermore, a global variable defined in the AppMain.cpp file is in scope anywhere within AppMain.cpp. If you want a global to also be in scope (that is, accessable) in some other file you only need to employ the extern keyword in that other file to add a declaration for that variable. Note this terminology: without the extern keyword the statement defines a variable, with the extern keyword it merely declares a variable. A definition allocates memory while a declaration connects a name with memory that exists due to a definition somewhere else.

Programming text books universally condemn the use of globals because it is actually bad to have things in scope in too many places, as this just invites unauthorized access. Think back to the poor LaserJet engineer responsible for maintaining the functions that move paper through the printer if all the other HP engineers can muck around with his variables.

As the Signboard application is currently written, I don't need for the FontData array to have global scope. I could move its definition into the WinMain( ) function and everything would still work. But I will soon give you an assignment that will be simplified by having the FontData array have global scope. And then I will show you how to avoid this global.

When you look at the code in WinMain( ) that calls the cSignboardDbg::RotateLeft( ) function, you will observe statements such as:

   signboard.RotateLeft( FontData[8*8 + 1] );

Notice that the portion of this statement "8*8 + 1" only involves constants. The compiler is smart enough to spot this, and will perform the math at compile time, thus speeding up the execution of the program at run time. This means that what will really get compiled is the following statement:

   signboard.RotateLeft( FontData[65] );

I have written this code as "8*8 + 1" so you can figure out that its purpose is to produce the second column of the ninth letter of the alphabet, that letter being "I". Remember that arrays begin with the index value of 0 and hence FontData[0] holds the first column of the first letter of the alphabet.

This code in WinMain( ) is plain old ugly, and I want you to fix it. Your assignment is to invent a new function called PaintLetter( ) which gets passed a zero-based index into the alphabet and which then draws all 8 columns of that letter. That is, with your new function I want the code in WinMain( ) to be simplified down to:

   PaintLetter( 'H' );
   PaintLetter( 'I' );

These examples of calls to the new PaintLetter( ) function should convince you that PaintLetter( ) is NOT going to be a member function of the cSignboardDbg class. This function doesn't belong in the cSignboardDbg class because a signboard is a general device that can draw any image while the PaintLetter( ) function only draws letters consisting of 8 columns.

I would suggest you use the following signature for the new PaintLetter( ) function:

void  PaintLetter( char chLetter )

I want you to write this PaintLetter( ) function. It is probably easiest to place this new function just before the WinMain( ) function (in the AppMain.cpp file).

The problem you will have in writing the new PaintLetter( ) function is that you will need access to FontData and to the cSignboardDbg instance. The former is available because FontData is a global but the latter is not available because the variable named "signboard" is only in scope within the WinMain( ) function. For your initial solution, you may move the definition of the signboard variable out of WinMain( ) and place it outside of all functions, thereby converting this to a global variable. I would recommend you locate the definition of the signboard variable just after the FontData array and just before the new PaintLetter( ) function. With this change you should be able to write the PaintLetter( ) function.

At this time open the Signboard2 project. You will see that the WinMain( ) function calls the new PaintLetter( ) function but the code doesn't compile because no one has yet written the PaintLetter( ) function. So get busy! As always, my solution is available in the Solutions folder.

When you get around to looking at my solution I want you to find where I provide a function prototype for the PaintLetter( ) function. This is going to keep you busy because there ain't no such thing. But I have said that the compiler must see the signature of any function before you employ it. The answer to this conundrum is the fact that I have placed the implementation of the PaintLetter( ) function ahead of all calls to the PaintLetter( ) function. Since the compiler has seen the implementation, it certainly has seen the signature.

You will also note that my solution has the following code inside the PaintLetter( ) function:

   if ( ! g_pWinApp->PumpMessages() )
      return;

This is some more of the infrastructure provided by my CSL framework. Unless we pump (process) the messages sent to use by the Windows operating system, our program will not be able to receive the notifications intended for it. This would prevent it from repainting when necessary and prevent it from closing when the operator clicked on the Close button, among other deficiencies.

In the next section we will next learn how to avoid the use of the global variables seen in the Signboard2 project.


( 37 )

Pointers

By now you should have finished the PaintLetter( ) function needed for the Signboard2 example program. Inside this PaintLetter( ) function you needed access to the FontData array and the cSignboardDbg instance variable. The latter is necessary so we can make calls on this instance to the RotateLeft( ) function of the cSignboardDbg class. I allowed you to employ global variables to accomplish the Signboard2 project, but that's not going to be how we leave things. Excessive use of globals has caused a lot of trouble out in industry so I want to show you how to avoid globals.

The trick is rather simple. Anything that the PaintLetter( ) function needs should be passed to the PaintLetter( ) function. So whereas before the function signature for the PaintLetter( ) function was:

void  PaintLetter( char chLetter )

we now want it to be:

void  PaintLetter( char chLetter, BYTE * pjFontData, cSignboardDbg * pSignboard )

With the 2 new passed parameters we can completely avoid using any globals in the program. "And that's a good thing", as one corrupt celebrity is fond of saying. The Hungarian prefix on the second passed parameter is pj which means "pointer to BYTE". The Hungarian prefix on the third passed param is p meaning a pointer of some sort (there are no Hungarian prefix letters for user-defined types such as cSignboardDbg).

The question that should be on your minds is, "Why did he decide to employ pointers for the 2 new passed parameters?" Good question! The alternative would be a function signature such as:

void  PaintLetterSlowly( char chLetter, BYTE ajFontData, cSignboardDbg Signboard )

I have named this alternative the PaintLetterSlowly( ) function for reasons that will become clear shortly. In this function signature we intend for the second passed param to be an array of bytes (hence the aj Hungarian prefix) and the third param to be an instance of the cSignboardDbg class. There is no problem with this third param but the compiler will interpret the second passed parameter as a single byte. You see, the compiler pays no attention to Hungarian prefixes, it cares only about the declaration of the data type which in this case is "BYTE" which means just 1 byte. The only way you can pass an array to a function is by using a pointer. Why is the compiler designed this way? I'll answer that by discussing the third passed parameter.

In the function prototype for the PaintLetterSlowly( ) function seen above, we will pass a copy of a cSignboardDbg instance via the third passed param. That's what we want, right? Well, it is what we want. It's just not the most efficient means of accomplishing what we want. That's why I have named it the PaintLetterSlowly( ) function.

The problem hinges on the fact that the C and C++ languages use call by value when they pass parameters to functions. This means that every time we make a call to the PaintLetterSlowly( ) function the computer must copy an entire instance of a cSignboardDbg class. How many bytes is that? It would be the sum of the sizes of all the data variables found in the cSignboardDbg class declaration. For some classes with a lot of state variables, this will be a large number of bytes that need to be copied. And this copying must occur prior to EVERY call to the PaintLetterSlowly( ) function.

There is a faster solution and it is one of the main reasons that pointers were invented. When we call the function whose signature is:

void  PaintLetter( char chLetter, BYTE * pjFontData, cSignboardDbg * pSignboard )

the computer can accomplish the second passed param by copying a byte address (because that is what a pointer is). And the computer can accomplish the third passed param by copying another pointer. This one will be a pointer to a cSignboardDbg instance but all pointers have the same size (4 bytes on an x86 microprocessor). So a tremendous amount of overhead is avoided during every call to the PaintLetter( ) function as compared to the PaintLetterSlowly( ) function.

And that is why the designers of the C/C++ language decided that they would not even offer a mechanism for copying an entire array as a passed parameter to a function. Instead you must use a pointer to pass the first address of the array. If the function needs to know the length of the array then it will probably need another passed param to carry this info. In the case of the PaintLetter( ) function that we will write, we will choose to just embed the knowledge that the FontData array holds 8*27 = 216 bytes within the PaintLetter( ) function and hence we can avoid this additional passed parameter.

Now that you know the goal, you just need to learn the implementation details of using pointers as passed parameters. It's not too tough. The function signature must declare the data types of all passed params so if you want one of these to be a pointer then you just need to include an asterisk. When you call that function, instead of supplying a variable instance for the passed param, you supply the address of that variable, since pointers hold addresses. And the final change you need to make is that within the implementation of the function, you employ the arrow operator rather than the dot operator. You will be able to observe all of these changes in my code.

At this time load the Signboard3 project and study the code in the AppMain.cpp file. Find the definition of the FontData array. Since this definition has been moved inside the WinMain( ) function, it is only in scope in the WinMain( ) function. No other functions can access this variable, unless they have been passed the address of this array. Once you have an address, that object is definitely in scope to you. Each time we call the PaintLetter( ) function we supply the address of the FontData array and the address of our cSignboardDbg instance.

Find the locations where we call the PaintLetter( ) function. You will see the use of the address of operator & to acquire the address of the signboard variable to use for the third passed param to PaintLetter( ). But it probably doesn't look like we are supplying the address of the FontData array for the second passed param because you don't see the ampersand used there. What you see instead is:

   PaintLetter( 'H', FontData, &signboard );

This is basically a special case of the C/C++ language. The FontData variable is an array, and the name of any array variable can be used for the address of that array. If you really want to see the ampersand used in your call then you are allowed to type:

   PaintLetter( 'H', &FontData[0], &signboard );

The notation "&FontData[0]" indicates the address of the first member of the array, which is obviously the starting address of the array. But since this notation involves more typing, you rarely see it used.

You can compile and run the Signboard3 example program just to convince yourself that it is healthy. Note again that we have completely eliminated the use of global variables.


( 38 )

Static Data Members

We have already encountered the static keyword. In the C language the static keyword is used to prevent a local variable from being an automatic variable.

The C++ language added some new uses for the static keyword. Unfortunately, the word "static" is not very descriptive of these new uses, so I wish a different keyword had been selected.

In the C++ language you can request static data members and static member functions. Let's learn about static data members first.

Our prior Signboard3 example program provides a perfect lead-in to the concept of static data members. You will remember that all flavors of the Signboard example program have employed a FontData variable which is an array of bytes that describe the columns of lights needed to form the shape of each letter of the alphabet. In the Signboard and Signboard2 example programs the FontData array was a global variable. The purpose of the Signboard3 example program was to illustrate how to eliminate the global variables such as FontData.

But we can still do better. The FontData array is data that is only important to the cSignboardDbg class and hence it makes sense that this data should be encapsulated inside the class. The only such possibility that I have shown you is to make the FontData array a class data member. This would work but would also mean that the 216 bytes (27 letters times 8 columns per letter) would reside in every instance of the cSignboardDbg class. If every instance of the cSignboardDbg class could offer a unique font then it would make sense to store these 216 bytes in every class instance. But I have only taken the time to develop a single font and hence there is really no need to keep repeating these 216 bytes in every instance. There ought to be some way that all of the instances of a class could share some data.

And, of course, there is. The technique is called a static data member. We will come back and make the FontData array a static data member of the cSignboardDbg class, but let's start with an even easier example.

At this time open and study the StaticDataMembers example program. You will observe the static keyword used in the declaration of the cTwoCounters class found in the cTwoCounters.h header file. The only purpose of this cTwoCounters class is to demonstrate the difference between a normal data member (which is unique to each class instance) and a static data member (which is shared among all instances of the class). We initialize the normal ("non-static") data member in the class constructor just like we have always done. But the static data member cannot be initialized in the class constructor because it must have a life separate from any particular instance of the class. Therefore you find the definition and initialization of this static variable on the following line of the cTwoCounters.cpp file:

int   cTwoCounters::m_iStaticCounter = 0;

If it were not for the appearance of a class name and the scope resolution operator (::) this statement would define a global variable because it occurs outside of all functions. But this is not a global variable: it is a class resource which is only in scope within cTwoCounters member functions.

Now compile and execute the StaticDataMembers example program. You will observe from the printf( ) statements that the static counter advances twice as fast as the non-static counter. This occurs because there are two instances of the cTwoCounters class and all the instances are incrementing the exact same data memory location.

To further clarify this concept, the program uses the sizeof( ) function to print the number of bytes in every instance of the cTwoCounters class. This number of bytes is 4, because every instance of a class gets a unique copy of all the non-static data members of that class. In this case there is only 1 non-static data member and it is an int which is 4 bytes. The static data members are shared among all instances and hence are not located within any instance. This is why sizeof( cTwoCounters ) does not return the value 8.

With this introduction to static data members you should be able to understand the Signboard 4 example program. In the cSignboardDbg.h file you will find the declaration of the m_jFontData array as a static data member. Because this is a static data member, the 217 bytes in this array are not increasing the size of each instance of the cSignboardDbg class. Then in the cSignboardDbg.cpp file you will be able to find where the m_jFontData array is defined and initialized. And finally, look in the cSignboardDbg::PaintLetter( ) function to see where the m_jFontData static data member is being used within a member function of the cSignboardDbg class.

In the earlier Signboard3 example program the PaintLetter( ) function was a stand-alone function located in the AppMain.cpp file. At that time this PaintLetter( ) function had the rather ugly function signature:

void  PaintLetter( char chLetter, BYTE * pjFontData, cSignboardDbg * pSignboard );

But in the new Signboard4 example program the PaintLetter( ) function has been converted to a class member function and this has simplified its function signature down to:

void  cSignboardDbg::PaintLetter( char chLetter );

At this time compile and execute the Signboard4 example program. You will notice that no characters appear on the signboard. The program is waiting for you to type on the keyboard. As long as you stick to upper or lower case alphabet letters (and the space bar) the message you type will be scrolled onto the signboard.

But I bet you can't figure out why the letters are appearing. That is, what is making the connection between keystrokes on the keyboard and the call to the cSignboardDbg::PaintLetter( ) function?

Well this little bit of magic is just the message passing nature of the Windows operating system. Whenever the operator types on the keyboard, the Windows operating system generates a WM_CHAR message to announce each alphabetic keystroke. No matter how many applications are currently running on the computer, only one of these programs has keyboard focus, which means that the operating system delivers the WM_CHAR messages to that program. What does the program do in response to the arrival of the WM_CHAR messages? That is up to the designer of the program (that is, you).

In order to write a Windows (as opposed to a console-mode) program you must participate in this unending stream of messages. And since you can't predict the order of these messages, you must be willing to respond to whatever messages arrive, in the order they arrive. This requires you to abandon the linear organization that you typically see in a console-mode program. Our earlier Signboard3 program still used a linear organization, as evidenced by the following code from the WinMain( ) function found in the AppMain.cpp file:

    cSignboardDbg    signboard;

    signboard.Create();

    signboard.printf( "Starting program...\n" );

    PaintLetter( 'H', FontData, &signboard );
    PaintLetter( 'O', FontData, &signboard );
    PaintLetter( 'W', FontData, &signboard );
    PaintLetter( 'D', FontData, &signboard );
    PaintLetter( 'Y', FontData, &signboard );

In this code snippet we create an instance of our class, call a few class member functions and standalone functions in a specific order, and then close up shop.

True Windows programs cannot be written in this fashion. Take a look now at the same section of the WinMain( ) function of our new Signboard4 example program:

    cSignboardDbg    signboard;

    signboard.Create();

    signboard.printf( "Type any alphabet characters...\n" );

    // We now enter the message loop.

    return theApp.Run();

    // cWinApp::Run() does not return until a WM_QUIT message is received.

The first statement shown above creates an instance of the cSignboardDbg class. Once we have such an instance we can use it to call the member functions of the cSignboardDbg class. You can observe in the code shown above that we call the cSignboardDbg::Create( ) and cSignboardDbg::printf( ) functions. We then make a call to the cWinApp::Run( ) function which continually shuttles the various Windows messages issued by the operating system to the windows owned by this application. When the cWinApp::Run( ) function notices the WM_QUIT message it returns to its caller and hence WinMain( ) returns and hence our program concludes.

You can generate the WM_QUIT message by clicking on the X shaped icon found in the upper right-hand corner of the program's title bar.

When you type on the keyboard the Windows operating system issues the WM_CHAR message to our application. As long as the Signboard4 program has keyboard focus these WM_CHAR messages will be delivered to the cSignboardDbg::MainWndProc( ) function. Windows procedures ("wind procks") are those functions that exist to receive the flow of messages coming from the operating system. They are typically nothing but a huge switch statement that branches according to the particular message that has just been received. Shown below is a condensed synopsis of the relevant section of the cSignboardDbg::MainWndProc( ) function:

LRESULT CALLBACK cSignboardDbg::MainWndProc( HWND   hWndMain,
                                             UINT   msg,
                                             WPARAM wParam,
                                             LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <do some stuff>
            return 0;
        }
        case WM_PAINT:
        {
            <do some stuff>
            return 0;
        }
        case WM_CHAR:
        {
            char    ch = wParam;

            pThis->PaintLetter( ch );

            break;
        }
        ...
    }
}

You can see that when the msg passed param to the cSignboardDbg::MainWndProc( ) function holds the value WM_CHAR, the wParam passed param holds the actual keystroke. Therefore we employ this keystroke in a call to our PaintLetter( ) function.

But the call to the PaintLetter( ) function undoubtedly looks quite screwy. What is this "pThis" business? Unfortunately, it is a messy business that warrants its own page.


( 39 )

Static Member Functions

On the previous page we studied one of the two new uses for the static keyword that were introduced in the C++ language. We are now ready to learn about the other new use: static member functions.

Static member functions are not as useful as static data members. In fact, I wouldn't bother to demonstrate them at all in an introductory C++ course such as this except for an unfortunate historical accident. You see, Microsoft selected the C language as the native language of their WIN32 API, which is the set of functions that you must employ to write a Windows program. I suspect Microsoft would have based their WIN32 technology upon C++ if only that language had been a bit more mature when these decisions were being made. Microsoft now employs C++ to write all of their killer apps such as Word, Excel, PowerPoint, etc. so we know that they have to struggle with the same issue that I am about to introduce, which is that the design of the WIN32 API presents some difficulties for a Windows program written in the C++ language. The problem has to do with something called the "this" pointer, so I better start there.

I have explained that every instance of any C++ class has both state and behavior. The state is given by the values of the class data members, which are unique to each instance of the class. And the behavior comes from the class member functions, which are shared by all instances of the class. Since the member functions are shared, there has to be a mechanism that provides a link to the particular data members that should be manipulated during a particular execution of a member function. You can probably guess that this linkage is accomplished with a pointer. It is, and the pointer is called the this pointer.

As a simple example, consider the following class:

class  cExample
{
public:
     cExample( int i ) { m_iValue = i; };   // the constructor
     ~cExample() {};                        // the destructor
     void Print() { printf( "m_iValue = %d\n", m_iValue ); }
                                            // a member function
private:
     int  m_iValue;
};

To keep this code simple I have inlined the constructor, destructor, and the member function.

Now consider a main( ) function that will create 2 instances of this class:

int    main()
{
    cExample   First( 17 );
    cExample   Second( -5 );

    First.Print();
    Second.Print();

    return 0;
}

Via a passed parameter to the class constructor, this code initializes the class data member of the 2 instances. When you execute this program you get the expected results:

m_iValue = 17
m_iValue = -5

Now suppose that we want to display the memory location of these cExample instances and we want to do this both from within main( ) and from within cExample::Print( ). We can easily accomplish the first goal via the code shown below:

int    main()
{
    cExample   First( 17 );
    cExample   Second( -5 );

    printf( "First  instance lives at 0x%x\n", &First );
    printf( "Second instance lives at 0x%x\n", &Second );

    First.Print();
    Second.Print();

    return 0;
}

In the call to the printf( ) function seen above I have employed the %x format specifier in order to print a value in hex (32 bit addresses are most easily displayed in hex). You also see the address of operator & used to obtain the address of the First and Second variables.

We are left with the hard job of printing these same addresses from inside the Print( ) member function. This goal is not just hard, it is impossible given what I have taught you about the C++ language. There is no variable name available for use with the address of operator. Remember that all member functions are shared by all instances of their class. Hence there is nothing within the Print( ) member function that is unique to a particular instance.

But wait, there actually is something unique to the instance used to call the member function. This is the this pointer, which you don't have to declare because it rather magically exists. You can employ the this pointer inside the Print( ) member function as shown below:

     void Print() { printf( "this = 0x%x\n", this );
                    printf( "m_iValue = %d\n", m_iValue ); }

When you execute this new code you will observe something like:

First  instance lives at 0x74fdd0
Second instance lives at 0x74fdc0
this = 0x74fdd0
m_iValue = 17
this = 0x74fdc0
m_iValue = -5

The reason that you don't need to declare the this pointer is that it is actually a passed parameter. Any time a member function is called, there is a hidden first passed parameter which is the this pointer. Hence the this pointer is always in scope within any member function. It is also available in the constructor and destructor.

The presence of the hidden this pointer explains a mystery that perhaps you have wondered about. Given that the member functions are shared by all instances and the data members are unique, how does a member function know which data member it should affect. That is, in the following statement taken from the cExample::Print( ) function, how does the member function know which of the possibly many m_iValue data members to employ? Remember that there will be as many m_iValue data members in memory as there are cExample instances.

     void Print() { printf( "m_iValue = %d\n", m_iValue ); }

The answer is the hidden this pointer. Even though you type the function as shown above, the compiler understands you to mean:

     void Print() { printf( "m_iValue = %d\n", this->m_iValue ); }

It is acceptable to type the function in this manner, but few programmers do since it is just more keystrokes. But the key point is that the shared member functions are connected to the particular member data via a hidden this pointer.

Now I just finished saying that the this pointer is always in scope within any member function. Well, almost any member function. The this pointer is the first passed param to any non-static member function. When you declare a member function as static you are saying you don't want to receive the hidden this pointer. You almost never want to do this because without the hidden this pointer you can't access any of the non-static data members of the class. Static member functions can only access static data members of the class. We learned about these on the last page: static data members are those data members that don't reside in a particular instance of the class but rather are shared among all instances. Because static data members do not reside in a particular instance, they can be located without the this pointer. But nothing else can be located and hence static member functions are not too useful.

But thank god they do exist, or else we wouldn't be able to place a WndProc( ) function inside a class. In my CSL Framework I decided to encapsulate everything associated with the toolbar in a cToolBar class, and everything associated with the status bar in a cStatusBar class, etc. This meant I definitely wanted to place the WndProc( ) function that receives all of the windows messages intended for the toolbar in the cToolBar class.

But here's the rub: Microsoft defined the passed parameters to any WndProc( ) function and there is no place for a hidden this pointer. Hence a WndProc( ) function cannot be a normal class member function. Fortunately it can be a static member function.

But the rub continues: as a static member function the WndProc( ) function will not be able to access any of the non-static data members of the class. If we couldn't get around this problem the whole concept of the CSL Framework would go down in flames.

Fortunately, the designers of the C and C++ languages favored permissiveness over dictatorial rigidity. This looseness allows us to invent our own this pointer when we need one. When you hear me start to talk about permissiveness you know I am about to mention casting. The language allows you to cast a variable of one data type to a different data type. But this is dangerous because it subverts all of the type checking built into the compiler. Use a cast only when it is absolutely necessary and you are 100% confident you know what you are doing. The compiler performs many checks upon your code in order to help make sure that your program works as you intend. The cast is your way of saying, "Trust me". Make sure you mean it.

So let's bring all of these concepts together. The designer of a Windows program gets to decide how to subdivide his screen real estate into "windows". Every window is a rectangular area of pixels that has an associated WndProc( ) function that is the destination for a steady stream of messages generated by the operating system (and by the program and by all the other programs running on the computer). The design of Microsoft's WIN32 API prevents us from using a regular C++ member function for a WndProc( ) function but fortunately we can employ a static member function. And we can get around the normal limitations of static member functions (namely, that they can't access class data members) by reconstructing the hidden this pointer using a cast. Are you with me? This is pretty advanced stuff, and I don't expect you to master it at this time. I am merely -- if you can remember that far back -- explaining a particular statement we observed in the Signboard4 example program. That statement was in the cSignboardDbg class's WndProc( ) function and looked like:

    pThis->PaintLetter( ch );

The pThis pointer seen in this statement is the reconstituted this pointer. At the very top of the WndProc( ) function you can observe the following statement which uses a cast to create a pointer to a cSignboardDbg class, which is exactly what a this pointer is. We can't name our reconstituted this pointer with the identifier "this" because that is a reserved language keyword. But with a Hungarian p prefix (for pointer) we can make a nice stand-in.

    cSignboardDbg * pThis = (cSignboardDbg*) GetWindowLong( hWndMain,
                                                            GWL_USERDATA );

Perhaps you are now hoping I will explain the GetWindowLong( ) function. Sorry. This function is one of the thousands of internal details of the WIN32 API and would take me too far a field of my goal which is to teach the C++ language while using fun examples that happen to be Windows programs. When you are ready for things like the GetWindowLong( ) function then you are ready for the Petzold book.

The only reason I discuss the this pointer at all is because you will see many instances of the reconstituted pThis pointer as you look through my example programs and the code generated by the New Project Wizard. Think of it as a patch that smoothes over a disconnect between the WIN32 API and the C++ language. And note that the ONLY place you will observe me using a reconstituted pThis pointer is within a WndProc( ) function.


Peak Detect

( 40 )

The Peak Detector Example Program

As part of the Windows class framework that I distribute in my libCSL.a file, I have built a cWavePlayer class that offers a standard set of tape deck pushbuttons and a cPeakMeter class that offers a 2 channel (stereo) LED bar graph. The cWavePlayer class can play any .WAV file while providing the actual sound samples to a callback function. A callback function is like any other function except in how it is employed. You provide the callback function to some other code which then makes calls back to the function you provided, hence the name "callback".

With these 2 classes we have most of the infrastructure we need to play an arbitrary song and display its volume peaks on an LED bar graph. Any song may be employed as long as it resides in an uncompressed .WAV file on your computer. I have provided a few sample .WAV files holding fragments of songs in the following locations (assuming you accepted the default options while installing Computer Science Lab):

C:\Computer Science Lab\CppIde\Fortunate Son.wav
                  [copyright Creedence Clearwater Revival, Fantasy Records]
C:\Computer Science Lab\CppIde\Hawkeye.wav
                  [copyright The Alan Parson's project, Arista Records]
C:\Computer Science Lab\CppIde\PeakDetector\Its Only Money.wav
                  [copyright Argent, CBS Records]

You can download other .WAV files from the Internet or else rip them from your favorite music CDs. Once we have the PeakDetector program running you will just need to RIGHT click on the Play pushbutton in order to get a standard "Open File" dialog that you can use to browse to any location on your hard drive and select a particular .WAV file.

The thing I haven't provided is the logic to walk through the audio samples and determine the peaks. This is where you come in.

The cWavePlayer class and cPeakMeter class are designed to create child windows. And my cToolbar, cStatusBar, and cEditBox classes also create child windows. Therefore we need a top-level window to host all these child windows. This is the purpose of the cPeakDetectorDbg class which I provide in the cPeakDetectorDbg.cpp and cPeakDetectorDbg.h files. In case you haven't caught on to my naming convention yet, the letters "Dbg" in the class name remind me that this class includes a text editor pane (courtesy of the cEditBox class) that I can use to host the output of printf( ) debugging messages.

At this time open the Peak Detector example program. You can look through the actual code but I want to show you the simplified version of the cPeakDetectorDbg class:

class cPeakDetectorDbg : public cBaseWindow
{
public:
    cPeakDetectorDbg();           // constructor
    virtual ~cPeakDetectorDbg();  // destructor

    // Interface
    BOOL         Create();
    BOOL         LoadSong( char * szWavFileName );
    UINT         GetSamples( int * piLeftSamples, int * piRightSamples,
                             UINT  uiMaxNumSamples );
                                       // Note this is a blocking call !
    BOOL         SetLevel( UINT uiLeftLevel,
                           UINT uiRightLevel );
    BOOL CDECL   printf( char * pszFormat, ... );

private:
    BOOL         Destroy();
    void         OnPaint();
    void         OnSize( UINT nType,
                         UINT uiClientWidth, UINT uiClientHeight );

    static LRESULT CALLBACK MainWndProc( HWND hWnd, UINT msg,
                                         WPARAM wParam, LPARAM lParam );
    // Child windows.
    cToolBar     * m_pToolBar;      // each of these instances is
    cStatusBar   * m_pStatusBar;    //   allocated in the cPeakDetectorDbg
                                    //   ctor
    cPeakMeter   * m_pPeakMeter;    // child window holding LED bar graph
    cWavePlayer  * m_pWavePlayer;   // child window holding tape deck
                                    //   pushbuttons
    cEditBox     * m_pEditBox;      // child window for printf() statements

    UINT           m_nWavSamples;   // # entries in m_iLeftWavSamples[] and
                                    //   m_iRightWavSamples[]
    int            m_iLeftWavSamples[MAXNUMWAVESAMPLES];
    int            m_iRightWavSamples[MAXNUMWAVESAMPLES];
};

You should see a lot of similarities to the cMazeDbg class and the cSignboardDbg class that we have already studied. This is the advantage of my CSL class framework. The Create( ) member function opens up a top-level window which hosts the tape deck pushbuttons and the LED bar graph and the text editor pane that we can use for whatever printf( ) statements we desire.

A callback function is slightly complex because you never know when it will be called. That is beyond your control. In the present situation where we are playing a .WAV file, our callback function will be called whenever a short segment of the song finishes playing. I am not going to ask you to interact with the actual callback function. Instead you will write code that calls the GetSamples( ) member function which returns the latest group of audio samples. After you look through all of these audio samples you then call the SetLevel( ) member function to update the LED bar graph.

Each time the cPeakDetectorDbg class's callback function is called, it needs a place to hold the latest group of audio samples until you ask for them via the GetSamples( ) function. This is the purpose of the following arrays seen in the private section of the cPeakDetectorDbg class declaration:

private:
    UINT    m_nWavSamples;     // # entries in m_iLeftWavSamples[]
                               //   and m_iRightWavSamples[]
    int     m_iLeftWavSamples[MAXNUMWAVESAMPLES];
    int     m_iRightWavSamples[MAXNUMWAVESAMPLES];

In this code snippet you can observe the declarations for 2 arrays of ints, one for the left audio channel and the other for the right audio channel. These arrays are dimensioned to be able to hold lots of audio samples (thanks to the macro named MAXNUMWAVESAMPLES). But the actual number of samples residing in them is equal to the current value of the m_nWavSamples class data member (the m_ prefix indicates a class data member and the n prefix indicates "number of"). This is a pretty common situation in programs: you allocate plenty of memory and then keep track of how much you actually use.

A .WAV file using CD quality encoding employs 16 bit samples. Hence these samples could be stored as "short ints" (16 bit signed integers) since they can only range from -32768 to +32767. But the cPeakDetectorDbg class stores these audio samples in normal ints (32 bit signed integers) just for convenience. When we positive peak detect these samples, the peak can range from 0 to +32767. Hence to represent quiet on the left channel of the LED bar graph and the loudest amplitude possible on the right channel you would call:

    SetLevel( 0, 32767 );

So the real heart of the matter is the GetSamples( ) function whose function prototype is:

UINT     GetSamples( int * piLeftSamples, int * piRightSamples,
                     UINT  uiMaxNumSamples );

To call this function you must provide the beginning addresses of 2 arrays of ints. The pi Hungarian prefix indicates "pointer to array of ints". The third passed param states the dimensioned size of these arrays. Given the dimensioned size, the GetSamples( ) function can avoid overfilling these arrays (such an occurrence is called a memory overrun).

Let's now look at the code in the AppMain.cpp file. A simplification of the code you see in the WinMain( ) function is shown below:

    cPeakDetectorDbg    peak;

    peak.Create();

    int     iLeftChannelSamples[MAXNUMWAVESAMPLES];
    int     iRightChannelSamples[MAXNUMWAVESAMPLES];
    UINT    nSamples;

    while ( true )
    {
        nSamples = peak.GetSamples( iLeftChannelSamples,
                                    iRightChannelSamples,
                                    MAXNUMWAVESAMPLES );

        if ( nSamples > 0 )
        {
            UINT    i;
            int     iMaxLeftSample = 0;
            int     iMaxRightSample = 0;

            iMaxLeftSample  = 10000;  // not right
            iMaxRightSample = 20000;  // not right

            peak.SetLevel( iMaxLeftSample, iMaxRightSample );
        }
    }

You can see that we instantiate an instance (object) of the cPeakDetectorDbg class. We then call its Create( ) member function to make its window visible. We then declare 2 arrays to hold the audio samples that are going to be given to us each time we call the GetSamples( ) member function. We then enter a while(true) loop that continually calls GetSamples( ). Each time GetSamples( ) returns the song has progressed a little farther and a new group of left channel and right channel audio samples has been returned to us. It is our duty to search through these arrays, find the peaks, and then call the SetLevel( ) member function in order to update the LED bar graph. The while(true) loop then causes us to do it all over again, endlessly.

I want to point out that the GetSamples( ) function actually returns 3 different items. Because it is passed pointers for its first 2 passed parameters, it is able to return values via these passed parameters. And in this case, these returned values are arrays of numbers rather than individual numbers. Furthermore, the GetSamples( ) function returns the number of items returned in these arrays via the conventional function return value.

The actual code that you see in WinMain( ) is only a little more complex than what I have just described. The main change is that the while loop is not a while(true) loop but instead employs the test:

    while ( theApp.PumpMessages() )

We need to keep responding to the flow of Windows messages (i.e., WM_PAINT, WM_SIZE, etc.) while the song plays and this is accomplished by periodically (once per loop iteration) calling the PumpMessages( ) member function of the cWinApp class.

Hopefully this gives you enough background to fix the code that I provide in the WinMain( ) function of the AppMain.cpp file. Do not change any of the other files. The problem in the original code is that we don't search through the iLeftChannelSamples array and the iRightChannelSamples array for the positive peaks. You need to replace the following statements:

   iMaxLeftSample  = 10000;  // not right
   iMaxRightSample = 20000;  // not right

with a loop that looks through all of the samples. Specifically, you will need to examine iLeftChannelSamples[0] through iLeftChannelSamples[nSamples-1], where nSamples is the value returned by the GetSamples( ) function. And, of course, you will have to do the same for the right channel samples.

There are two traditional ways to find the maximum value in a set of samples. Algorithm #1 starts by setting iMaxLeftSample to the smallest possible value (-32768 in our case) and then compares iMaxLeftSample to each of the entries in the iLeftChannelSamples array. Each time an entry in the iLeftChannelSamples array is larger than the value in iMaxLeftSample we re-initialize iMaxLeftSample with that value. We proceed until we have looked at all of the iLeftChannelSample array. Algorithm #2 is slightly more efficient. It starts by initializing iMaxLeftSample equal to iLeftChannelSamples[0] (that is, equal to the first entry in the array). A loop then compares iMaxLeftSample to each of the values from iLeftChannelSamples[1] through iLeftChannelSamples[nSamples-1]. I don't care which algorithm you choose to implement.

You will know you got the code right when you can compile and run your program and observe that the LED bar graph matches up with the music (the LED bar graph will be very slightly delayed behind the actual music but you may not even notice this). I have provided my solution in the Solutions folder. Happy listening.

The menu offered by the PeakDetector program consists of only a single item: File/Exit. As in our previous Windows programs, this menu was not functional. But the version of the PeakDetector program that I supply in the Solutions folder does have a functional menu (try it). As part of my campaign to gradually get you comfortable with Windows messages, I want to show you how I accomplished this.

Whenever the operator selects a menu item or clicks on a toolbar button the Windows operating system sends a certain flavor of the WM_COMMAND message to the WndProc( ) function associated with that window. All WndProc( ) functions must employ the following function signature:

LRESULT CALLBACK WndProc( HWND   hWnd,
                          UINT   msg,
                          WPARAM wParam,
                          LPARAM lParam )

When a menu item has been selected the "msg" passed parameter to the WndProc( ) function will be WM_COMMAND and the flavor of WM_COMMAND will be indicated by the least significant 16 bits of the wParam passed parameter. Microsoft provides a LOWORD( ) macro that helps you access these least significant 16 bits of the 32 bit wParam.

I show below the code that I added to the WndProc( ) function in order to make the menu functional. Remember that all WndProc( ) functions are just giant switch statements that branch based upon the value of the msg passed parameter.

LRESULT CALLBACK cPeakDetectorDbg::MainWndProc( HWND   hWndMain,
                                                UINT   msg,
                                                WPARAM wParam,
                                                LPARAM lParam )
{
    switch ( msg )
    {
        case WM_COMMAND:
        {
            WORD     wNotifyCode = HIWORD( wParam ); // notification code
            WORD     wID         = LOWORD( wParam ); // menu item, control, or
                                                     //   accelerator identifier
            HWND     hWndChild   = (HWND) lParam;
            HMENU    hMenu       = GetMenu( hWndMain );

            switch ( wID )
            {
                case IDM_FILE_EXIT:
                {
                    // Upon receiving this message we send ourselves a
                    // WM_CLOSE message.  This way, the WM_CLOSE message
                    // (normally generated by the system menu) can be the
                    // single location where we prompt the user if he
                    // really wants to quit.

                    SendMessage( hWndMain, WM_CLOSE, 0, 0 );

                    return 0;
                }
                default:
                    break;

            } // end of switch (wID)

            break;
        }
        ...
    }
}

The code snippet shown above uses a nested switch statement (that is, a switch inside a switch) to watch for the occurrence of the IDM_FILE_EXIT flavor of WM_COMMAND message. IDM_FILE_EXIT is nothing but a macro name for a manifest constant such as 1102.

Where did these flavors of WM_COMMAND message come from? You invented them, you clever fellow. When you described the menu that you wanted for your program you specified the flavor of WM_COMMAND message that you wanted to be associated with each of your menu items. Shown below is the code from the .RC (Windows resource) file that specifies that the PeakDetector program should have a menu and that menu should consist of only a single item:

IDR_cPeakDetectorDbgMenu MENU DISCARDABLE
BEGIN
  POPUP "&File"
  BEGIN
    MENUITEM "E&xit",    IDM_FILE_EXIT
  END
END

This code snippet is not C or C++, it is the language of .RC files. This language was invented by Microsoft. I provide a short introduction to this language in an appendix of this on-line help document. But even without knowing the language of .RC files you can probably observe how this code snippet requests a top-level menu heading of "File" which opens up to display a single item labeled "Exit". And you should observe how we request that the IDM_FILE_EXIT flavor of WM_COMMAND message be associated with this File/Exit menu choice.


Breakout

( 41 )

The Breakout Example Program

Do you like video games? If so, you have probably seen a version of the Breakout game. Breakout looks like the Pong video game (which was the world's first video game) with the addition of a wall of bricks. The human operator controls a paddle which directs a moving ball to smash into the wall of bricks. Every time a brick is struck by the ball that brick disappears and you get more points. You lose the ball if it goes past the bottom of the window which is where your paddle is located.

At this time load the Breakout example program and compile the code. Then go ahead and run the program. A top-level window will appear and you will see a green paddle, a gray ball, and a double row of blue bricks. The ball will initially be stationary, because the game hasn't started yet. The paddle however will already respond to your control when you hit the Left Arrow and Right Arrow keys on your keyboard (in fact, the Home and End keys are also functional). You will observe that the Breakout window offers a single menu entry named "Game". Inside this menu are the choices Start, Pause, Resume, Options, and Exit. Next to each menu choice is listed an accelerator keystroke. For example, next to the Start menu choice is listed "Ctrl+S". This means that if you hold the CTRL key while simultaneously pressing the S key then you will select the Start menu choice. The advantage of accelerator keys is that you don't have to open the menu to make your selections.

You will find it beneficial while playing this game to not take your hands off the keyboard. Therefore the accelerator keys allow you to control all aspects of the game without using the mouse. Go ahead and type Ctrl+S and observe the ball starting to move.

Ooops! As things stand at the moment, the ball goes right through the bricks, the paddle, and the walls. We are going to have to fix this.

But before we get started, let's look at what is already right about the program. Practice with the Ctrl+P (pause) and Ctrl+R (resume) keystrokes so you can see that you have the ability to interrupt and then continue a game at any point. The Ctrl+S keystroke will abandon the old game and initiate a new game. Also try the Ctrl+O keystroke which opens the Options dialog which gives you an opportunity to choose the ball's velocity, to choose whether the game should make sounds, and to choose whether to employ what is called Training Mode. In Training Mode there is an invisible barrier across the bottom of the window that prevents any balls from escaping no matter how bad you are at controlling the paddle. The velocity option already works but we will have to implement the code for Training Mode and for the sounds.

Now exit the game and look at the source code in the AppMain.cpp file. You will observe the following statements:

    cBreakoutDbg    game;

    game.Create();

    game.printf( "Starting program...\n" );

    // We now enter the message loop.

    return theApp.Run();

Finally, we see what a real Windows program should look like! When we first started writing console-mode programs we employed a linear program flow. For example, in the Factorial example programs we structured the main( ) function to start by asking the operator for an operand and to then compute the factorial of that operand. We had in mind this particular start-to-finish sequence when we wrote that program. And when we started to write Windows programs we continued to employ a linear flow of program logic in the WinMain( ) function. For example, the Signboard program scrolls out a certain message and then exits.

But contrast this linear approach with how we interact with true Windows programs. Consider Microsoft's WORD word processor for example. One day you might start up WORD in order to type in a new document which you then save on the hard drive. You then exit from WORD. The next day you might start up WORD in order to retrieve that document and then print it. You then exit from WORD. The day after that you might start up WORD to view a document you received in an e-mail and had transfered to WORD via the Windows clipboard. Because you find the document hard to read, you decide to change the font before reading the document. You then exit from WORD.

These examples are called use cases or use scenarios. Whenever you design some software you want to have in mind the way that your customers will employ your software. So let's see what these use cases say about the structure of the WORD program. Basically, the graphical user interface offered by the WORD program means that it can never predict how it is going to be used! When this program starts up it can do some general initialization but then it will just have to sit and wait to see what operations the user selects. In the use cases that we outlined above, on the first day the user will start WORD and then select File/New followed by File/Save As. On the second day the operator selects File/Open followed by File/Print. On the third day the operator selects Edit/Paste followed by Format/Font. So you can see that the WORD program cannot be structured with a linear flow. Instead it must initialize and then enter a wait state where all supported operations are allowed. That is, the WORD program must stand ready to do everything it knows how to do.

This is exactly the structure we have finally achieved in the Breakout program. In the code below:

    cBreakoutDbg    game;

    game.Create();

    game.printf( "Starting program...\n" );

    // We now enter the message loop.

    return theApp.Run();

we see the top-level window being initialized and then a call is made to the Run( ) member function of the cWinApp class. The code that you would find in this Run( ) function looks like:

   while ( GetMessage( &msg, NULL, 0, 0 ) )
   {
      TranslateMessage( &msg );
      DispatchMessage( &msg );
   }

This is a standard Windows message loop. The functions GetMessage( ), TranslateMessage( ), and DispatchMessage( ) are provided by Microsoft and are part of their WIN32 API. A message loop is an endless loop that simply waits for the arrival of Windows messages such as WM_PAINT or WM_SIZE. The WM_PAINT and WM_SIZE messages were defined by Microsoft but every Windows program is allowed to define additional messages. When the operator clicks on the menu or uses an accelerator key, an application-specific message is received by the GetMessage( ) function. For example, the Breakout game uses the IDM_GAME_START message to represent the operator requesting that the game start. The call to DispatchMessage( ) then routes this message to the WndProc( ) of the cBreakoutDbg class. I showed you a typical WndProc( ) a while back. Do you recall that it was just a giant switch statement that stands ready to react to whatever Windows message arrives next?

Our Breakout game is structured this way. It does nothing until the operator requests something. And it is ready to perform every possibility all the time. This is a more complicated way to structure your programs than the linear flow you typically see in a console-mode program. It is one of the many new technologies required to accomplish a Windows program.

Okay, enough background. Let's get this game improved. I can't ask you to improve the AppMain.cpp file because I have just bragged about how it is perfectly structured for a Windows GUI (graphical user interface) program. And I don't need for you to change the cBreakoutDbg class since that class merely provides the top-level window that hosts both a text editor pane (which is an instance of the cEditBox class) and a breakout game pane (which is an instance of the cBreakout class). There is nothing wrong with the top-level window.

The game's problems are all located in the implementation of the cBreakout class. But I haven't given you this code (my liability insurance wouldn't cover the risk that your head would explode if I showed you how much code is required to accomplish a sprite in a Windows program). If I haven't given you the code then how are you going to improve it?

This situation is solved by inheritance. Anytime you have a class, you can treat it as a base class and derive an improved class from it. That is what we are going to do.

Let's arbitrarily decide to call this better cBreakout class the cBetterBreakout class (I'm a whiz with names). We will need a cBetterBreakout.h file to hold the class declaration, which will look like:

class cBetterBreakout : public cBreakout
{
    < put some stuff here >
};

The very first line of this class declaration says that the cBetterBreakout class inherits from the cBreakout class. That means, any instance of the cBetterBreakout class already has all the functionality of the cBreakout class. You can look at the declaration of the cBreakout class in the cBreakout.h file located in the CppIde\include folder (the exact location depends upon the choices you made when you installed Computer Science Lab). But for our purposes it will be sufficient to consider the following simplified declaration for the cBreakout class:

class cBreakout
{
public:
    BOOL     Create();
    void     StartGame();
    void     PauseGame();
    void     RestartGame();
    void     SetVelocityMagnitude( UINT uiInitVelMag );
    void     SetTrainingMode( BOOL bTrainingMode );
    void     SetMakeSound( BOOL bMakeSound );
    virtual void CALLBACK  UpdateSprites();
};

It should be pretty obvious how these member functions are employed. At any time when our top-level window has keyboard focus, if the operator types Ctrl+S then the Windows operating system will generate an IDM_GAME_START message and route it to the WndProc( ) function of our window. When the WndProc( ) function of the cBreakoutDbg class receives the IDM_GAME_START message it calls the StartGame( ) member function. The SetVelocityMagnitude( ), SetTrainingMode( ), and SetMakeSound( ) member functions accomplish the Options dialog that allows the operator to specify the ball velocity and decide whether the game should be in training mode or make sounds.

It turns out that the only place we need to make changes in the cBreakout class is within its UpdateSprites( ) member function, since this is where we need to detect collisions between the ball and the paddle and between the ball and the walls and between the ball and the bricks. Since this is the only member function that we need to improve, then our cBetterBreakout class can be declared as shown below:

class cBetterBreakout : public cBreakout
{
    virtual void CALLBACK  UpdateSprites();
};

This declaration says that an instance of the cBetterBreakout class will behave in all regards just like an instance of the cBreakout class except for whatever functionality is located within the UpdateSprites( ) function. That is, we have replaced (or overridden) the original member function with an improved member function. I am not going to explain the word virtual that appears in the function signature other than to say it is a C++ language keyword that helps us to accomplish our purpose of replacing the original member function (I have to save something for your next course!).

At this time open the Breakout 2 project. You will observe that this project includes 2 files that were not present in the original Breakout project. These files, cBetterBreakout.h and cBetterBreakout.cpp, are the header file and implementation file for the new cBetterBreakout class. Take a look at the contents of the cBetterBreakout.h header file. You will see that this class declaration consists of only the one statement that overrides the UpdateSprites( ) member function. But every member function and every state variable of the base class, cBreakout, is still present in any instance of the cBetterBreakout class. In comments I list those data members of the cBreakout class that we will need to employ when we write our improved UpdateSprites( ) member function (don't panic at the size of this list of data members).

Now look at the implementation of the UpdateSprites( ) function in the cBetterBreakout.cpp file. The passed parameters to this function are a little scary only because it is a callback function that is repeatedly called by an animation engine. You see, to produce the illusion of a moving sprite we must constantly repaint the screen 30 times per second. But the body of the UpdateSprites( ) member function is basically empty, and that is the problem. The UpdateSprites( ) member function seen in the cBetterBreakout class is identical to the original UpdateSprites( ) member function that is part of the cBreakout class. So even though we have overridden this function, we will still get the same behavior. But at least we now have a location to put new code.

So the Breakout2 project has a new cBetterBreakout.h file and a new cBetterBreakout.cpp file which hold the declaration and implementation of a new class. So what? These new files are pointless unless we employ them in the program. This part you are going to love. Watch how easy it is to replace the original cBreakout instance with the new cBetterBreakout instance.

In order to throw away the original instance of the cBreakout class and replace it with an instance of our new cBetterBreakout class I only have to make 3 changes in the project. In the cBreakoutDbg.h file there had previously been the #include statement:

#include "cBreakout.h"

This is the statement that allows the compiler to find the declaration of the cBreakout class so I can instantiate variables of this type. I need to replace this statement with:

#include "cBetterBreakout.h"

before I can instantiate a cBetterBreakout instance. The second change is also located in the cBreakoutDbg.h file. In the private data members section of the cBreakoutDbg class there had previously been the declaration:

   cBreakout * m_pBreakout;      // child window holding game

which is the data member that holds the class instance that represents the child window that hosts the breakout game. We now want this to be an instance of the cBetterBreakout class so this statement needs to be changed to:

   cBetterBreakout * m_pBreakout;      // child window holding game

Note that there is no need to change the variable name, only the data type must change.

Before we leave the cBreakoutDbg.h file I want to point out that I still refer to the "private section of the class declaration" even though you won't find the private keyword. Instead you will observe the protected keyword. Protected and private are almost synonymous but protected is what you want to use whenever you employ inheritance in a hierarchy of classes.

The third change is located in the cBreakoutDbg.cpp file where we had called the constructor for the m_pBreakout data member. In the cBreakoutDbg constructor there had previously been the statement:

    while ( ( m_pBreakout = new cBreakout() ) == NULL )

which employs the new keyword to dynamically allocate memory to hold the cBreakout instance which the pointer m_pBreakout points at. This is an example of a compound statement. It could just as well be written as:

    m_pBreakout = new cBreakout();
    while ( m_pBreakout == NULL )       // new operator failed
    {
        < DO SOMETHING TO FREE UP SOME MEMORY >
        m_pBreakout = new cBreakout();  // try again
    }

In this form it is perhaps more clear that the new operator will initialize the m_pBreakout pointer with the value NULL if there is not enough memory available in the computer. In this situation the program can't proceed since it needs this memory to hold a cBreakout instance. So the program sits in this while loop waiting for some other program to be closed and thereby release its memory back to the operating system.

We will be learning more about dynamic memory allocation a few pages later. But for now let me get away with just saying that this statement needs to change to:

    while ( ( m_pBreakout = new cBetterBreakout() ) == NULL )

so that the m_pBreakout data member will now point to an instance of our new cBetterBreakout class.

And that is all that is required to swap out the child window that hosts a cBreakout instance and replace it with a child window that hosts a cBetterBreakout instance. Nothing about the top-level window needed to change. Now that's a clean operation.

At this time compile and execute the Breakout2 project. You will see that the ball still ignores the paddle, the walls, and the bricks. But we now have a way to fix that.


Collisions

( 42 )

The Physics of Collisions

Surprise! We're going to have to learn a little bit about physics in order to complete the Breakout game. Computer programmers that write games are very knowledgeable about velocity, friction, gravity, resource consumption, population growth, and, of course, gory death.

The physical event that we need to model is a lossless (i.e., no friction) impact between a ball and an immovable object like a wall. We want our ball sprite to behave identically to a pool ball bouncing off a pool table bumper or a light beam bouncing off a mirror. The math is simplified by expressing the ball's velocity in two components: a first component aligned parallel with the wall and a second component aligned perpendicular with the wall. The following figure should then make clear that the x axis velocity should remain unchanged during the impact while the y axis velocity should reverse. So if prior to the collision the y axis velocity was given by "a" then after the collision the y axis velocity should be "-a".

Each time the ball sprite impacts against the upper wall we want to reverse the y axis component of the ball's velocity. It is a good thing, then, that the cSprite class offers the necessary member functions seen in the simplified class declaration shown below:

class cSprite
{
public:
    cSprite();           // constructor
    virtual ~cSprite();  // destructor

    // Interface
    int       GetX();
    int       GetY();

    void      GetPosition( int * piX, int * piY );
    void      SetPosition( int iX, int iY );

    int       GetXVel();     // x axis velocity in pixels per sec
    int       GetYVel();

    void      SetXVel( int iXVel );
    void      SetYVel( int iYVel );

    void      GetVelocity( int * piXVel, int * piYVel );
    void      SetVelocity( int iXVel, int iYVel );
};

It is for the convenience of the caller that this class provides different ways of requesting the sprite's current position and velocity. For example, you can ask for the x axis velocity by itself or the y axis velocity by itself or you can call a different member function and be returned both velocity components. You shouldn't be surprised to see pointers used for the passed parameters that need to return a value to the caller.

Now you might be wondering what good the member functions of the cSprite class are to us when we are trying to write the cBetterBreakout class. But look in the private data member section of the cBreakout class (the base class for our cBetterBreakout class) and you will find:

class cBreakout
{
private:
    cSprite       * m_pSprite;         // the ball
};

Since an instance of the cSprite class is a data member of the cBreakout class, and since our new cBetterBreakout class inherits from the cBreakout class, we can conclude that we have available an instance of the cSprite class and can use it to call any of the member functions declared in the cSprite class. Since this instance is a pointer (observe the asterisk in the declaration) we will need to employ the arrow operator -> to call these member functions.

At this time open the Breakout 3 project. Compile and run this code. You will observe that the ball sprite is getting smarter. It now successfully rebounds off the top and bottom edge of the game board. And it responds to the Training Mode option that you can set on the game's Options dialog (which you can get via a Ctrl+O keystroke). Then exit from the game and look in the UpdateSprites( ) member function of the cBetterBreakout class (found in the cBetterBreakout.cpp file) to learn how I have accomplished this. To understand this code you need to understand the following map of the Breakout game board:

It may surprise you to find the y axis pointing downward but that's just the way Microsoft defined it when they invented Windows.

When you ask the cSprite object to report its position it returns the x,y coordinates of its upper left hand pixel. We store this info in variables named iXPos and iYPos. This is accomplished with the following code:

    int   iXPos, iYPos;

    m_pSprite->GetPosition( &iXPos, &iYPos );

Note how we have to use the address of operator & to acquire the address of the iXPos and iYPos variables since the cSprite::GetPosition( ) function is declared to take passed parameters which are pointers to ints.

The cSprite class also offers a member function that returns the width and height of the sprite. We store this info in variables named uiSpriteWidth and uiSpriteHeight.

We know that the position of the top wall is the y axis value of 0 and the bottom wall is the y axis value of m_uiPanelHeight. Study the previous illustration until you understand why collisions with the top and bottom wall can be detected by the following if statement:

   if ( iYPos < 0 )                  // ball has hit the top wall...
   {
       m_pSprite->SetYVel( -iYVel ); // ... so reverse its y axis velocity
   }
   else if ( iYPos + uiSpriteHeight > m_uiPanelHeight )
                                     // ball has hit the bottom wall...
   {                                 // ... meaning you just lost that ball
       InitGame();                   // prep the game board for a new game
   }

The code that you will find in the Breakout3 project is slightly more complicated than what I have shown here. I am sure you can figure out that the calls to the PlaySound( ) function accomplish sound effects for the game (I will explain how you can customize these sound effects on the next page). But the calls to the SetPosition( ) function of the cSprite class are more mysterious. I didn't originally think to include these calls to SetPosition( ) but my testing showed a very curious bug. As long as the ball was moving with a sufficient velocity, the game worked great. But when I slowed the ball way down it would come to a halt against the first wall it collided with.

It looked like the ball was gluing itself to the wall. I knew that my code was never setting the ball velocity to zero, so this was really surprising. The only change my code ever made to the ball velocity was to negate it, so that +20 pixels per second became -20 pixels per second and -20 pixels per second became +20 pixels per second. How could the ball come to a stop?

I am not embarrassed to describe this bug because it is a good example of how the unexpected can bite you. Remember that the UpdateSprites( ) function is being called 30 times per second to accomplish animation. When the ball velocity is small, the ball may move less than 1 pixel per call to the UpdateSprites( ) function. So consider the situation where the ball has just collided with a wall. The code in UpdateSprites( ) detects the collision and negates the ball's velocity. One thirtieth of a second later UpdateSprites( ) is called again. Because the ball is moving with a low velocity, it has not yet moved a whole pixel and hence has not yet moved off the wall. Therefore the code in UpdateSprites( ) detects a collision with the wall and negates the ball's velocity. Uh oh. What was happening was that upon consecutive calls to UpdateSprites( ) the ball's velocity was being set to the sequence +20, -20, +20, -20, etc. The ball velocity was not getting set to zero but visually that is what appeared to be happening. The ball was trapped against the wall! The solution was to add the calls to cSprite::SetPosition( ) that would immediately move the ball slightly off the wall and thus prevent it from being trapped.

Study the code that I have provided for the top and bottom walls because your assignment is to complete the logic that handles the collisions with the other 2 walls. The code you will need to provide is slightly simpler than the code that is already there because the Training Mode variable, m_bTrainingMode, is not relevant to the left and right walls. However, make sure you think about which velocity component (x or y) it is that you want to reverse. You don't have to worry about the calls to SetPosition( ) that I just described unless you want to (if you want to see this bug, try a ball velocity of about 20 pixels per second). Compile your changes and see if you are on the right track. If you get hung up, consult my solution in the Solutions folder.


Structs

( 43 )

The Struct Data Type

Hopefully you were successful with your assignment for the Breakout 3 example program. I showed you how to detect collisions with the top and bottom walls and you needed to add similar code [in the UpdateSprites( ) member function of the new cBetterBreakout class] to detect collisions with the left and right walls.

This still leaves us with the need to detect collisions between the ball and the paddle and between the ball and each of the bricks. This code is more complicated so I think I will just show it to you. It also gives us our first opportunity to see a struct. The struct data type existed in the C language and was the basis for the class data type that was added in the C++ language. A struct is a user defined type because you get to invent it. A struct is a container for whatever stuff you want to keep together. For example, if you are a teacher and want to keep a database of your students, you might decide that you want to keep together the student's name, his ID number, and his grade point. Hence you might find it convenient to define the following struct you call the Student struct:

struct Student
{
    char  szName[80];
    UINT  uiID;
    float fGrade;
};

Note how the struct allows us to package together variables having different data types. We could create an instance of this struct using a statement such as:

   Student  Ringo;

and we could then initialize the three struct members using statements such as:

   strcpy( Ringo.szName, "RStarkey" );
   Ringo.uiID = 15;
   Ringo.fGrade = 4.0;

Note how we employ the dot operator to access the individual members of the struct. We had to employ the strcpy( ) (string copy) library function because you are NOT allowed to say:

   Ringo.szName = "RStarkey";

But we could do the initialization at the same time we instantiate the variable:

   Student  Ringo = { "RStarkey", 15, 4.0 };

The particular struct that we need to employ in the Breakout game is the RECT struct which was defined by Microsoft as shown below:

struct RECT
{
   int  left;
   int  top;
   int  right;
   int  bottom;
};

Obviously, this RECT struct is a good way to encapsulate the geometry of a rectangle. This RECT struct is used a lot in Windows since all top-level windows, all child windows, all bitmaps, all icons, all toolbars, etc. are rectangles.

Here's the code that will detect collisions between the ball and the paddle. You can see that it employs two instances of the RECT struct. The Hungarian prefix for any RECT struct is the letters rc.

   RECT   rcBall, rcPaddle;

   rcBall.left   = iXPos;
   rcBall.right  = iXPos + uiSpriteWidth;
   rcBall.top    = iYPos;
   rcBall.bottom = iYPos + uiSpriteHeight;

   rcPaddle.left   = m_uiXPaddle;
   rcPaddle.right  = rcPaddle.left + m_uiPaddleWidth;
   rcPaddle.bottom = m_uiYPaddle + m_uiPaddleHeight;
   rcPaddle.top = rcPaddle.bottom - PADDLETHICKNESS;

   if ( IntersectRect( &rcOverlap, &rcPaddle, &rcBall ) )
   {
      if ( m_bMakeSound )
      {
          PlaySound( PADDLESNDFILENAME, NULL, SND_FILENAME | SND_ASYNC );
      }

      m_pSprite->SetYVel( -iYVel );  // reverse the ball's velocity
   }

The easiest way to detect a collision between two rectangles is to let Microsoft do it. The WIN32 API includes a function called IntersectRect( ) which takes two rectangles and returns a Boolean which indicates whether there is any overlap at all between the two rectangles. In the code above, find where we employ the Boolean returned by IntersectRect( ) in the test of an if statement. This if statement is another example of a compound statement since it could also be rewritten as:

   BOOL   bOverlap = IntersectRect( &rcOverlap, &rcPaddle, &rcBall );

   if ( bOverlap )
   {
      <make the sound and reverse the velocity>
   }

The two rectangles which are passed into IntersectRect( ) are placed in the second and third passed params. The first passed param to the IntersectRect( ) function is not a passed param at all, but a returned param.

Up until now I have been telling you that functions can take any number of passed params but they can only return one value. It is true that the return statement can only return one value and it is true that the return type found in a function's signature can only return one value, but there is another way to return values from a function: use a pointer for the passed param. Since a pointer gives a function the ability to change a memory location that is in the scope of the caller, then a pointer gives a function a way to return a value. Microsoft designed the IntersectRect( ) function such that its first passed param would return a rectangle that described the geometry of the area of overlap between the two rectangles placed in the second and third passed params. Our Breakout game has no use for this geometry info and hence we ignore the values placed into the rcOverlap variable by the IntersectRect( ) function.

The code to detect collisions between the ball and the bricks is rather ugly. One reason it gets messy is the fact that there are multiple bricks. Every time the ball sprite moves by even one pixel we must laboriously ask each of the bricks, "Did you just get hit by the ball?"

At this time load the Breakout 4 example project. Compile and run this code to see that the ball correctly impacts with all the other objects on the gameboard. Then exit from the game and look at the code in the UpdateSprites( ) member function of the cBetterBreakout class. Locate the following nested loop that checks each of the bricks for a collision with the ball:

   for ( row=0; row < m_nRows; row++ )
   {
      for ( col = 0; col < m_nCols; col++ )
      {
         if ( IntersectRect( &rcOverlap, &rcBrick, &rcBall ) )
         {
            m_uiColumnData[col] &= ~( 1 << row ); // remove brick

            if ( m_bMakeSound )
               PlaySound( BRICKSNDFILENAME, NULL, SND_FILENAME | SND_ASYNC );

         } // end of if ( IntersectRect(...) )

      } // end of for ( col = 0; col < m_nCols; col++ )

   } // end of for ( row=0; row < m_nRows; row++ )

This is a simplified version of the actual code as I have removed a lot of the statements so you can better visualize the nested for loops. The data member m_uiColumnData is an array that remembers which of the bricks are left on the game board. This array is dimensioned as:

UINT   m_uiColumnData[12];

because there are 12 columns of bricks. This means m_uiColumnData contains 12 (unsigned) ints, each of which are 32 bits wide. Each bit of each of these ints represents a single brick in that column. Hence there can be no more than 32 rows of bricks. Although the game board only shows 2 rows of bricks, the code has been designed to support any number of rows up to 32. The assignment of bits to bricks is shown in the following illustration:

All of the bits in all of the entries in the m_uiColumnData array are initialized to 1 at the start of the game, since all of the bricks are initially present. Then, whenever a brick is hit by the ball we need to clear one of the bits within one of the ints in m_uiColumnData. If the brick that has been hit is described by:

   UINT   row, col;

then the statement that clears the necessary bit is:

  m_uiColumnData[col] &= ~( 1 << row ); // remove brick

Some people criticize the C (and hence C++) language for the cryptic appearance of statements such as this. But repeated exposure quickly builds up your fluency. Let's break this statement down so you can see how it works. The statement employs the notation A &= B which is just shorthand for A = A & B. Hence we could also write the statement in the following form:

  m_uiColumnData[col] = m_uiColumnData[col] & ~( 1 << row ); // remove brick

The ampersand & represents the bitwise AND operator. We need to use the AND operation to clear a bit. For example, if some 8 bit variable uiVar originally held 0xf7 and we wanted to clear the LSBit then we would need to AND uiVar with 0xfe. This will be obvious if we write these values in binary:

    uiVar  0xf7  1111 0111
    mask   0xfe  1111 1110
    ------ ----  ---------
    result 0xf6  1111 0110

A value used to set or clear individual bits in another value is called a mask. Note how this AND operation has cleared a single bit without disturbing any of the other bits in uiVar. So our Breakout code needs a mask. If the brick that has just been hit was in the top row then the mask we want is 1111 1110. If the brick was in the second to the top row then the mask we want is 1111 1101, etc. The C language gives us a left shift operator which is indicated by two "less than" symbols <<. Examples of this operator as shown below:

                   result     result
 operand  shift   in binary   in hex
    1   <<  0   = 0000 0001  = 0x01
    1   <<  1   = 0000 0010  = 0x02
    1   <<  2   = 0000 0100  = 0x04
    1   <<  3   = 0000 1000  = 0x08

Using the left shift operator we have come close to creating the masks that we need, except for the fact that we want 1 bits everywhere there are 0 bits and vice versa. This means we need to use the bitwise complement operator which is indicated by the tilde ~ symbol. When you complement a binary bit you invert it. Hence a 1 becomes a 0 and a 0 becomes a 1. Here are some examples:

   binary value    complemented value
   ------------    ------------------
     0000 0001         1111 1110
     0000 0010         1111 1101
     0000 0100         1111 1011
     0000 1000         1111 0111

So when we need to clear the brick in row "row" of column "col" we merely need to AND the value in m_uiColumnData[col] with the mask given by:

~(1 << row )

With this explanation you should be able to understand the function of the statement:

  m_uiColumnData[col] &= ~( 1 << row ); // remove brick

Right below this statement [i.e., still in cBetterBreakout::UpdateSprite( )] you will be able to find the following code:

    if ( ( rcOverlap.right - rcOverlap.left )
                > ( rcOverlap.bottom - rcOverlap.top ) )
    {
        // Anytime we hit on a horizontal surface of
        // a block we want to reverse our Y velocity.

        m_pSprite->SetYVel( -iYVel );
    }
    else
    {
        m_pSprite->SetXVel( -iXVel );
    }

This code is reversing either the x axis or y axis velocity of the sprite. Which one we want to reverse depends upon the geometry of how the ball impacts the brick. The following illustration shows the 2 possibilities:

The variable rcOverlap is a RECT struct that holds the area of intersection. The if statement:

    if ( ( rcOverlap.right - rcOverlap.left )
                > ( rcOverlap.bottom - rcOverlap.top ) )

is determining whether this area of intersection is taller than it is wide. Depending upon this outcome, we reverse either the x or y axis velocity.

The PlaySound( ) function used in the nested for loop is part of Microsoft's WIN32 API. I'll tell you how to have some fun with this in just a moment. The other code that you will observe in the actual UpdateSprites( ) function are examples of optimizations that allow the program to run faster and hence achieve the refresh rate required to do animation. An example of these optimizations is the if statement seen buried inside the nested for loops below:

   for ( row=0; row < m_nRows; row++ )
   {
      if ( ( iYPos <= uiBrickBottom )
        && ( iYPos + uiSpriteHeight >= uiBrickTop ) )
      {                         // ball is within a row
         for ( col = 0; col < m_nCols; col++ )
         {
           ...
         } // end of for ( col = 0; col < m_nCols; col++ )

      } // end of if ball is within a row

   } // end of for ( row=0; row < m_nRows; row++ )

The purpose of this if statement is to avoid the inner loop that iterates over the columns when the ball sprite is not within the right portion of the game board. The test for this if statement is a combination of two tests. The logical AND operator && requires both subtests to be TRUE for the body of the if statement to be executed. Note that both subtests are expressions which evaluate to Boolean results and hence can be combined with the logical AND operator.

In another optimization, you will be able to spot me using the detested goto keyword in order to speed up the code.

Another reason that the code to detect collisions between the ball and the bricks is messy is that the operator can stretch or shrink the program's top-level window at any time. This influences the game board and hence the sizes and locations of the bricks. There's also a splitter bar that influences the relative sizes of the child windows. So the cBreakout class has to remember the geometry of the bricks using the data members m_auiRowBoundaries and m_auiColBoundaries which are both arrays that are recomputed every time the Windows operating system sends us the WM_SIZE message.

The coding fireworks are now over. I'll soon start distributing the graduation caps and gowns (I won't give you one unless you promise to keep quiet about an alleged use of the goto keyword). But there are still a few things you might like to do for fun. For example, you might enjoy constructing your own ball. The default ball is located in the Sprite.bmp file that I provide. But you can overwrite this file with a different .BMP file to get a new ball. The Breakout game will use whatever image it finds in the Sprite.bmp file. To create a new ball you can use Microsoft's Paint program (Start/Programs/Accessories/Paint) or any other program that can construct a .BMP file. My default ball is 32 by 32 pixels and I would recommend that you stick to this approximate ball size.

A sprite is a moving image whose background is transparent. That is, if you open the default Sprite.bmp file that I provide (simply double-click on this filename when it is displayed by Windows Explorer) you will see the gray ball surrounded by a cyan background. But when the ball appears in the Breakout game, the cyan background is missing. Here's the trick. Whatever color is located in the upper left-hand corner of the bitmap will be treated as the transparent color. Any pixels in the .BMP image that have that color will not be painted onto the screen and will therefore appear to be transparent. If you draw a face and make the eyes the same color as the upper left-hand pixel, then this face will become the ball but the eyes will be transparent.

I have also given you the opportunity to select your own sound effects. Using Windows Explorer (Start/Programs/Windows Explorer) you will be able to observe that there are four .WAV files in the folders that hold each of the versions of the Breakout example program. These four .WAV files are used for the following sound effects:

Paddle.wav : impact of the ball against the paddle
Bumper.wav : impact of the ball against any wall
Brick.wav  : impact of the ball against any brick
Lose.wav   : game over sound

The Breakout game will employ whatever sounds it finds in these four .WAV files. If you have a microphone you can record original sounds. Or you can undoubtedly find some .WAV files already on your computer. Commercial games install lots of .WAV files onto your hard drive. To locate these files select "Start/Find/Files or Folders" from the Windows Start menu and then request a search for files named:

*.wav

The asterisk seen above is a wild card character that matches up with any string of characters. Yet another possibility is to download sound effects off the Internet.

The Breakout game will not notice that you have changed the Sprite.bmp file or any of the .WAV files until you restart the game. And, obviously, you must be using the version of the game located in the folder where you changed the .BMP or .WAV files.

In closing, I need to point out that Microsoft offers an application programming interface (API) specifically designed for use in computer games. This is Microsoft's DirectX technology. You can't really start to understand DirectX until you understand Microsoft's WIN32 API, which is the technology that Microsoft intends for developers to use to create standard Windows programs such as business spreadsheets, database report generators, etc. So your time has not been wasted. I created the Breakout computer game based upon the WIN32 API in order to provide a fun demonstration of the C++ language. But you can perceive a little bit of the mismatch when you play the Breakout game. You will probably notice that the paddle keeps overshooting your intensions. This has to do with the fact that the paddle is controlled via keystrokes, and the WIN32 API assumes that keystrokes are employed for typing, not gaming. Consequently, the Windows operating system automatically kicks into what is called typematic action when you hold any key down. This simply means that the keystroke starts repeating at a high speed while you hold the key depressed. This is usually just what you want when you are trying to repeat a certain letter a bunch of times in a document. But it leads to the overshoot we see in the paddle. If we were employing Microsoft's DirectX technology for this game we would be able to accomplish a much more responsive paddle.

If you are interested in a little introduction to DirectX, then I can point you to a tutorial I have written about Direct3D, which is that portion of DirectX that allows you to create 3D models and move among them in a 3D world. I provide the source code for a Direct3D8 program that allows the operator to fly through the Munsell Color Solid, which is an old (1905) model of all the colors perceivable by humans. You can find my tutorial (the file Direct3DTut1.htm) and the Windows executable (the file Direct3DTut1.exe), and all the source code on my web site and in the D:\Direct3DTut1 folder of the Computer Science Lab CD-ROM. Note that this program requires DirectX8, which is the version of DirectX that comes with Windows XP. This version of DirectX can be obtained from Microsoft and then installed on the other, older flavors of the Windows operating system. Don't just read the .HTM file, the bulk of the tutorial resides in the comments in the actual source code. Unfortunately this source code is not based upon the CSL Framework and hence it is a tangent to our current goal of learning the WIN32 API.


Dynamic Memory

( 44 )

Dynamic Memory Allocation

Now that we are nearing the end of this introductory C++ course I think it would be fun to bring you full circle and again visit the very first technology we studied with the RPN calculator. Long ago, I told you that I selected a Reverse Polish Notation calculator because it employed a stack and I said that stacks were ubiquitous within computers. Besides the 4 deep operand stack, we also discovered that the calculator employed a subroutine return address stack. When we next studied the 8051 microprocessor we again discovered a stack. This stack was again used to hold subroutine return addresses but the PUSH and POP instructions also allowed it to hold arbitrary data.

In this C++ section of the curriculum, I mentioned that memory for automatic variables is allocated from the stack. I also described the show function call stack ( [image]) button seen on the toolbar while we are in the debugger. I said that the call stack showed the history of calls made to functions. Just as with assembly language, there is nothing embedded in a function that tells it where execution should continue when the function's return statement is reached. Some other mechanism has to keep track of what code called that function and hence what code should be returned to when the function exits. I hope you understand from my discussion of the 8051 why a stack is the ideal method for retaining this information.

I want to further discuss the stack and introduce a very fun aspect of pointers.

We have learned that a pointer is just a data memory location that is used to hold an address. Presumably, that address is important because there is data located in memory at that address. We have learned that because C and C++ employ call by value you can share values with a function you call without allowing it to muck around with your variables. But if you want to allow the function to change your variables then you merely need to employ pointers for the passed params because then the function will know the actual addresses of your variables.

One thing that is typically done with pointers is to ask a function to populate a section of memory beginning at a certain address. For example, somebody might write a function called ReturnTenInts( ) whose function prototype looked like:

void   ReturnTenInts( int * pai );

The idea is that this function would place ten numbers in memory starting at the location (address) you specified with the passed param. The Hungarian prefix used for the passed parameter is pai meaning "pointer to array of ints". You would call this function in the following manner:

int   aiMyIntArray[10];

ReturnTenInts( aiMyIntArray );
printf( "The third int is %d\n", aiMyIntArray[2] );

We have learned that arrays are treated specially by the C/C++ compiler. Whenever you employ the name of an array as a passed parameter the compiler actually sends the starting address of the array. Hence the above call to the ReturnTenInts( ) function does provide the "pointer to array of ints" data type that the function requires. Notice that the calling code allocated the memory for the ten ints but then did not initialize this memory. Instead the calling code is leaving it up to the ReturnTenInts( ) function to initialize this memory at aiMyIntArray.

There is another type of function that is often useful: a function that not only initializes memory but also allocates the memory. An example of a call to this type of function is shown below:

int   * pai;

AllocateAndReturnTenInts( &pai );
printf( "The third int is %d\n", pai[2] );

There are a couple of surprising things here. The first is the fact that we are using the square bracket array notation on the pai variable which is not an array but is instead a pointer. This is legal. It is just another reflection of the fact that the C/C++ compiler treats the name of an array as the beginning address of that array. Hence the name of an array is equivalent to a pointer, since pointers hold addresses. So, as long as the AllocateAndReturnTenInts( ) function did allocate memory for ten ints and then populated this array with ten ints, then we could employ the pai[2] notation to print the value of the third int.

I need to point out that the compiler sees no difference between:

int   * pai;

and:

int   * pi;

That is, both pai and pi are just "pointers to ints" to the compiler. When I add the letter a to the name I am reminding myself that this pointer will be used to walk through an array. But that naming convention is just for my convenience. It is up to the programmer to keep straight which pointers point at single instances and which pointers point at arrays of instances. And when a pointer points at an array, it is up to the programmer to not walk the pointer past the end of the array (this may be the most common bug in the C and C++ languages).

You probably didn't catch the second surprising thing about the above code snippet. We are trying to write an AllocateAndReturnTenInts( ) function which will allocate memory. If it is going to allocate the memory then it will need to tell us where the memory was allocated. This is a crucial difference between the AllocateAndReturnTenInts( ) function and the ReturnTenInts( ) function. We specified the beginning memory address to the ReturnTenInts( ) function but we need to be informed of the beginning memory address selected by the AllocateAndReturnTenInts( ) function.

Is this possible? Yes, but it requires another level of indirection. Because C and C++ employ call by value, we need to send to the AllocateAndReturnTenInts( ) function the address of our pointer. Only if the AllocateAndReturnTenInts( ) function has the address of our pointer will it be able to change the value of that pointer. And that is what we want: after the function returns we want our pai pointer to point to the memory that the AllocateAndReturnTenInts( ) function allocated.

I am hoping there is this big grin on your face about now, as you realize that I am describing a pointer to a pointer. The address of an int is a pointer, so the address of a pointer is a pointer to a pointer to an int.

If you were the designer of the C language, what notation would you employ for this new pointer to a pointer data type? The obvious notation is the notation seen used below in the implementation of the AllocateAndReturnTenInts( ) function:

void  AllocateAndReturnTenInts( int ** ppai )
{
    int   aiMyIntArray[10];
    *ppai = aiMyIntArray;
    return;
}

The notation "int **" reads as "pointer to a pointer to an int". To keep my mind from getting too tangled, I add a second p letter to the Hungarian prefix. If you dereference a pointer to a pointer to an int, you get a pointer to an int. You dereference a pointer to a pointer just like you dereference a pointer, using an asterisk as seen in the second statement of the AllocateAndReturnTenInts( ) function. Both sides of the equals sign in this second statement hold pointers to ints.

The code that I have shown you will compile. But it's not any good. And the reason has to do with -- drum roll please -- a stack.

Consider the following code:

int   SquareIt( int i );  // function prototype

int  main()
{
   int  i = 3;

   printf( "Answer = %d\n", SquareIt( i ) );

   return 0;
}

int  SquareIt( int i )
{
    int    j;

    j = i * i;

    return j;
}

In this code snippet we see a function named SquareIt( ) that has one passed parameter named i and one other variable called j. The j variable is called a local variable because it exists only within the SquareIt( ) function. We have discussed the isolation between functions and we know that this j would be independent from all other j variables that might appear in other functions.

I have taught you that this isolation is accomplished via the scope and lifetime rules which say that the variable j comes into existence when the SquareIt( ) function is called and goes out of existence when the SquareIt( ) function exits. Have you wondered what mechanism allows a variable to appear and disappear? All local variables are allocated on the stack, the same stack that is used to hold the function return address. When the SquareIt( ) function exits, the stack is popped of both the return address and the data memory location used for the j variable. This means that the stack address where the j variable used to be located is now available for some new assignment. Hence it would be pointless to provide this address to somebody and claim that it was the home of the j variable.

Another name for local variables such as j is automatic variables because their memory allocation occurs automatically. You just declare the variable and when the enclosing block is entered the compiler automatically allocates memory on the stack to hold the variable. And this memory is automatically recovered when the flow of execution exits from the enclosing block.

Not let's return to our other code snippet, which I repeat below:

void  AllocateAndReturnTenInts( int ** ppai ); // function prototype

int  main()
{
   int   * pai;

   AllocateAndReturnTenInts( &pai );

   printf( "The third int is %d\n", pai[2] );

   return 0;
}

void  AllocateAndReturnTenInts( int ** ppai )
{
    int   aiMyIntArray[10];
    *ppai = aiMyIntArray;
    return;
}

The AllocateAndReturnTenInts( ) function is supposed to allocate memory and return the address of this memory. Well, it does do these things. But unfortunately, it employs a local, stack allocated variable for the array. Hence this memory disappears when the function exits. So the calling code in main( ) is returned an address, but by the time the AllocateAndReturnTenInts( ) function returns, this is now an invalid address because that section of the stack has already been reclaimed.

Is your head starting to hurt? I am almost finished.

The fact that the AllocateAndReturnTenInts( ) function returns an invalid address means that this program could crash when it is run. The program may or may not crash, you can't be sure. If the memory at the invalid address has already been used for a new purpose then a crash becomes more likely. Even without a crash (such as the famous blue screen of death) the program could still act screwy due to this bug.

I have provided you with this code in a project folder named "DynamicMemory". You can play with the code if you like. It doesn't happen to cause a crash, but this is only because nobody else has done anything at the invalid address before the program itself quits using the invalid address.

There is a way to write a function like AllocateAndReturnTenInts( ): we just need to employ dynamic memory allocation. This is an entirely different way to allocate memory. You employ the new keyword and the memory you receive is not located on the stack but instead comes from the heap (boy, those computer science majors can really name things). The advantage of memory allocated from the heap is that you get to keep it for as long as you want. Hence the AllocateAndReturnTenInts( ) function can request ten ints worth of memory from the heap and then return the address of this memory allocation to the caller via a passed param which is a pointer to a pointer. Problem solved!

If you would like to impress someone with your new knowledge of computer science, just repeat that last sentence (the long one) to them. I will have succeeded as an instructor if it only sounds like gobbledygook to one, not both, of you. I am going to leave further discussions of the heap and dynamic memory allocation to your next C++ course, but in closing I will point out that you have seen one instance of a dynamically allocated variable. That situation occurred in our Breakout example program when we replaced:

   while ( ( m_pBreakout = new cBreakout() ) == NULL )

with:

   while ( ( m_pBreakout = new cBetterBreakout() ) == NULL )

The new keyword signals that this m_pBreakout pointer will point at memory that has come from the heap. The new keyword also reminds you that the constructor for the cBetterBreakout class will be called in order to initialize the new instance of this class.

I have said you can keep dynamically allocated memory as long as you want. So how do you eventually let go of it? You employ the delete keyword which is the counterpart to the new keyword. The delete keyword causes a call to the class destructor, which is the counterpart to the call to the constructor caused by the new keyword. If you forget to match up your calls to new with corresponding calls to delete then your program has a memory leak. If enough memory is leaked (that is, lost) then eventually the operating system itself will crash because the computer will have run out of memory.

In the DynamicMemory example program, the AllocateAndReturnTenInts( ) function employs the new keyword to obtain a new memory allocation. The address of this memory allocation is returned to the caller. Therefore the caller also takes possession of the responsibility to eventually release (also called "free") this memory allocation via the delete keyword.


The New Project Wizard Continued

( 45 )

The New Project Wizard

for Windows Programs

I am going to go out on a limb here and state that you are ready to start constructing your own original Windows programs. Make no mistake, you still have lots to learn. So you will have to start with simple programs and gradually add to them as you master new aspects of the WIN32 API. As I recall it was only at about my 1 year anniversary with the WIN32 API that I began to feel the least bit comfortable using it (really!).

On the next page I discuss how you can use my CSL Framework to quickly build Windows programs, but on this page I will assume you are going to do it the hard way, from scratch.

All it takes to use CPPIDE to compile and execute ANY original program you wish to write (or which you find in a book) is for you to create a project (.PRJ) file and a makefile (.MAK) and place these 2 items in a new folder with your source code (.C or .CPP) and header (.H) and windows resource (.RC) files. And you don't even have to manually prepare the .PRJ and .MAK files since CPPIDE offers a wizard (automation tool) that performs these steps for you.

I already showed you the New Project Wizard when we were working with console-mode programs, so this time around I will concentrate on Windows programs. Let's assume that you want to write a brand new Windows program of your own creation. To get started you want to either select "Project/New Project..." from CPPIDE's menu or else click on the CPPIDE toolbar button that looks like a blank sheet of paper. This will initiate CPPIDE's New Project Wizard.

The New Project Wizard, like all wizards, is nothing but a sequence of dialog boxes that leads you through your choice of options. There are 5 steps in the New Project Wizard and hence 5 dialog boxes. But 2 of these 5 dialogs are skipped when you are not employing the CSL Framework. The first dialog box is shown below:

You use this dialog to name your project, declare whether it will be a console-mode or Windows program, and whether -- if it is a Windows program -- it will employ my CSL class framework. And finally, this dialog allows you to select either the C or C++ language. Only after you enter a legal project name (which will also be used for the name of your executable file) will the "Next" pushbutton on the dialog box become enabled, thereby allowing you to proceed to the next step.

You generally will not need to type in the edit control labeled "Project Location" because the wizard will automatically construct a location based upon your project name. The project location is just the name of the folder that holds the project (.PRJ) file and the makefile (.MAK file) and whatever .C/.CPP and .H and .RC source files the project employs.

Since I am assuming on this page that you will not be using the CSL Framework, when you click on the Next pushbutton seen on the Step #1 dialog box you will be fast forwarded to Step #4. This is because Step #2 and Step #3 are only relevant to programs that employ the CSL Framework.

Step #4 of the New Project Wizard is shown below:

In Step #4 you list the names of all of your .C/.CPP and .RC files in the left hand column and the names of all of your .H files in the right hand column. If you name one of your .CPP files "Main.cpp" or "AppMain.cpp" or "WinMain.cpp" then the New Project Wizard will be able to infer your intentions and will place some useful boilerplate code for the WinMain( ) entry point into the .CPP file it constructs for you. Of course, if you don't like any of this boilerplate you are free to change it. Remember that all Windows programs (that do not employ the CSL Framework) begin in the WinMain( ) function.

Things to remember when selecting file names:

  • Do not employ space (blank) characters in your file names,
  • Employ lower case file name suffixes such as .cpp rather than .CPP,
  • Specify .c file name suffixes if you requested the C language and .cpp file name suffixes if you requested the C++ language.

When you click on the "Next" pushbutton you will proceed to Step #5 of the New Project Wizard which is shown below:

Only advanced programs have any need of the options offered in this fifth step. If you are writing programs that need these options then you may need to learn the language used in makefiles. I offer a short introduction to the language of makefiles in an appendix of this on-line help document.

Based upon the information you provide, the New Project Wizard will then construct a project (.PRJ) file and a makefile (.MAK) as well as the .C/.CPP, .H, and .RC files that you requested. Of course, the source files will be mostly empty since the New Project Wizard cannot read your mind.

Now let's consider the situation where you want to compile a Windows program you found in a book or on the Internet. In this situation you employ the New Project Wizard just as I have described except you specify the exact file names that you observe in the program. Then AFTER the New Project Wizard finishes constructing the project folder you use Windows Explorer (Start/Programs/Windows Explorer) to overwrite the empty .C/.CPP, .H, and .RC files with the corresponding files from the book. The advantage of this procedure is that the wizard constructs the .PRJ and .MAK files for you using the correct file names.

In the example programs that I distribute with CPPIDE I have been hiding the .PRJ and .MAK files. However when you use the New Project Wizard you will discover that the .PRJ and .MAK files appear in the Files listbox on the left-hand pane of CPPIDE. This means you can inspect these files and even edit them from within CPPIDE. You won't need to interact with the .PRJ and .MAK file unless you need to add or delete some file from your project. The structure of a .PRJ file is very obvious so you don't need to be scared to edit it. After making any change to your .PRJ file you merely need to re-open that project. The makefile (.MAK) is a bit scarier but if you know what you are doing you can edit this file. In this case there is no need to re-open the project as your makefile changes will become active the next time you compile.

Be aware that if you need to add or delete a source file from a CPPIDE project you will need to edit both the .PRJ and the .MAK file.

Even though it only takes a few seconds to answer the New Project Wizard's questions, this can still get tedious if you want to construct a CPPIDE project for every example program in a large book. Therefore I have done a lot of this work in advance as a favor.

The only WIN32 book that I can wholeheartedly recommend is the one by Charles Petzold entitled Programming Windows Fifth Edition (his older book, Programming Windows 95 is very similar). I have already prepared all the CPPIDE infrastructure necessary for all 145 of the example programs illustrated in this Petzold book. The only way to get the source code for these programs is from the CD-ROM that accompanies the book, but perhaps you can find it in your library.

You can read about the details of the Petzold book from the following world-wide-web page:

http://www.computersciencelab.com/Petzold.htm

or from the following file on the Computer Science Lab CD-ROM:

D:\Petzold\Petzold.htm

where I am assuming that drive letter D: corresponds to your CD-ROM drive.


( 46 )

The CSL Framework

Hopefully I have been successful in my goal of demonstrating Windows programs while teaching the C++ programming language. Perhaps I even have you salivating at the chance to create your own Windows programs using the type of infrastructure that you have observed in my example programs. Well then, this is the page for you.

The New Project Wizard, which was described on the prior page, has 2 steps dedicated to the construction of new Windows programs based upon the CSL Framework. This CSL (Computer Science Lab) framework is a C++ class framework meaning that it consists of a set of C++ classes that are related by inheritance. To base an original program on the CSL framework you merely need to link with the libCSL.a file. The New Project Wizard automatically places the necessary statements in the makefile (.MAK) to link with the libCSL.a file whenever you request a project based upon the CSL Framework.

I'll start by discussing what is good about frameworks, and then I'll mention their limitations.

The main purpose of a framework is to serve as a productivity enhancer. That is, the framework should already have as much functionality as possible. The only thing missing should be the application specific logic that makes each program unique. And the framework should make clear where this application specific code should go. The framework should offer an architecture that makes it easy to tack on the additional code. I picture a framework as a coat rack offering lots of hooks pointing in all directions. When you have something to hang up you can find just the right hook for that object (you are also free to leave a hook empty).

Microsoft's WIN32 API is a collection of a few thousand functions that any Windows program can call. The WIN32 API is most definitely not a framework because it offers no structure, no guidance on when and where to call these functions. The WIN32 API certainly provides infrastructure, but it is a very low level infrastructure. It can be described as a whole bunch of bits and pieces rather than an almost finished Windows program.

Microsoft does offer a framework known as MFC ("Microsoft Foundation Class"). But I don't like it and hence I don't use it. Charles Petzold and many others share this opinion, although MFC is widely used. The main problem with the MFC framework is that it is so confining you keep finding the need to go "under the covers" to get something done via a call to a WIN32 API function. But this means you have to learn the WIN32 API while learning MFC, and this doubles your workload. Once you have learned the WIN32 API you no longer need MFC.

MFC is a good lead-in to what is bad about frameworks. Many frameworks, because they are designed to be almost finished superstructures, lock you into one particular approach. If you are new to Windows programming then being able to see one particular approach is definitely a good thing. But if your goal does not match up with the goal that the framework was designed to address, then you may have a very tough time working around the design of the framework to get your job done. This is the advantage of low-level interfaces such as the WIN32 API. The WIN32 API consists of only bite sized pieces which are compatible with all approaches.

I believe my CSL Framework is the optimum way to employ the WIN32 API in a C++ program. I don't suggest that the CSL Framework will save you from having to learn the WIN32 API. Rather, the CSL Framework provides you with an overall structure (the big picture) that you make specific to your purposes using your knowledge of the WIN32 API.

Remember that you can use CPPIDE's "Help/WIN32 API" menu choice to view Microsoft's documentation for the thousands of functions that make up the WIN32 API. Each page of this documentation is dedicated to a different function. The page will tell you if you need to #include a particular header (.H) file in order to call the function. Some of the pages include sample code showing a typical call to the function. Another place you can find documentation on the WIN32 API is at the Microsoft web site. They are constantly changing their URLs but you can try clicking here.

So what is the design of the CSL Framework? Here it is in a nutshell. The CSL Framework assumes that every Windows program has at least 1 top-level window (a very safe assumption). The CSL Framework requires that there be a C++ class that encapsulates all the details of this top-level window. You instantiate an instance of this top-level window class in the WinMain( ) function. As soon as you call the Create( ) member function of this class your top-level window will make its appearance on the screen.

The CSL Framework further assumes that your top-level window will be composed of some number of child windows (again, a very safe assumption). The details of each of these child windows are encapsulated by child window C++ classes. The top-level window class owns the instances of these child window classes and hence the child windows appear on the pixels owned by the top-level window. When the operator re-sizes the top-level window, the top-level window's WndProc( ) function receives the WM_SIZE message from the operating system and then commands all of the child windows to resize proportionately.

The top-level window is responsible for the application's menu and toolbar and status bar. When the operator selects a particular menu or toolbar choice, the top-level window relays the request on to the necessary child window(s). The top-level window also facilitates communication between the various child windows, since they may not be aware of each other's presence.

This will become clearer with a few examples. Initially, I will be employing the child window classes that come with the CSL Framework. Later I will show you how to invent brand new child window classes. You can intermix any combination of pre-existing and brand new child windows on your top-level window.

The list of child window classes that I am currently distributing with the CSL Framework is shown below:

  • cAudioCD [offers tape deck like pushbuttons for playing an audio CD residing in the CD-ROM drive]
  • cBitmap [can display any picture from a .BMP file]
  • cBreakout [offers the game board for this video game]
  • cComboBox [a standard Windows control]
  • cEditBox [a word processor]
  • cIntBox [accepts an unsigned integer]
  • cListBox [a standard Windows control]
  • cMaze [includes a mouse and a piece of cheese]
  • cMovie [can play any movie from an .AVI file]
  • cPaintGrid [a mosaic we use to construct scrolling signboards]
  • cPeakMeter [a stereo (2 channel) LED bar graph]
  • cProgressBar [a standard Windows control]
  • cSlider [a standard Windows control]
  • cStaticText [useful for labels]
  • cStatusBar [the conventional status bar seen at the very bottom of a top-level window]
  • cToolBar [the toolbar seen just below the menu bar]
  • cWavePlayer [can play any song from a .WAV file]

I consider these child window classes to be fully functional. However you can view them as raw materials since the C++ language allows you to inherit from any class and thereby add new functionality. I have already demonstrated this technique when I showed you how to augment the cBreakout class by using it as the base class for a cBetterBreakout derived class.

While the CSL Framework comes with a number of finished child window classes, it comes with zero top-level classes. It is the top-level class that really makes every Windows program unique. But don't worry. The New Project Wizard will automatically construct your top-level window class. The wizard will accomplish a pretty functional program that awaits your further enhancements. The code created by the wizard will compile and run without you needing to do anything!

So let's see some examples. We will initially assume that we want to create a Windows program that dedicates its entire top-level window for the purpose of showing a movie from an .AVI file. Therefore we will only need to request a single child window class, and the pre-existing cMovie child window class fits the bill perfectly.

To get started you want to either select "Project/New Project..." from CPPIDE's menu or else click on the CPPIDE toolbar button that looks like a blank sheet of paper. This will initiate CPPIDE's New Project Wizard. The New Project Wizard is just a sequence of 5 dialog boxes.

We have already seen Step #1 of the New Project Wizard a number of times. You can select any project name you wish (don't include blanks) but for now request the project name of "Test1". After using the radio control to specify a "Windows" project type and adding a checkmark on the "Employ CSL Framework" checkbox you are ready to click on the "Next" pushbutton.

In the Step #2 dialog you will see that the wizard has provided a default name of "cTest1" for the top-level window class (it is a common convention to use a lower or upper case c as the Hungarian prefix for class names). The wizard makes this suggestion because the top-level window is usually synonymous with the program. The default name is acceptable for our purposes so we are ready to click on the "Next" pushbutton.

In the Step #3 dialog we need to list each of the C++ class names that will be responsible for child windows. We can intermix pre-existing class names and the names of classes that still don't exist. For our present purpose we want to enter "cMovie" in the edit control and then click on the "Next" pushbutton.

The Step #4 dialog allows us to specify additional files that we may need to compile and link into our program. DO NOT list .CPP or .H files associated with child window classes here. But if your program used a cFile class in order to construct .TXT files then you would want to specify cFile.cpp in the left hand edit control and cFile.h in the right hand edit control. For our present purpose we leave this dialog blank and click on the "Next" pushbutton.

We can also leave the Step #5 dialog blank and click on the "Finish" pushbutton.

We then have to wait a second while CPPIDE constructs whatever new files we requested. Once this is complete CPPIDE opens the new project. If you have been following my instructions then the status bar at the bottom of CPPIDE will show that the Test1 project is now open. The Files listbox in the left hand pane of CPPIDE will show that this project consists of the 7 files:

  • AppMain.cpp
  • cTest1.h
  • cTest1.cpp
  • cTest1.rc
  • cTest1Resource.h
  • cTest1.prj
  • cTest1.mak

These files all exist and will compile without error (go ahead, try it!). By clicking on the file names in the left hand pane you can inspect the contents of each of these files. You won't need to interact with the project (.PRJ) and makefile (.MAK) unless you later need to add or delete some file from your project so let's forget about these files. The AppMain.cpp file holds the WinMain( ) entry point. Inside this WinMain( ) function you will observe the following code:

    cTest1    mainWindow;

    mainWindow.Create();

    // We now enter the message loop.

    return theApp.Run();

You can see that we create one instance of our top-level window class and then call its Create( ) member function. This call to Create( ) is what makes the program appear on the screen. Then we call the cWinApp::Run( ) function which is responsible for interacting with the steady flow of Windows messages. Should the operator move the mouse or type on the keyboard or make a selection from the menu then Windows messages will be generated and routed to our top-level window's WndProc( ) function.

It is highly unlikely that you would need to modify the AppMain.cpp file generated by the New Project Wizard.

The next 4 files listed in the Files listbox: cTest1.h, cTest1.cpp, cTest1.rc, and cTest1Resource.h hold the implementation of the top-level window class. You will need to further customize these files in order to add functionality to your program. But they already compile!

Since the only child window that we requested is already provided in the CSL Framework there were no .H, .CPP, or .RC files associated with child window classes added to our project. When we request a brand new child window class our project will have an additional set of .H, .CPP, and .RC files for each such child window.

At this time compile and execute this program. Let's take an inventory of what already works in the program.

The program already displays a standard title bar with Minimize, Maximize, and Close buttons in the upper right hand corner. Furthermore, the program correctly responds to these Minimize, Maximize, and Close buttons. At the far left hand edge of the title bar is an application specific icon. At the moment this icon displays the text "CPP IDE" in two rows. However you can substitute any other icon you want. To do so only requires that you modify the following statement in the cTest1.rc file such that it references a different .ICO file:

IDI_cTest1  ICON  DISCARDABLE  CppIde.ico

The cTest1 program already displays a menu which you can open with the mouse (or with the keyboard using the ALT key if you are an old-timer who can remember that menus predated mice). You can alter the menu to include whatever headings and choices you wish since it is controlled by the following statements found in the cTest1.rc file:

IDR_MainMenu MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&New...\tCtrl+N",        IDM_FILE_NEW
        MENUITEM "&Open...\tCtrl+O",       IDM_FILE_OPEN
        MENUITEM "&Save\tCtrl+S",          IDM_FILE_SAVE
        MENUITEM "Save &As...",            IDM_FILE_SAVE_AS
        MENUITEM SEPARATOR
        MENUITEM "Page Set&up...",         IDM_FILE_PAGE_SETUP
        MENUITEM "Print Pre&view",         IDM_FILE_PRINT_PRE
        MENUITEM "&Print...\tCtrl+P",      IDM_FILE_PRINT
        MENUITEM SEPARATOR
        MENUITEM "E&xit\tCtrl+E",          IDM_FILE_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo\tCtrl+Z",          IDM_EDIT_UNDO
        MENUITEM SEPARATOR
        MENUITEM "Cu&t\tCtrl+X",           IDM_EDIT_CUT
        MENUITEM "&Copy\tCtrl+C",          IDM_EDIT_COPY
        MENUITEM "&Paste\tCtrl+V",         IDM_EDIT_PASTE
        MENUITEM "&Delete\tDel",           IDM_EDIT_DELETE
        MENUITEM SEPARATOR
        MENUITEM "Select &All\tCtrl+A",    IDM_EDIT_SELECT_ALL
        MENUITEM SEPARATOR
        MENUITEM "C&hoose Font...",        IDM_EDIT_CHOOSEFONT
    END
    POPUP "&View"
    BEGIN
        MENUITEM "&Toolbar",               IDM_VIEW_TOOLBAR, CHECKED
        MENUITEM "&Status Bar",            IDM_VIEW_STATUS_BAR, CHECKED
        MENUITEM "&Options...",            IDM_VIEW_OPTIONS
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&Help...",               IDM_HELP_HELP
        MENUITEM "&About cChild",          IDM_HELP_ABOUT
    END
END

All of the identifiers such as IDM_FILE_NEW, IDM_FILE_OPEN, etc. that you see in this .RC file are just macro names for manifest constants that you can find listed in the cTest1Resource.h file:

#define IDM_FILE_NEW              101
#define IDM_FILE_OPEN             102
#define IDM_FILE_SAVE             103
#define IDM_FILE_SAVE_AS          104
#define IDM_FILE_PAGE_SETUP       105
#define IDM_FILE_PRINT_PRE        106
#define IDM_FILE_PRINT            107

#define IDM_FILE_EXIT             121

#define IDM_EDIT_UNDO             201
#define IDM_EDIT_CUT              202
#define IDM_EDIT_COPY             203
#define IDM_EDIT_PASTE            204
#define IDM_EDIT_DELETE           205
#define IDM_EDIT_SELECT_ALL       206
#define IDM_EDIT_FIND             207
#define IDM_EDIT_FINDNEXT         208
#define IDM_EDIT_FINDPRIOR        209
#define IDM_EDIT_REPLACE          210
#define IDM_EDIT_CHOOSEFONT       211

#define IDM_VIEW_TOOLBAR          401
#define IDM_VIEW_STATUS_BAR       402
#define IDM_VIEW_OPTIONS          403

#define IDM_HELP_HELP             601
#define IDM_HELP_ABOUT            602

Under the "File" heading on the menu are the standard choices of "New", "Open", "Save", etc. None of these menu options are functional, except for the File/Exit choice which will shut down the program. I will demonstrate how easy it is to make the other menu choices functional. All of the choices listed under the "View" heading on the menu are already functional. I will discuss these further in a minute. All of the choices listed under the "Help" heading on the menu are already functional. The "Help/About" menu choice causes the program's About Box to appear. You can easily modify the About Box since it is controlled by the following statements in the cTest1.rc file:

IDD_About DIALOG DISCARDABLE  0, 0, 160, 140
                           // x, y, width, height
STYLE WS_POPUP | WS_CAPTION | DS_3DLOOK | WS_SYSMENU
CAPTION "About cChild"
FONT 10, "MS Shell Dlg"
BEGIN
    ICON            IDI_cChild, IDC_STATIC,
                    10,10,32,32
    CTEXT           "cChild.EXE"
                    IDC_STATIC, 30,15,100,15
    CTEXT           "version 1.0"
                    IDC_STATIC, 30,25,100,15
    CTEXT           "Copyright (c) 2005"
                    IDC_STATIC, 30,40,100,15
    CTEXT           "by John Kopplin"
                    IDC_STATIC, 30,50,100,15
    CTEXT           "Distributed by:"
                    IDC_STATIC, 30,70,100,15
    CTEXT           "Computer Science Lab Inc."
                    IDC_STATIC, 30,80,100,15
    CTEXT           "Home page:"
                    IDC_STATIC, 30,90,100,15
    CTEXT           "www.computersciencelab.com"
                    IDC_STATIC, 30,100,100,15

    PUSHBUTTON      "OK", IDOK, 60,120,40,14
END

This is how dialog boxes are described in .RC files. I have provided a short introduction to the language used in .RC files in an appendix of this on-line help document. Additional documentation is available in the comments in the actual cTest1.rc file.

The "Help/Help" menu choice causes the Windows Explorer web browser to display the Help.htm file that is located in the project folder with all the .CPP and .H files. This is a standard HTML (hypertext markup) file such as is used for home pages on the world-wide-web. You can customize this Help.htm file so that it describes the Windows executable that you are creating. Even if you don't know anything about HTML, I bet you can look at my example and figure things out. To see the actual HTML code of any page displayed by Internet Explorer you merely need to select View/Source from Internet Explorer's menu. Windows will then activate Notepad to show you the HTML code, which you can modify to your heart's content. Each time you make a change you need to select File/Save from Notepad's menu and then click on the Refresh button on Internet Explorer's toolbar in order to see the consequences of your latest changes.

Another way you can activate the on-line Help documentation (other than the Help/Help menu choice) is to hit the F1 key on the keyboard. This is a Microsoft standard keystroke for this purpose. The reason that F1 works is the final accelerator statement found in the cTest1.rc file (you can see all the other standard accelerator keystrokes as well):

IDR_MainAccelerator ACCELERATORS DISCARDABLE
BEGIN
    "^N",           IDM_FILE_NEW,           ASCII, NOINVERT
    "^O",           IDM_FILE_OPEN,          ASCII, NOINVERT
    "^S",           IDM_FILE_SAVE,          ASCII, NOINVERT
    "^P",           IDM_FILE_PRINT,         ASCII, NOINVERT
    "^E",           IDM_FILE_EXIT,          ASCII, NOINVERT
    "^Z",           IDM_EDIT_UNDO,          ASCII, NOINVERT
    VK_BACK,        IDM_EDIT_UNDO,          VIRTKEY, ALT, NOINVERT
    "^X",           IDM_EDIT_CUT,           ASCII, NOINVERT
    VK_DELETE,      IDM_EDIT_CUT,           VIRTKEY, SHIFT, NOINVERT
    "^C",           IDM_EDIT_COPY,          ASCII, NOINVERT
    VK_INSERT,      IDM_EDIT_COPY,          VIRTKEY, CONTROL, NOINVERT
    "^V",           IDM_EDIT_PASTE,         ASCII, NOINVERT
    VK_INSERT,      IDM_EDIT_PASTE,         VIRTKEY, SHIFT, NOINVERT
    VK_DELETE,      IDM_EDIT_DELETE,        VIRTKEY, NOINVERT
    "^A",           IDM_EDIT_SELECT_ALL,    ASCII, NOINVERT
    VK_F1,          IDM_HELP_HELP,          VIRTKEY, NOINVERT
END

Continuing on with our inventory, the cTest1 program already displays a toolbar. This toolbar is equipped with standard tool tips which are the helpful messages that appear when you allow the mouse cursor to loiter over a toolbar button. The entire toolbar can be made to come and go based upon the "View/Toolbar" menu choice (try it!). Only 1 of the toolbar buttons is currently functional. This is the "Options" toolbar button which leads to the Options dialog. The New Project Wizard can not predict what options will be relevant to your program so it instead chooses to include the code for the most common types of dialog box controls: a checkbox, an edit control, a radio control, and some pushbuttons. This Options dialog is completely functional, as you can observe.

The cTest1 program is also equipped with a status bar. You can request that this status bar display any desired message via a statement such as:

   m_pStatusBar->UpdatePane( 0, "Washington slept here" );

You can also subdivide the status bar into multiple panes and display different messages in each pane (this is the reason for the first passed parameter to the UpdatePane( ) function shown above). The status bar is also already functional for the job of offering help on the various menu choices. As you use the mouse to wander through the menu you will observe 1 line of text being displayed on the status bar for each menu choice. You can change these messages since they originate from the following statements in the cTest1.rc file:

STRINGTABLE DISCARDABLE
BEGIN
    IDM_FILE_NEW              "Creates a new document"
    IDM_FILE_OPEN             "Opens an existing document"
    IDM_FILE_SAVE             "Saves the active document"
    IDM_FILE_SAVE_AS          "Saves active document with a new name"
    IDM_FILE_PAGE_SETUP       "Changes page layout settings"
    IDM_FILE_PRINT_PRE        "Displays a simulation of printed output"
    IDM_FILE_PRINT            "Prints the active document"
    IDM_FILE_EXIT             "Quits the application"
    IDM_EDIT_UNDO             "Reverses the last action"
    IDM_EDIT_CUT              "Cuts selection and puts it on Clipboard"
    IDM_EDIT_COPY             "Copies selection and puts it on Clipboard"
    IDM_EDIT_PASTE            "Inserts Clipboard contents at insertion point"
    IDM_EDIT_DELETE           "Deletes the selection"
    IDM_EDIT_SELECT_ALL       "Selects the entire document"
    IDM_EDIT_FIND             "Finds the specified text"
    IDM_EDIT_FINDNEXT         "Repeats the last find in the forward direction"
    IDM_EDIT_FINDPRIOR        "Repeats the last find in the backward direction"
    IDM_EDIT_REPLACE          "Replaces specific text with different text"
    IDM_EDIT_CHOOSEFONT       "Selects a new font"
    IDM_VIEW_TOOLBAR          "Shows/Hides the toolbar"
    IDM_VIEW_STATUS_BAR       "Shows/Hides the status bar"
    IDM_VIEW_OPTIONS          "Changes options"
    IDM_HELP_HELP             "Displays the on-line documentation"
    IDM_HELP_ABOUT            "Displays program info, version #, and copyright"
END

These strings are also the source of the text used for the toolbar's tool tip messages.

The entire status bar can be made to come and go based upon the "View/Status Bar" menu choice (try it!).

Completing our inventory of what already works, you can use the mouse to grab the border of the top-level window and resize it. Every time you resize the top-level window the child windows resize proportionately.

Another nice feature already provided in the wizard generated code is that the cTest1 program will automatically resize itself to cover only half of the available screen real estate when you enter the debugger. You should remember that it is critical that the debugger's window (that is, the CPPIDE window) and the window of the program being debugged don't overlap at all. You can modify the [Default Window Placement] statement in the .PRJ file if you prefer that the debugger and the program being debugged sit side by side rather than stacked on top of each other.

I am sure you will agree that the cTest1 program as originally created by the New Project Wizard already has an impressive amount of functionality. Every time you employ the New Project Wizard you will automatically gain this same functionality.

But we're not done yet. Because in this example we requested an existing child window class, our child window is functional also. If you left click on the "Play" pushbutton to start the movie you will be informed that "No .AVI file has been opened!". This is easy to rectify. Simply RIGHT click anywhere on the child window and then select "Open..." from the context menu that pops up. I will remind you again that the presence of the 3 dots (ellipsis) merely indicates that this menu choice leads to a dialog box, as you will observe. You can use the resulting dialog to wander anywhere on your hard drive in search of an .AVI file. I have provided a few sample .AVI files in the CppIde\movies folder (the exact location depends upon the choices you made when you installed CPPIDE).

On the next page we will look at a program that employs 2 child windows. But before I can end this page I have to explain the black console-mode window that appeared as soon as you initiated the cTest1 program. This window is perhaps my greatest gift to you. This console mode window can be used as the destination for whatever printf( ) statements you wish to add to your program. Believe me, this console window is the key to learning the WIN32 API. When you wonder how something works or what value was returned from a function, just slap in a printf( ) statement.

At the moment this may not seem like a breakthrough since you have been using my cMazeDbg, cSignboardDbg, cPeakDetectorDbg, and cBreakoutDbg top-level windows that offer a text pane for printf( ) statements (my naming convention is that a cXYZDbg top-level window splits its screen area between an instance of the cXYZ child window class and an instance of the cEditBox child window class). But when you start writing your own custom Windows programs you may not want a cEditBox child window. And you are suddenly going to lack any place to send printf( ) debugging messages. This is the purpose of the black console-mode window.

The only difference between this printf( ) statement and a printf( ) statement in a console-mode program is that this printf( ) function is a member function of the cDbgConsole class. Therefore you must call the printf( ) function on an instance of the cDbgConsole class. There is such an instance available at global scope (meaning available everywhere throughout your program) thanks to the following statement found in the AppMain.cpp file:

#ifdef _DEBUG
cDbgConsole  g_DbgConsole;
#endif

The Hungarian g_ prefix is the standard way to name a global variable. The _DEBUG macro is defined whenever CPPIDE is performing what is called a Debug build. Debug builds result in larger executable (.EXE) files than the alternative, which is a Release build, because they include extra information needed for the debugger. Therefore the preprocessor #ifdef statement seen in the code snippet above indicates that the g_DbgConsole global variable will only be available in a Debug build. CPPIDE performs Debug builds by default but you can control this via the View/Options choice on CPPIDE's menu. It is the Debug build that you work with to get your program finished. Then you hand out copies of the Release build to your friends. You will no longer need the printf( ) debugging statements when you are ready for a Release build.

Go ahead and use the mouse to resize the top-level window of the cTest1 program we have just created. You will see a new line of text being added to the black console window. This line of text is the result of the following statement:

#ifdef _DEBUG
g_DbgConsole.printf( TEXT( "WM_SIZE msg received, w/h = %u by %u pixels\n" ),
                     uiClientWidth, uiClientHeight );
#endif

To find this statement simply use the Find tool located on CPPIDE's toolbar to search the cTest1.cpp file for the fifth occurrence of the pattern "WM_SIZE". You will notice that this call to the cDbgConsole::printf( ) function is surrounded by a preprocessor #ifdef statement. This prevents a compiler error when you compile a Release build (which lacks the g_DbgConsole instance). If you don't like seeing the black console mode window while working with your Debug build then simply delete these statements.

This topic also gives me an opportunity to introduce another language keyword. The following statement found in the AppMain.cpp file:

#ifdef _DEBUG
cDbgConsole  g_DbgConsole;
#endif

defines a variable (in this case, an object) outside of any function and hence this variable is a global, meaning it is in scope within all of the functions within the AppMain.cpp file. But at the moment it is what is called a local global meaning that it is only global within the AppMain.cpp file. Local globals are sometimes useful, but we want to be able to use a printf( ) debugging statement in ANY file in our project and hence we want the g_DbgConsole variable to be in scope in all of the files of our project. This can be accomplished using the extern keyword.

If you look near the top of the other .CPP files prepared by the New Project Wizard you will observe the following statement in every .CPP file that is part of this project:

#ifdef _DEBUG
extern cDbgConsole  g_DbgConsole;
#endif

Because of the presence of the extern keyword, this last statement does not instantiate a variable (which, in this case, is an object). Instead it references an existing variable that is defined in some other file. In this case the g_DbgConsole variable is defined in the AppMain.cpp file. Thanks to these statements in all of the other .CPP files, the g_DbgConsole variable is not just a local global but is a true global available everywhere throughout the project. The extern statement brings the existing variable into scope throughout the file.

All programming books recommend that you limit your use of global variables. I concur with this reasoning and one of the example programs I have shown you demonstrates how to eliminate global variables. But this particular g_DbgConsole variable, whose only purpose is to allow you to slap a new printf( ) debugging statement at any point in your program, makes an excellent global.


( 47 )

Child Window Classes

Are you impressed with the New Project Wizard and the CSL Framework? Do you see how it gives you a large amount of repetitive code that needs only a minimal amount of customization? I will be discussing how you perform this customization soon, but I still have more features of the New Project Wizard to demonstrate.

Let's start another new project. So initiate the New Project Wizard and -- in Step #1 of the wizard -- request a project named "Test2" that is a Windows program in the C++ language that uses the CSL Framework. Incidentally you will notice that the "Employ CSL Framework" checkbox is disabled if you select either a console-mode program or the C language.

Then in Step #2 of the New Project Wizard accept the default top-level window name of "cTest2".

Then in Step #3 of the wizard let's request two child windows. These could be instances of any child window class, including both the pre-existing classes distributed with CPPIDE or brand new classes that you will write. But for right now request two instances of the cMovie class. This will allow us to have some fun and will also demonstrate that it is acceptable to repeat a child window class. So I am asking you to complete the Step #3 dialog box as shown below:

Leave both Step #4 and Step #5 blank and then click on the "Finish" pushbutton.

After the New Project Wizard finishes creating the files needed for this project it will then open the project. The Files listbox shown in the left-hand pane of CPPIDE will show an AppMain.cpp file and then a set of .H, .CPP, .RC, and Resource.h files for the top-level window named cTest2. If we had requested any instances of child window classes that didn't already exist we would see additional sets of .H, .CPP, and .RC files for these classes.

The wizard generated code is ready to be compiled and run. Do so now. You will then see that the client area of the top-level window is split between two instances of the cMovie child window. You might like to stretch the top-level window to give each child plenty of screen area.

Now move the mouse cursor to the horizontal line that separates the two child windows. Observe that while over this line the mouse cursor changes to a new image. This indicates the presence of a splitter bar which you can click and drag in order to change the relative sizes of the two child windows. This takes a fair amount of code to accomplish, yet the New Project Wizard has automatically built the code for you!

Now right click in the two child windows and "Open" two .AVI files. You can use Microsoft's Find utility (Start/Find/Files Or Folders...) to locate .AVI files on your hard drive and you can download additional .AVI files off the Internet. But I have provided a few .AVI files in the CppIde\movies folder. You can open whatever movies you want. Some movies might be too large (in terms of screen area) to fit within the child windows, even after you have stretched or maximized the top-level window. These movies will still play, they will just be clipped. If you attempt to open the exact same .AVI file in both child windows you will get the error:

ERROR: mciSendCommand(MCI_OPEN) returned 265: MMSYSTEM265. This alias is already in use by this program. Use a unique alias rather than the device name.

I would suggest you open the Highway.avi file in one window and the Skiing.avi file in the other window. I suggest this in order to show you a type of psychedelic color distortion caused by palette exhaustion.

Most recent computers are capable of displaying 65,000 or 16,000,000 million colors simultaneously. With this many colors available both movies will appear fine when they are played simultaneously. However a few years ago most home computers were only capable of displaying 256 colors at a time. The 2 movies in these particular .AVI files require way more than 256 colors and hence cannot look right when played simultaneously under these conditions.

To see this effect you need to restrict your computer to using only 256 colors. You can do this by right clicking anywhere on a vacant spot of your Windows desktop and then selecting "Properties" from the context menu. Proceed to the "Settings" tab of the "Display Properties" dialog and then set the "Colors" control to "256 Colors". Note that the choice labeled "High Color (16 bit)" corresponds to 65,000 colors and the choice labeled "True Color (32 bit)" corresponds to 16,000,000 colors. Remember to return your computer to its original configuration after you finish this experiment.

Whenever your video card is set to 256 color mode, all of the windows you have open are forced to share a common palette of only 256 colors. The Skiing.avi and Highway.avi movies look alright individually but together require more than 256 colors. Depending upon which movie you start playing first the other movie will be forced to use wrong colors for most of its pixels and hence will look pretty psychedelic.

The New Project Wizard gives you a horizontal splitter bar in any project that employs exactly 2 child windows. It is possible to use splitter bars when you have 3 or more child windows, but the necessary code becomes more complicated due to the need to prevent the splitter bars from sliding past each other. The New Project Wizard cannot currently implement this code for you but, as a programmer, you can always add it later. Another thing you can do is convert the horizontal splitter bar into a vertical splitter bar.

Now let's create another new project, but this time let's request 3 child windows. Name this project "Test3" and request 1 instance of the pre-existing cMovie class, 1 instance of a brand new cScore class, and 1 instance of the pre-existing cWavePlayer class. When the New Project Wizard finishes creating this project you will notice that the Files listbox has -- in addition to the files we have seen previously -- a cScore.h, cScore.cpp, cScore.rc, and cScoreResource.h file which together are the implementation of the brand new cScore class. Scan through these files and observe the amount of wizard generated code. This is the code you need to get comfortable with in order to master the CSL Framework. I'll be discussing this code as we go along, but right now let's go ahead and compile and run this project.

The code generated by the New Project Wizard subdivides the top-level window into rows with one row for every child window you request. There are no splitter bars but all 3 child windows evenly share the client area of the top-level window and automatically resize whenever the top-level window is resized. You can load an .AVI file movie into the upper pane and load a .WAV file song into the lower pane and actually play both simultaneously (feeling schizophrenic?). Of the .AVI files that I have provided in the CppIde\movies folder, only the BowlingBall.avi and SwimmingFish.avi movies have sound tracks. You can find two .WAV files in the CppIde folder and another .WAV file in the CppIde\PeakDetector folder.

You are not forced to retain the row layout of the child window panes. You're the programmer. You can accomplish any arrangement of the child window panes. Perhaps you want columns or a checkerboard layout. It's easy to do. The wizard gets you started and you provide the finishing touches.

Let me now show you how easy it is to convert the Test3 project to use columns rather than rows. First let me ask you, "Who is in charge of the layout of the child window panes? The top-level window class or the child window classes?" The answer is, of course, the top-level window. The top-level window commands the sizes and locations of its child windows so as to keep them properly positioned as the top-level window changes. The child windows just do what they are told.

So we want to modify the code in the top-level window class (that's the cTest3 class in this situation) that executes whenever the top-level window is resized. The Windows operating system sends the WM_SIZE message upon this event and hence we can search through the cTest3.cpp file for the fifth occurrence of the pattern "WM_SIZE" and we will discover the following code in the cTest3::MainWndProc( ) function:

LRESULT CALLBACK cTest3::MainWndProc( HWND   hWndMain,
                                      UINT   msg,
                                      WPARAM wParam,
                                      LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_PAINT:
        {
            <some stuff>
            return 0;
        }
        case WM_DISPLAYCHANGE:
        {
            <some stuff>
            return 0;
        }
        case WM_GETMINMAXINFO:
        {
            <some stuff>
            return 0;
        }
        case WM_SIZE:
        {
            UINT    uiSizeType = wParam;          // SIZE_RESTORED, etc.
            UINT    uiClientWidth  = LOWORD( lParam );
            UINT    uiClientHeight = HIWORD( lParam );

#ifdef _DEBUG
g_DbgConsole.printf( TEXT( "WM_SIZE msg received, w/h = %u by %u pixels\n" ),
                     uiClientWidth, uiClientHeight );
#endif

            pThis->OnSize( uiSizeType, uiClientWidth, uiClientHeight );

            return 0;
        }
        ...
    }
}

This is a WndProc( ) ("wind prock") function which means it is the destination for all of the windows messages that the operating system generates for a particular window. The window in this case is our top-level window. I have already explained about the pThis pointer on the Static Member Functions page so return there if you skipped it. Otherwise you should recognize from the code shown above that the cTest3 class calls its OnSize( ) member function whenever it receives the WM_SIZE message.

So now use the Find tool (on CPPIDE's toolbar) to locate the cTest3::OnSize( ) function in the cTest3.cpp file. The first part of this function is boilerplate code that never needs to change. It computes the true client area after subtracting the screen area consumed by the toolbar (if visible) and status bar (if visible). The place where you will need to start making your modifications is right after the following 2 statements:

    UINT    uiAvailWidth = rc.right - rc.left;
    UINT    uiAvailHeight = rc.bottom - rc.top;

You want to replace the rest of the cTest3::OnSize( ) function with the following code. You can even cut and paste from this HTML document to move these exact lines into the CPPIDE editor.

    UINT    uiWidth = uiAvailWidth / 3;

    m_pMovie->Resize( (UINT)(rc.left + 0 * uiWidth), // x
                      (UINT)(rc.top),                // y
                      uiWidth,                       // width (cx)
                      uiAvailHeight,                 // height (cy)
                      true );

    m_pScore->Resize( (UINT)(rc.left + 1 * uiWidth), // x
                      (UINT)(rc.top),                // y
                      uiWidth,                       // width (cx)
                      uiAvailHeight,                 // height (cy)
                      true );

    m_pWavePlayer->Resize(
                      (UINT)(rc.left + 2 * uiWidth), // x
                      (UINT)(rc.top),                // y
                      uiAvailWidth - 2 * uiWidth,    // width (cx)
                      uiAvailHeight,                 // height (cy)
                      true );
    return;

} // end of cTest3::OnSize()

You will observe that we are calling the Resize( ) member function on each child window class instance. This is how you command a child window to resize. If you look in the cMovie.h, cScore.h, or cWavePlayer.h header files you won't find a Resize( ) function listed. However all of these classes (in fact, all child window and top-level window classes) inherit from cBaseWindow. You can look in the cBaseWindow.h file to find the prototype for the Resize( ) function:

 void  Resize( UINT x, UINT y, UINT cx, UINT cy, BOOL bRepaint );

This is another example of code re-use. By using inheritance I have managed to type once yet benefit multiple times. Since all classes associated with windows need a way to be commanded to resize, I placed this code in a common base class.

You can now recompile and run to observe that the 3 child windows are now arranged in columns on the top-level window.

In summary, all it took to convert from a row layout to a column layout is to modify the code in the top-level OnSize( ) function. More specifically, we just had to change the x, y, width, and height parameters passed to the Resize( ) function of each child window. If you consider this a heavy workload then you are not well suited for Windows programming.

The CSL Framework is designed to allow you to place multiple instances of the same child window within a top-level window. And the New Project Wizard is smart enough to support this, with the single proviso that you need to list multiple instances of a particular child window class on ADJACENT lines of the edit control on Step #3 of the New Project Wizard. This limitation means that the wizard code will locate the 2 instances on adjacent rows of the top-level window. But this you can change by editing the OnSize( ) function as I just demonstrated.

In both the original and the reworked OnSize( ) function we had to employ the pointer dereference operator (->) in order to call the Resize( ) member function on each of our three child window instances. This is because the top-level window class (cTest3) possesses pointers to the child window instances. You can observe this in the cTest3.h file. Look in the protected section of the cTest3 class declaration and you will observe:

    // Child windows.
    cToolBar     * m_pToolBar
    cStatusBar   * m_pStatusBar;
    cMovie       * m_pMovie;
    cScore       * m_pScore;
    cWavePlayer  * m_pWavePlayer;

The asterisks (and the p Hungarian prefixes) remind us that these are pointers.

The cTest3 class could be redesigned to hold instances of the child windows rather than pointers to instances. If this was the case then in the cTest3::OnSize( ) function we would have used the dot operator rather than the pointer dereference operator. That is, the statement:

m_pMovie->Resize( ... );

would change to:

m_Movie.Resize( ... );

A natural question is, "Why does the New Project Wizard fabricate code that employs pointers for all of the child windows?" There are a number of reasons. First, remember that the size of a class instance is the sum of the sizes of all of the (non-static) data members. Therefore the cTest3 class is at least as big as 5 pointers because we just observed 5 data members which were pointers. Pointers are not very big. They are only as big as an address and on an x86 microprocessor an address is 32 bits or 4 bytes. Hence 5 pointers only represents 20 bytes. But if the cTest3 class had used non-pointer variables to hold the child window instances then the cTest3 instance would be significantly larger. And this effect is cumulative. I haven't shown you the code that makes up the cMovie and cWavePlayer child window classes, but those classes are comprised of even smaller child windows which are represented in their parent instances by data members. And those child windows might again just be aggregations of even smaller child windows. So the use of pointers saves a considerable amount of memory.

Another reason to employ pointers is that this is how Microsoft's COM technology works. This is not a course on COM but it never hurts to get you used to how real-world code looks. COM employs pointers because pointers involve indirection and indirection allows for all sorts of handy tricks. A trick available in COM is that you can ask for an object that you wish to employ to get something done. You will be given a pointer which you will use like you use any other pointer. But unknown to you the code that you are actually executing could be located on your own computer or on a different computer located half way around the world. That is a neat trick made possible by indirection.

The final reason that I decided to use pointers for the child window instances in my CSL Framework classes is that this decision guarantees you won't make a mistake when employing a child window as a passed parameter to a function. Remember that the C language employs call by value for all parameters passed to functions. There are many situations where you will want to employ a child window instance as a passed parameter to a function. If you had available a non-pointer child window instance then you might accidentally forget to employ the address of operator (&) when passing that instance to a function. Consequently your program would suffer a performance penalty due to the fact that an entire copy of the child window instance would have to be constructed before the called function could begin executing. But if you only have available a pointer to the child window instance then you can't possibly make this mistake. I love designing code today that prevents me from screwing up tomorrow.

Switching topics now, I need to point out that the cToolBar and cStatusBar child window classes are built into the CSL Framework but you NEVER list these classes in Step #3 of the New Project Wizard. This is because the cToolBar and cStatusBar classes are just slightly different than all the other child window classes. The New Project Wizard gives every project an instance of these 2 classes. I have shown you how the View menu allows the operator to hide either of these 2 child windows if he doesn't want to see them. The code necessary to hide the toolbar and the status bar is provided by the New Project Wizard and is completely functional.


Extending CSL

( 48 )

Extending The CSL Framework

Now are you impressed with the New Project Wizard and the CSL Framework? Boy, you're a hard sell. Perhaps you are smart enough to realize that even though I provide a selection of pre-existing child window classes, the framework would be no good unless you can extend it. And indeed you can.

My attitude while writing this page is best described by that old maxim, "Give a man a fish and you feed him for one day. Teach a man to fish and you feed him for a lifetime." I want to convince you that the majority of Windows programs can be accomplished using the CSL Framework. And every time you create a new child window class you have created a reusable component that can be effortlessly deployed in any number of future programs.

The best way to write Windows programs is to steal code from existing programs. Microsoft sells a Platform SDK (software development kit) for only about $10 that holds a huge collection of Windows programs. When I am trying to employ one of the WIN32 API functions I search through the Platform SDK to see how that function is employed in those programs. Unfortunately, while the Platform SDK has a large number of programs, it has very little documentation to explain the thought process of the author's of those programs. You can read about the Platform SDK (and order the CD-ROM) on the following page found on the Microsoft web site: http://www.microsoft.com/msdownload/platformsdk/sdkupdate/

The largest single repository of documented Windows code that I know of is the Petzold book that I refer to in about every third sentence. So I am going to take one of the example programs from that book and show you how you go about modifying that code so it can serve as a child window class. We will develop a new child window class that can be reused again and again on different top-level windows (or even placed within another child window).

The reason I asked you on the previous page to use cScore as the name of the new child window class that we requested for the Test3 project is that I had in mind creating a child window which could display the score for our Breakout game. I recalled seeing in the Petzold book (chapter 8, page 338 to be exact) a program which drew a digital clock using crude numerals known as "7 segment displays". If you have access to this book then you will want to compare Petzold's DIGCLOCK.C program to what I am about to show. I think you will realize that you have just been taught to fish.

Each digit of a 7 segment display can show one of ten possible images corresponding to the ten numerals. In order to form a particular numeral we just need to know which of these 7 lamps (segments) to illuminate. The design of each digit is explained in the following illustration.

In order to remember the pattern of segments that need to be illuminated to form each of the 10 numerals, Petzold decided it would be handy to employ a two-dimensional array. He named this array fSevenSegment using the f Hungarian prefix to indicate "flag" which is just another name for a Boolean; i.e., something that is either on or off (true or false). I will use the b prefix and hence I'll name this array bSevenSegment.

No, wait. I'll name it m_bSevenSegment because I am programming in the C++ language so it makes sense to convert Petzold's static array to a class data member. Almost all of the global and static variables that you observe in Petzold's C language code will function better as data members in a C++ program.

No, hold on. I'll name it s_bSevenSegment since I will make it a static data member. If you can't remember that a static data member means that all instances of the cScore class will share the same memory then you need to review the page on static data members.

We also need to describe the geometry of each segment. Petzold has decided to use the Polygon( ) function to draw the segments. This Polygon( ) function is one of the thousands of functions available in the WIN32 API. Its function prototype -- which you can observe from within CPPIDE by selecting "Help/WIN32 API" from CPPIDE's menu -- is shown below:

BOOL  Polygon( HDC hdc,,                // handle to DC
               const POINT *lpPoints,   // polygon vertices
               int nCount );            // count of vertices

An HDC is a "handle to a device context", which is the imaginary world wherein reside pens and brushes that you can use to draw on the screen. I don't explain device contexts or pens or brushes, etc. because I don't want to spend my time repeating Petzold (what, has it already been 3 more sentences?). Anyway, in order to call the Polygon( ) function to draw one of the seven segments we need an array of POINTS for the second passed parameter. The POINT data type is simply a name for the following struct:

typedef struct tagPOINT
{
    LONG x;
    LONG y;
} POINT;

So a POINT struct is just a convenient way to package together an x and y coordinate pair. If you can't remember that a typedef statement just creates an alias for a data type then you need to review this page. Note that you can verify that Microsoft defines the POINT struct in this manner by searching CPPIDE's WIN32 API documentation for the word POINT.

The particular polygons that we want to use for our segments require 6 vertices (see the prior illustration). So we will be using a 6 as the third passed parameter to the Polygon( ) function.

Since we need 7 segments (polygons) then we need 7 * 6 POINTS. Petzold defines a ptSegment variable as an array of POINTS in order to hold these 43 x/y pairs. I will call this the s_ptSegment array since pt is the Hungarian prefix for the POINT data type and I decide this should be class static data member. I initialize this array just as Petzold did:

POINT cScore::s_ptSegment[7][6] =
{
   7,  6,  11,  2,  31,  2,  35,  6,  31, 10,  11, 10, // x,y,x,y,x,y...
   6,  7,  10, 11,  10, 31,   6, 35,   2, 31,   2, 11,
  36,  7,  40, 11,  40, 31,  36, 35,  32, 31,  32, 11,
   7, 36,  11, 32,  31, 32,  35, 36,  31, 40,  11, 40,
   6, 37,  10, 41,  10, 61,   6, 65,   2, 61,   2, 41,
  36, 37,  40, 41,  40, 61,  36, 65,  32, 61,  32, 41,
   7, 66,  11, 62,  31, 62,  35, 66,  31, 70,  11, 70
};

You should be able to find the numbers seen in the first line of the array initialization values in the prior illustration.

Because I am not trying to display the time I can dispense with Petzold's code that involves the WM_TIMER message and which paints colons to separate the hours from the minutes and the minutes from the seconds.

Besides the 2 arrays I have already mentioned, we need pull only a handful of other statements from Petzold's DIGCLOCK.C example program in order to complete our cScore child window class.

Let's now cut to the chase. Go ahead and open the "ThreePanes" example project that I have provided. You will observe that it started out as the Test3 program where I requested 3 child windows, those being a cMovie instance, a cScore instance, and a cWavePlayer instance. However I requested the name cThreePanes rather than cTest3 for my top-level window class. I then modified the cScore.h, cScore.cpp, cScore.rc, and cScoreResource.h files in order to finish the code started by the New Project Wizard. I will walk you through every one of my changes so you can see how easy this technique is. But at this time go ahead and compile and run the program to observe that our new cScore child window class is displaying numerals and resizes appropriately. Furthermore, the cScore child window offers a functional right click context menu. Go ahead and try it.

The reason the cScore child window initially displays a particular score is that I added a SetScore( ) member function to the cScore class and I then call this SetScore( ) function in my cThreePanes::Create( ) function. cThreePanes is the name of my top-level window class. Use CPPIDE's Find tool (located on the toolbar) to search for the first occurrence of the SetScore pattern in the cThreePanes.cpp file. You will discover in the cThreePanes::Create( ) function the following code:

    // Create the cScore child window.

    m_pScore->Create( this,                  // parent window class
                      0,0,0,0,               // x,y,cx,cy
                      0,                     // extra window style flags
                      IDC_Score );           // child window ID

    m_pScore->SetScore( 2340 );  // TEMPORARY for demo purposes only

    m_pScore->Show();                        // make the window visible

In order to differentiate between the code created by the New Project Wizard and the code I had to add, I will print every statement that was not generated by the New Project Wizard in red.

This call to the SetScore( ) function really doesn't belong here but it gives you something to see and allows you to verify that you can rezero the score using the right click context menu.

Let's now look at the cScore class declaration found in the cScore.h header file:

class cScore : public cBaseWindow
{
public:
    // Constructors
    cScore();
    virtual ~cScore();

    // Events
    // All event publishers must supply a public enumeration listing
    // their event numbers.  Event subscribers should use these
    // enumeration values rather than simple ints when subscribing
    // to events in order to help the compiler catch programming errors.

    enum cScoreEvents { eUnsupportedEvent };

    // Interface
    BOOL           Create( cBaseWindow * pParent,
                           UINT x, UINT y, UINT cx, UINT cy,
                           DWORD dwWS_Flags,   // extra window style flags
                           UINT  uiChildID,    // used for the hMenu param
                                               //   passed to CreateWindow()
                           TCHAR * pszObjName = NULL );
                                               // arbitrary component name

    inline BOOL    SetScore( UINT uiNewScore )
    {
            m_uiCurrentScore = uiNewScore;
            InvalidateRect( m_hWnd, NULL, true );  // force a repaint
            return true;
    };

    inline UINT    GetScore() { return m_uiCurrentScore; };

    inline BOOL    RezeroScore()
    {
            m_uiCurrentScore = 0;
            InvalidateRect( m_hWnd, NULL, true );  // force a repaint
            return true;
    };

    inline BOOL    AdvanceScore( UINT uiIncrement )
    {
            m_uiCurrentScore += uiIncrement;
            InvalidateRect( m_hWnd, NULL, true );  // force a repaint
            return true;
    };

    BOOL           ShowChooseColorDlg( HWND  hWnd );

    virtual const TCHAR * const GetClassName() const;
    // Note that a similar GetObjName() function is inherited from cCallbackObj.

protected:
    void           OnSize( UINT nType,
                           UINT uiClientWidth, UINT uiClientHeight );
    void           OnPaint();

    void           DisplayScore( HDC hDC );
    void           DisplayDigit( HDC hDC, UINT uiNumeral );

    //  Any WndProc() that is to be a class member function must be
    //  declared as "static".

    static LRESULT CALLBACK cScoreWndProc( HWND hWnd, UINT msg,
                                           WPARAM wParam, LPARAM lParam );

    // Data
    static UINT    s_cRef;             // reference count as used in COM
    static const TCHAR * const s_pszClassName;
    TCHAR          m_szFileName[MAX_PATH];
               // WARNING: this must be dimensioned to MAX_PATH because
               //          of its use with GetOpen/SaveFileName().
    ULONG          m_dwFilterIndex;    // used with GetOpen/SaveFileName(),
                                       // controls the default filename suffix

    UINT           m_uiCurrentScore;   // the game score
    UINT           m_cxClient;         // width of client area
    UINT           m_cyClient;         // height of client area
    static BOOL    s_bSevenSegment [10][7];  // describes illumination patterns
    static POINT   s_ptSegment [7][6];       // describes segment geometry
    COLORREF       m_crSegments;       // color used for segments
    HBRUSH         m_hbrSegments;      // brushed used to paint segments
    COLORREF       m_crCustColors[16]; // used with ::ChooseColor()
};

Concentrating on the lines in red, you can observe that I added some public and private member functions and some private data members. All of the member functions that are only 1 or 2 statements long I decided to inline which means that their implementation is placed in the .H header file. The implementations of the other member functions are found in the .CPP file. The keyword inline is optional, by the way. If the function's body is placed in the .H file then the compiler will inline it. I just like using the inline keyword to better document this.

I will generally invent a Get...( ) function anytime I invent a Set...( ) function. This is the reason for the GetScore( ) function which currently is unused. The RezeroScore( ) function is used by the right click context menu (more on this later). The AdvanceScore( ) function allows you to increment the score without knowing its prior value and hence is likely to get more use in a game than the SetScore( ) function. The ShowChooseColorDlg( ) function is used by the right click context menu.

Those functions are all public meaning anybody who holds an instance of the cScore class can call them. But the DisplayScore( ) and DisplayDigit( ) functions are private (where I favor the protected keyword over the private keyword) because we don't want the outside world messing around with our display. The outside world can request a score but only we get to talk to the guy running the display.

You will always want to make your class data members private, as I do in the cScore class. The m_uiCurrentScore data member is perhaps the most important, since it remembers the score. You then see the m_cxClient and m_cyClient data members. "cx" and "cy" is Microsoft speak for a width and a height. In this case it is the width and height of our client area; i.e., the screen real estate we can use to draw the 7 segment display. We receive a WM_SIZE message each time our client area changes. The next data members are the s_bSevenSegment and s_ptSegment arrays that we took from the Petzold program. The m_crSegments data member remembers the color selected by the operator for the display. We use this color to construct a brush which is held in the m_hbrSegments data member. We need the m_crCustColors[16] data member because we want to employ Microsoft's ChooseColor( ) function to allow the operator to switch colors. This is a new capability (hold on to see the code) which is not found in Petzold's DIGCLOCK.C program.

This is probably a typical amount of customization that you will need to make in a header file originally prepared by the New Project Wizard.

Now let's look at the cScore.cpp implementation file. Near the top of the cScore.cpp file you will find the definitions of the two static data member arrays:

BOOL  cScore::s_bSevenSegment[10][7] =
{
   1, 1, 1, 0, 1, 1, 1,     // describe numeral 0
   0, 0, 1, 0, 0, 1, 0,     // 1
   1, 0, 1, 1, 1, 0, 1,     // 2
   1, 0, 1, 1, 0, 1, 1,     // 3
   0, 1, 1, 1, 0, 1, 0,     // 4
   1, 1, 0, 1, 0, 1, 1,     // 5
   1, 1, 0, 1, 1, 1, 1,     // 6
   1, 0, 1, 0, 0, 1, 0,     // 7
   1, 1, 1, 1, 1, 1, 1,     // 8
   1, 1, 1, 1, 0, 1, 1      // 9
};

POINT cScore::s_ptSegment[7][6] =
{
   7,  6,  11,  2,  31,  2,  35,  6,  31, 10,  11, 10, // x,y,x,y,x,y...
   6,  7,  10, 11,  10, 31,   6, 35,   2, 31,   2, 11,
  36,  7,  40, 11,  40, 31,  36, 35,  32, 31,  32, 11,
   7, 36,  11, 32,  31, 32,  35, 36,  31, 40,  11, 40,
   6, 37,  10, 41,  10, 61,   6, 65,   2, 61,   2, 41,
  36, 37,  40, 41,  40, 61,  36, 65,  32, 61,  32, 41,
   7, 66,  11, 62,  31, 62,  35, 66,  31, 70,  11, 70
};

I will leave it up to you to figure out how the ones and zeros seen in the s_bSevenSegment array specify the particular pattern of lights that form the shape of each numeral.

A little later in the cScore.cpp file you will see that I added a few items to the initializer list of the cScore constructor and to the body of this constructor:

cScore::cScore()
    : cBaseWindow( eUnsupportedEvent ),      // NULLs m_hWnd & m_pParent
                         // Since eUnsupportedEvent is the first unsupported
                         // event number, it is also the # of supported events.
      m_dwFilterIndex( 1 ),                  // 0 indicates the custom filter

      m_uiCurrentScore( 0 ),
      m_crSegments( RGB( 255,0,0) ),         // red
      m_hbrSegments( NULL )
{
    m_dwStyle = WS_CHILD | WS_TABSTOP
              | WS_BORDER,

    m_dwExStyle = WS_EX_CLIENTEDGE
                | WS_EX_NOPARENTNOTIFY;      // don't issue WM_PARENTNOTIFY msgs

    m_szFileName[0] = (TCHAR) 0;

    // Create the brush used to paint the segments.

    m_hbrSegments = CreateSolidBrush( m_crSegments );

    m_crCustColors[0] = RGB( 255, 0, 0 );     // red
    m_crCustColors[1] = RGB( 0, 255, 0 );     // green
    m_crCustColors[2] = RGB( 0, 0, 255 );     // blue

    for ( UINT i = 3; i < 16; i++ )
        m_crCustColors[i] = RGB( 0, 0, 0 );   // black
}

In this code you can see a few uses of the RGB( ) function (it's actually a macro) which is part of Microsoft's WIN32 API. This is how you request a color by specifying its red, green, and blue components. Each component is described by an unsigned byte and hence ranges from 0 to 255. Hence pure red is RGB( 255, 0, 0 ) while yellow is RGB( 255, 255, 0 ).

CreateSolidBrush( ) is another of the thousands of functions provided by Microsoft in their WIN32 API. This function returns an HBRUSH which is a handle to a brush. A handle is a data type but it was invented by Microsoft and is not part of the C++ language. Because the operating system has only a limited number of brushes, we must eventually return our brush handle. This is why I decided to construct the brush in the constructor. I could then return the brush in the destructor. The C++ language guarantees that both the constructor and destructor will be executed and hence I will have the brush while I need it and I will return it when I am done with it. You can see me return the brush in the code I added to the cScore destructor:

cScore::~cScore()
{
    if ( m_hbrSegments )
    {
        DeleteObject( m_hbrSegments );
        m_hbrSegments = NULL;
    }

    s_cRef--;                           // decrement the reference cnt

    if ( s_cRef == 0 )
    {
        UnregisterClass( s_pszClassName, g_pWinApp->GetInst() );
    }

    // ~cBaseWindow() sets m_pParent = NULL and m_hWnd = NULL.
}

When developing a new child window class for the CSL Framework the only windows messages that you will usually need to deal with are the WM_SIZE, WM_PAINT, and WM_COMMAND messages. And that is the case here. It is the all-important WndProc( ) function that receives these messages. This function is summarized below:

LRESULT CALLBACK cScore::cScoreWndProc( HWND   hWndMain,
                                        UINT   msg,
                                        WPARAM wParam,
                                        LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_CONTEXTMENU:
        {
            <some stuff>
            return 0;
        }
        case WM_COMMAND:
        {
            <some stuff>
            return 0;
        }
        case WM_SIZE:
        {
            UINT    uiSizeType = wParam;
            UINT    uiClientWidth  = LOWORD( lParam );
            UINT    uiClientHeight = HIWORD( lParam );

            pThis->OnSize( uiSizeType, uiClientWidth, uiClientHeight );
            return 0;
        }
        case WM_PAINT:
        {
            pThis->OnPaint();
            return 0;
        }
        ...
    }
}

I have already explained the pThis pointer on the Static Member Functions page so return there if you skipped it. Otherwise you should recognize from the code shown above that the cScore class calls its OnSize( ) member function whenever it receives the WM_SIZE message and it calls its OnPaint( ) member function whenever it receives the WM_PAINT message.

Although there is presently no red ink shown in this WndProc( ) function, we will have to come back and insert some code in the WM_COMMAND section in order to make our right click context menu functional. But for right now we are going to trace through the OnSize( ) and OnPaint( ) functions.

The changes that we need to make to the wizard created OnSize( ) function are trivial:

void cScore::OnSize( UINT nType, UINT uiClientWidth, UINT uiClientHeight )
{
    if ( uiClientWidth == 0 || uiClientHeight == 0 )
        return;

    m_cxClient = uiClientWidth;
    m_cyClient = uiClientHeight;

    return;
}

The m_cxClient and m_cyClient data members allow us to keep track of the width (cx) and height (cy) of our client area. This way we can keep adjusting the size of our 7 segment display to use all of the available pixels.

Here are the changes needed in the OnPaint( ) function:

void  cScore::OnPaint()
{
    HDC             hDC;                // handle of a device context
    PAINTSTRUCT     ps;                 // populated by BeginPaint()

    hDC = BeginPaint( m_hWnd, &ps );

    SetMapMode( hDC, MM_ISOTROPIC );
    SetWindowExtEx( hDC, 276, 72, NULL );  // child window width = 276 logical units
                                           // child window height = 72 logical units
       // Note that each numeral of the 7 segment display
       // occupies a 42 wide by 72 tall cell.
    SetViewportExtEx( hDC, m_cxClient, m_cyClient, NULL );
    SetViewportOrgEx( hDC, m_cxClient, 0, NULL );
       // The logical origin (x,y = 0,0) is the upper right corner of child window.
    SelectObject( hDC, GetStockObject( NULL_PEN ) );
    SelectObject( hDC, m_hbrSegments );

    DisplayScore( hDC );

    EndPaint( m_hWnd, &ps );

    return;
}

Besides the 2 static arrays, these are the only other statements that actually came from the Petzold DIGCLOCK.C program. Petzold decided to assign a width of 276 logical units and a height of 72 logical units to the client area. He also designed each digit of his 7 segment display to cover 42 by 72 logical units (look again at the illustration at the top of this page). You'll notice that the width is not an even multiple of 42. Petzold was designing a clock and the colons that separate the hours from the minutes and the minutes from the seconds did not require as much width as the numerals. I could have changed the width to some number that was a multiple of 42 but I didn't want to throw those of you who have the Petzold book. But I do employ a different origin for the viewport because I print the digits of the score in a right to left order while Petzold prints the digits of the time in a left to right order.

I replaced Petzold's DisplayTime( ) function with a DisplayScore( ) function. DisplayScore( ) calls DisplayDigit( ) and that is where you will find the call to the WIN32 Polygon( ) function.

If we didn't care about offering a right-click context menu then we would now be done with the changes that we need to make to the code that was generated by the New Project Wizard for the cScore class. Petzold's original DIGCLOCK.C program has 100 statements whereas after typing only 47 statements (beyond those generated for us) we have achieved something significantly more valuable: we have built a reusable component that can be employed on the top-level window of any number of future CSL Framework projects!

And with only a few more statements we can add capability not present in the DIGCLOCK.C program. Specifically, I am going to push on here without a bathroom break and show you how to give the cScore child window class a functional context menu. Remember, I am teaching you to fish so what I am going to show you applies to all of the original child window classes you might decide to construct.

The New Project Wizard generates a place-holder context menu which you can observe if you RIGHT click anywhere on a brand new child window. This default context menu results from the following statements placed into the .RC file by the wizard:

IDR_cScoreContextMenu MENU DISCARDABLE
BEGIN
    POPUP "Dummy"
    BEGIN
        MENUITEM "Context Menu Entry #1...",  IDM_cScoreContext1
        MENUITEM "Context Menu Entry #2...",  IDM_cScoreContext2
        MENUITEM "Context Menu Entry #3...",  IDM_cScoreContext3
    END
END
The macros such as IDM_cScoreContext1, IDM_cScoreContext2, etc. are just manifest constants defined in the cScoreResource.h file. I decided to change the .RC file to read:
IDR_cScoreContextMenu MENU DISCARDABLE
BEGIN
    POPUP "Dummy"
    BEGIN
        MENUITEM "Rezero Score",  IDM_cScoreRezeroScore
        MENUITEM "Choose Color",  IDM_cScoreChooseColor
    END
END

Since I changed the macro names I had to make corresponding changes in the cScoreResource.h file:

#define IDM_cScoreRezeroScore      101
#define IDM_cScoreChooseColor      102

Next we need to provide some code that will be activated upon receipt of the IDM_cScoreRezeroScore or IDM_cScoreChooseColor message. Like all context menu selections, these messages are actually a specific flavor of the WM_COMMAND message. We detect and respond to these particular messages using the code shown below:

LRESULT CALLBACK cScore::cScoreWndProc( HWND   hWndMain,
                                        UINT   msg,
                                        WPARAM wParam,
                                        LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_CONTEXTMENU:
        {
            <some stuff>
            return 0;
        }
        case WM_COMMAND:
        {
            WORD     wNotifyCode = HIWORD( wParam ); // notification code
            WORD     wID         = LOWORD( wParam ); // menu item, control, or
                                                     //   accelerator identifier
            HWND     hWndChild   = (HWND) lParam;
            HMENU    hMenu       = GetMenu( hWnd );

            switch ( wID )
            {
                case IDM_cScoreRezeroScore:
                {
                    pThis->m_uiCurrentScore = 0;
                    InvalidateRect( hWnd, NULL, true );  // force a repaint
                    return 0;
                }
                case IDM_cScoreChooseColor:
                {
                    if ( pThis->m_hbrSegments != NULL )
                        DeleteObject( pThis->m_hbrSegments );

                    pThis->ShowChooseColorDlg( hWnd );

                    pThis->m_hbrSegments = CreateSolidBrush( pThis->m_crSegments );
                    InvalidateRect( pThis->m_hWnd, NULL, true );  // force a repaint
                    return 0;
                }

                default:
                    break;

            } // end of switch (wID)

            break;
        }
        ...
    }
}

You should be able to tell from this code snippet that we handle the IDM_cScoreRezeroScore message entirely within this WndProc( ) function whereas we call the ShowChooseColorDlg( ) member function upon receipt of the IDM_cScoreChooseColor message.

I will let you look at the ShowChooseColorDlg( ) function on your own. It is only 13 statements long and is pretty typical of what you see when using Microsoft's WIN32 API.

Even after adding the right click context menu we have still only typed 90 statements beyond those generated by the New Project Wizard. The next time you start a new top-level window and decide to include an instance of the cScore child window you will just need to mention the cScore component to the New Project Wizard. You will need to type 0 statements beyond those generated by the wizard in order to get this component to appear on your top-level window. Furthermore, the right click context menu will be functional. How's that for productivity!

But while the New Project Wizard can supply the code that makes a child window appear, the wizard is not smart enough to write the code that allows direct interaction between the parent window and the child window. The New Project Wizard can make the child window appear because the Create( ) member function, which makes a child window appear, has been standardized among all of the child window classes. But the wizard doesn't know anything about the SetScore( ), AdvanceScore( ), etc. functions that make the cScore class unique from all the other child window classes. You have to supply the code that calls the class specific functions.

The New Project Wizard places the text TODO in the code that it generates at those locations where it suspects you will want to add customization. You can use CPPIDE's Find tool to locate this sentinel. Microsoft's Visual C++ wizards generate code that has this same TODO marker so again you can see that I have designed CPPIDE to be quite similar to Microsoft's more expensive Visual C++ IDE.


( 49 )

Unicode

If you have been inspecting my code with sharp eyes you have probably noticed that I have glossed over some strange constructs. You may have seen a TCHAR in a spot where this HTML document claims there is a char. Or you may have seen a call to the _tcslen( ) function in a place where this HTML document claims there is a call to strlen( ). This all has to do with Unicode which is a relatively new standard for encoding alphabet characters and is intended to replace ASCII. The char data type indicates an ASCII char which is 1 byte or 8 bits. Using 8 bits of memory you can store no more than 256 different values. That is sufficient for encoding all the lower and upper-case letters of the English alphabet, the 10 numerals, the punctuation found on a computer keyboard, some invisible ("unprintable") control codes such as '\t' for tab and '\n' for newline, and a bunch more stuff. But an 8 bit char is completely insufficient for encoding the alphabet used in many other written languages such as Japanese Kanji.

Therefore an international standards organization set up an alternative to ASCII called Unicode. The big change is that every character is given 16 bits (2 bytes). The data type used to hold a single UNICODE character is the wchar_t data type. With 16 bits of memory, 65,536 different symbols ("glyphs") can be placed in each wchar_t memory location. This is claimed to be sufficient to encode all the major alphabets of the world. The ideographs used in China, Japan, and Korea take up almost half of the available space. The familiar ASCII characters are assigned 256 of the 65,536 codes.

I haven't previously brought up Unicode because most introductory C and C++ books don't mention it. Therefore I wanted my code to look like whatever other code you may be looking at.

But Microsoft has jumped onto the Unicode bandwagon and supports it in their Windows NT, 2000, and XP operating systems. Unfortunately, Microsoft does not support Unicode in their Windows 95, 98, or ME operating systems. This puts software developers in a bit of a bind. How can they write programs that are intended for both operating systems? Well this isn't really a problem since Windows NT, 2000, and XP still support ASCII. So all of my software and most other Windows programs that you come across are still built using ASCII.

Nonetheless, Microsoft has developed a nice infrastructure that allows me to construct my program in a way that means it is immediately ready to adopt Unicode when the day comes that I need to provide a user interface in a foreign language (and can afford to abandon Windows 95/98/ME). This infrastructure is nothing more than a collection of handy macros such as those shown below. Note that you can find these #define preprocessor statements in the <tchar.h> header file.

#ifdef _UNICODE

  typedef wchar_t  TCHAR;

  #define _tcslen wcslen
  #define _tcscpy wcscpy
  #define _tcscmp wcscmp

  #define TEXT(x)    L ## x

  #define _tWinMain  wWinMain

#else

  typedef char     TCHAR;

  #define _tcslen strlen
  #define _tcscpy strcpy
  #define _tcscmp strcmp

  #define TEXT(x)   x

  #define _tWinMain  WinMain

#endif

The net effect of using these macros is that by default a call to _tcslen( ) becomes (after the preprocessor does its thing) a call to strlen( ) and a statement that defines a TCHAR variable is really defining a char variable. In other words, everything is normal. But should you decide to #define the _UNICODE macro then suddenly that call to _tcslen( ) becomes a call to the wcslen( ) function (which can deal with Unicode strings) and the statement that defines a TCHAR variable is now defining a wchar_t variable, which is a 16 bit Unicode character.

One thing to remember when coding for Unicode is that strings (arrays of characters) are still NULL terminated but that NULL at the very end is now a zero value that takes up 16 bits just like all the other characters in the string.


Built-in Classes

( 50 )

The Built-In Child Window Classes

This page is intended to help you employ the child window classes that I distribute with my CSL Framework. The authoritative documentation on these classes is, of course, the public sections of the C++ class declarations found in the corresponding .H header files. You can find these header files in the CppIde\include folder (the exact location depends upon the choices you made when you installed Computer Science Lab). Hopefully I have selected function names and passed parameter names that help you discern their purpose and correct usage. But a little English always helps.

cAudioCD

This class offers a child window that can play an audio CD located in your computer's CD-ROM drive. You control the CD using a set of pushbuttons for Play, Pause, Rewind, etc. This child window is fully functional via its pushbuttons (assuming an audio CD is located in the CD-ROM drive) but you can also control playback programmatically via the following class member functions:

    BOOL    Open();
    BOOL    SetTrack( UINT nTrack );
    BOOL    Play();     // from current position
    BOOL    Stop();     // after stopping, play resumes from first track
    BOOL    Pause();    // after pausing, play resumes from last position

These are merely the principal member functions, there are additional member functions listed in the public portion of the class declaration. Before the Play pushbutton [or a call to the Play( ) member function] will work you have to open the device driver. You can do this either via the right click context menu or programmatically by calling the Open( ) member function. Have an audio CD in the CD-ROM drive before you do this. The cAudioCD class is designed to play only a single track (song) at a time so you will have to click on the Next Track pushbutton and then the Play pushbutton to hear the next song.

Note that as soon as you insert an audio CD into your CD-ROM drive some program (perhaps Microsoft's "CD Player" or "Windows Media Player") will start up and play that disc. While that program is controlling the CD-ROM drive, the cAudioCD class will NOT be able to control the CD-ROM drive. If you try anyway you will get an error message that mentions mciSendCommand and says, "the device driver is already in use". Simply terminate whatever other program is controlling the CD-ROM drive and the cAudioCD class should then work.

cBitmap

This class offers a child window that can display the picture located in any .BMP ("bitmap") file. You can use the right-click context menu to specify the .BMP file you want to open and to print the picture. The context menu also allows you to toggle in to and out of "scale to fit" mode. If you select the "scale to fit" mode then the picture will be stretched or compressed to fit the available child window area. If you do not select "scale to fit" then the picture will be displayed at its correct resolution, which probably means it won't exactly fit the available child window area. When too little room is available a set of scrollbars will appear that allow you to view the entire picture (but not all at once).

The principal member functions for the cBitmap class are listed below although additional member functions can be found in the public portion of the class declaration.

    BOOL     Open( TCHAR * pszFileName );
    BOOL     OnFileOpen();
    BOOL     ScaleToFit( BOOL bScaleToFit );
    BOOL     PrintBitmap();

The Open( ) member function allows you to programmatically specify the .BMP file holding the bitmap you want to display. The OnFileOpen( ) member function causes the standard Windows "Open" dialog to appear which allows the operator to browse and thereby find a .BMP file located anywhere on the hard drive. The ScaleToFit( ) member function can be used to select whether you want the bitmap stretched or shrunk to fit within the available screen real estate or whether you want the bitmap shown in its native size, using scroll bars when necessary. The PrintBitmap( ) member function displays the standard Windows "Print" dialog which allows you to print the bitmap picture. Note that the Scale To Fit option also applies to printing.

When you are not scaling the picture to fit the available area, and more area is available than is needed for the picture, the cBitmap child window will not completely fill its assigned space on the top level window. This doesn't make the picture look funny, but it does make the splitter bar act a little screwy. Remember that this splitter bar will only be present if your top-level window holds exactly 2 child windows. The problem is that the New Project Wizard builds the splitter bar by leaving a small sliver of top-level window area unclaimed by either of the two child windows. But when one of those child windows refuses to fill his assigned area, the splitter bar grows into that unclaimed area.

cBreakout

This class offers a child window that displays the Breakout video game. Many of the C++ example programs that I distribute with CPPIDE employ a cBreakoutDbg top-level window, which is nothing but a cBreakout instance paired with a cEditBox instance. Since I give you all the code for this cBreakoutDbg class, you can peruse it to learn how to manipulate a cBreakout instance.

cComboBox

This class offers a child window that displays a standard Windows combo box control. To discuss combo boxes we first need to clear up some terminology. A combo box consists of two parts, one which is always visible (the edit control) and another which comes and goes (the list box control). The purpose of the combo box is to allow you to specify a particular string known as the "current selection". You can type a string directly into the edit control, in which case it becomes the current selection. Alternatively, you can populate the current selection by clicking on one of the entries in the list box. Of course, to do this you first have to open the list box by clicking on the triangle seen at the right hand edge of the edit control.

The principal member functions are listed below and additional member functions can be found in the public portion of the class declaration.

    int     SetSelection( int iSelection );  // zero based index
    int     GetSelection( TCHAR * pszBuf, UINT uiMaxChars );
    int     GetSelectionIndex();
    int     AddString( TCHAR const * pszText );
    int     GetString( UINT uiIndex, TCHAR * pszBuf, UINT uiMaxChars  );
    int     DeleteString( UINT uiIndex );    // zero based index
    int     Empty();
    int     GetNumEntries();

As an example of typical usage of the cComboBox control, the lines shown below in black are the lines generated for you by the New Project Wizard and the lines in red are lines you may wish to add:

    m_pComboBox->Create( this,     // parent window class
                         0,0,0,0,  // x,y,cx,cy
                         0,        // extra window style flags
                         IDC_ComboBox );   // child window ID

    m_pComboBox->AddString( TEXT( "first entry" ) );
    m_pComboBox->AddString( TEXT( "second entry" ) );
    m_pComboBox->AddString( TEXT( "third entry" ) );

    m_pComboBox->Show();

You can see that I am employing the TEXT( ) macro in this code because that way the program is ready for Unicode. But you are not required to do this and if you want to be lazy you can simply type:

    m_pComboBox->AddString( "first entry" );

The cComboBox class needs some special attention due to the fact that this control does not always display at its full size. The "height" number that you specify in the call to the cComboBox::Resize( ) function is the vertical height that the list box can consume when it is visible. That is, this is the maximum height that the combo box can open up in to. But normally the combo box consumes only a very narrow horizontal stripe of screen real estate (28 pixels to be precise). When the combo box's list box does appear it temporarily covers up whatever is immediately below the combo box's edit control, which is exactly what is supposed to happen.

But the New Project Wizard doesn't understand this and consequently allots way too much space to the cComboBox instance. If the New Project Wizard creates a splitter bar, the splitter bar will appear to grow into the unused space allocated for the cComboBox instance. This happens because the New Project Wizard builds the splitter bar by leaving a small sliver of top-level window area unclaimed by either of the two child windows. But since the cComboBox child window will not employ its assigned area (not when it is closed) the splitter bar grows into that unclaimed area. You can solve the problem with just a little code (this is the official mantra of the Software Guild). Assuming you asked for the cComboBox to be the upper of 2 child windows, the changes you need to make in the wizard generated OnSize( ) function of the top-level window are shown in red below. Note that these changes also eliminate the splitter bar.

    m_pComboBox->Resize( (UINT)rc.left,   // x
                         (UINT)rc.top,    // y
                         uiAvailWidth,    // width (cx)
                         300,             // arbitrary height (cy)
                         true );

    m_pOtherChild->Resize( (UINT)rc.left,       // x
                           (UINT)rc.top + 28,   // y
                           uiAvailWidth,        // width (cx)
                           uiAvailHeight - 28,  // height (cy)
                           true );

cEditBox

This class offers a child window that functions as a text editor or a text display area. This class encapsulates Microsoft's standard multi-line edit control which is precisely what is used in Microsoft's Notepad word processor. By default, all instances of the cEditBox class are in a read-only state. This doesn't prevent your program from using the printf( ) member function to print text onto the child window but it does prevent the operator from typing text onto the child window. You merely need to call the class's ReadOnly( ) member function to remove the read-only attribute when you want to allow the operator to modify the text via the computer's keyboard.

As an example of typical usage of the cEditBox control, the lines shown below in black are the lines generated for you by the New Project Wizard and the lines in red are lines you may wish to add:

    m_pEditBox->Create( this,     // parent window class
                        0,0,0,0,  // x,y,cx,cy
                        0,        // extra window style flags
                        IDC_EditBox );   // child window ID

    m_pEditBox->printf( TEXT( "my first line\nmy second line" ) );
    m_pEditBox->ReadOnly( false );

    m_pEditBox->Show();

There are a tremendous number of member functions listed in the public portion of the cEditBox class declaration. But the principal member functions are probably the following:

    BOOL CDECL     printf( TCHAR const * pszFormat, ... );
    BOOL           SetText( TCHAR const * pszFullContents );
    BOOL           Empty();
    BOOL           InsertLine( UINT nLine, TCHAR * pszBuf );
                                // insert after, lines count from 0
    BOOL           ReplaceLine( UINT nLine, TCHAR * pszBuf );
                                // lines count from 0
    BOOL           DeleteLine( UINT nLine );   // lines count from 0
    BOOL           SetFont( TCHAR * szFaceName,
                            COLORREF crText,
                            int iDeciPtHeight,  // 10 times desired point size
                            int iDeciPtWidth = 0,   // typically can be set = 0
                            BOOL bBold = false,
                            BOOL bItalic = false,
                            BOOL bUnderline = false,
                            BOOL bStrikeout = false,
                            BOOL bLogRes = true );  // normally set to TRUE
    BOOL           ChooseFont();      // displays "Choose Font" common dialog
    void           ReadOnly( BOOL bReadOnly );
    BOOL           OnFileSaveAs();
    BOOL           PrintDoc();

The printf( ) member function appends a new line at the bottom of the existing text and hence acts exactly like the printf( ) function associated with a console window. The SetText( ) member function replaces the entire contents of the edit control and the Empty( ) member function empties it of all text. The ChooseFont( ) member function causes the standard Windows "Choose Font" dialog to appear. The OnFileSaveAs( ) member function causes the standard Windows "Save As" dialog to appear which allows the operator to specify a filename and to browse to any location on the hard drive. Many of the member functions accept an index which denotes a particular line of text. A value of 0 for this index corresponds to the first (top) line of text.

The cEditBox class encapsulates the standard Microsoft edit control. This is both good and bad. It is good because we get to use a control already prepared and debugged by someone else. But it is bad because it means we don't get to write the WndProc( ) function and hence we don't get the freedom to design this child window according to our preferences.

For example, we cannot specify or modify the right-click context menu. This context menu holds the same options you see in Notepad; namely: Undo, Cut, Copy, Paste, Delete, and Select All. I would have added a "Print" option and a "Choose Font" option on the context menu. But these things are still easy to accomplish because the cEditBox class offers a PrintDoc( ) member function and a ChooseFont( ) member function that you can call in response to the File/Print and Edit/Choose Font menu choices found in the typical menu of a top-level window. The only code that you need to add to the Wizard generated code is shown below in red:

LRESULT CALLBACK cTopLevel::WndProc( HWND   hWndMain,
                                     UINT   msg,
                                     WPARAM wParam,
                                     LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_COMMAND:
        {
            WORD     wNotifyCode = HIWORD( wParam ); // notification code
            WORD     wID         = LOWORD( wParam ); // menu item, control, or
                                                     //   accelerator identifier
            HWND     hWndChild   = (HWND) lParam;
            HMENU    hMenu       = GetMenu( hWnd );

            switch ( wID )
            {
                case IDM_FILE_PRINT:
                  pThis->m_pEditBox->PrintDoc();
                  return 0;

                case IDM_EDIT_CHOOSEFONT:
                  pThis->m_pEditBox->ChooseFont();
                  return 0;

                default:
                    break;

            } // end of switch (wID)

            break;
        }
        ...
    }
}

Another aspect of this control that you may not appreciate is that the background color (not the text color) is always gray whenever the edit control is in its read-only state. But fortunately Microsoft provided a way for you to change the background color. All you need to do to keep the background color white even when the edit control is in its read-only state is to add the following lines to your top-level window class's WndProc( ) function:

LRESULT CALLBACK cTopLevel::WndProc( HWND   hWndMain,
                                     UINT   msg,
                                     WPARAM wParam,
                                     LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_CTLCOLOREDIT:    // EDIT control about to repaint
        {
            // Note that the EDIT control will still generate the
            // WM_CTLCOLOREDIT message even while it has the ES_READONLY
            // style and will remember the color you specify but will
            // not employ it until the ES_READONLY style is removed.
            // If you want to influence the background color while
            // the EDIT control has the ES_READONLY style then you
            // must also respond to the WM_CTLCOLORSTATIC message.

            return (LONG) pThis->m_pEditBox->OnCtlColorEdit( (HDC) wParam,
                                                             (HWND) lParam );
        }
        case WM_CTLCOLORSTATIC:  // STATIC control about to repaint
        {
            // Note that the EDIT control generates WM_CTLCOLORSTATIC
            // messages while it has the ES_READONLY style.

            return (LONG) pThis->m_pEditBox->OnCtlColorEdit( (HDC) wParam,
                                                             (HWND) lParam );
        }
        ...
    }
}

cIntBox

This class offers a child window that provides a place where the operator can type an unsigned integer. This class encapsulates the standard Windows single-line edit control having the style flag ES_NUMBER. Curiously, while the ES_NUMBER flag is sufficient to cause this control to refuse to accept any keystrokes that do not result in an unsigned integer, it is still possible to paste illegal characters into this control (a typical Microsoft screw up).

The principal member functions are listed below and additional member functions can be found in the public portion of the class declaration.

    void     Set( UINT uiNumber );
    UINT     Get();

This is another one of the child window classes (such as cComboBox) that the New Project Wizard doesn't handle too well. A cIntBox instance looks stupid if it is given more than a small area on the top-level window and, unfortunately, the New Project Wizard is not aware of this. If you would like to see the cIntBox class used appropriately, check out the cAudioCD class which uses a collection of cIntBox instances to display the track number, track position, track length, etc. This demonstrates that child window classes don't have to be hosted by top-level windows. They can also be hosted on other child windows.

cListBox

This class offers a child window that displays Microsoft's standard list box control, which is simply a column of arbitrary strings. The operator can use the mouse to select one of the entries in the list box. The principal member functions are listed below and additional member functions can be found in the public portion of the class declaration.

    BOOL     PopulateEntry( int iIndex,  // zero based index (-1 adds
                                         //   item to end of list)
                            LPCTSTR pszString );
    BOOL     GetEntry( UINT nIndex,      // zero based index
                       UINT uiMaxNumChars,  // dimensioned size of pszBuffer[]
                       TCHAR * pszBuffer );
    BOOL     DeleteEntry( UINT nIndex );
    BOOL     SelectEntry( int iIndex );  // zero based index (-1 ends
                                         //   any prior selection)
    BOOL     Empty();

As an example of typical usage of the cListBox control, the lines shown below in black are the lines generated for you by the New Project Wizard and the lines in red are lines you may wish to add:

    m_pListBox->Create( this,     // parent window class
                        0,0,0,0,  // x,y,cx,cy
                        0,        // extra window style flags
                        IDC_ListBox );  // child window ID

    m_pListBox->PopulateEntry( 0, TEXT( "First" ) );
    m_pListBox->PopulateEntry( 1, TEXT( "Second" ) );

    m_pListBox->Show();

cMaze

This class offers a child window that displays a maze, a robotic mouse, and a piece of cheese. Many of the C++ example programs that I distribute with CPPIDE employ a cMazeDbg top-level window, which is nothing but a cMaze instance paired with a cEditBox instance. Since I give you all the code for this cMazeDbg class, you can peruse it to learn how to manipulate a cMaze instance.

cMovie

This class offers a child window that can play any movie located in an .AVI ("audio-video interleaved") file. You can use the right-click context menu to specify the .AVI file you want to open. You then control the movie using a set of pushbuttons for Play, Pause, Rewind, etc. You can also control playback programmatically via the following class member functions:

    BOOL        Open( TCHAR * pszFileName );
    BOOL        Play();
    BOOL        Stop();
    BOOL        Pause();
    BOOL        SetPosition( UINT uiFrameNum );   // counts from 1

These are merely the principal member functions, there are additional member functions listed in the public portion of the class declaration.

cPaintGrid

This class offers a child window that displays a rectangular grid of arbitrary width and height and hence number of cells. Each of the cells can be painted with one of two user-selectable colors. Many of the C++ example programs that I distribute with CPPIDE demonstrate a scrolling signboard. This is accomplished with a cSignboardDbg class, which is nothing but a cPaintGrid instance paired with a cEditBox instance. Since I give you all the code for this cSignboardDbg class, you can peruse it to learn how to manipulate a cPaintGrid instance.

cPeakMeter

This class offers a child window that displays a two channel (stereo) LED bar graph. Many of the C++ example programs that I distribute with CPPIDE employ this child window to display the peak levels detected in songs that are playing through the computer's speakers. The cPeakMeter class is perhaps the simplest class to employ since it basically has only 1 public member function:

    BOOL     SetLevel( UINT uiLeftLevel,
                       UINT uiRightLevel );

A .WAV file using CD quality encoding employs 16 bit samples. These samples are stored as "short ints" and hence range from -32768 to +32767 with quiet being represented as 0. When we (positive) peak detect these samples the peak can range from 0 to +32767. We pass this peak reading to the SetLevel( ) function as a UINT for speed even though it is actually a 15 bit unsigned quantity. The sound samples in an 8 bit .WAV file are stored as BYTES ranging from 0 to 255 with quiet being represented as 128. To employ this cPeakMeter class with an 8 bit .WAV file the caller must remove the 128 bit offset and then scale the peak readings by 256 because the cPeakMeter class assumes that the range of the uiLeftLevel and uiRightLevel passed parameters will be 0 to 32767.

cProgressBar

This class offers a child window that displays Microsoft's progress bar control, which is simply a rectangle of color (a color bar) that grows in order to show progress toward a goal or percent of completion of a lengthy task. The principal member functions are listed below and additional member functions can be found in the public portion of the class declaration.

    void     SetRange( UINT uiMaxValue ); // min is always 0
    void     Set( UINT uiBarValue );
    void     SetColor( COLORREF cr );
    void     SetStep( UINT uiStepValue );
    void     Step();

The default range for a progress bar is from 0 to 100. You cannot change the lower end of this range but you can use the SetRange( ) member function to change the upper end. The Step( ) member function advances the progress bar by 10 or by whatever value was most recently set using the SetStep( ) member function. You specify a color to the SetColor( ) member function via a COLORREF which is a data type defined by Microsoft. The easiest way to get a COLORREF is via the RGB( ) macro which I demonstrate below.

As an example of typical usage of the cProgressBar control, the lines shown below in black are the lines generated for you by the New Project Wizard and the lines in red are lines you may wish to add:

    m_pProgressBar->Create( this,     // parent window class
                            0,0,0,0,  // x,y,cx,cy
                            0,        // extra window style flags
                            IDC_ProgressBar );  // child window ID

    m_pProgressBar->Set( 30 );   // 30 out of 100 complete
    m_pProgressBar->SetColor( RGB( 255, 255, 0 ) ); // request a yellow bar

    m_pProgressBar->Show();

cSlider

This class offers a child window that displays a slider (also called a trackbar control) which allows the operator to manually adjust some numerical setting. A right click context menu offers the ability to display or hide a tool tip that documents the slider's current setting. A main title, a min value label, and a max value label can be displayed within the client area of the slider's window. The principal member functions are listed below and additional member functions can be found in the public portion of the class declaration.

    void     SetRange( int  iLeftTopValue, int iRightBottomValue );
    void     GetRange( int * iRangeLeftTop, int * iRangeRightBottom );
    BOOL     SetPosition( int iSliderValue );
    int      GetPosition();
    BOOL     ChangeLabels( TCHAR * pszTitle,
                           TCHAR * pszLeftTopLabel,
                           TCHAR * pszRightBottomLabel );

As an example of typical usage of the cSlider control, the lines shown below in black are the lines generated for you by the New Project Wizard and the lines in red are lines you may wish to add:

    m_pSlider->Create( this,     // parent window class
                       0,0,0,0,  // x,y,cx,cy
                       0,        // extra window style flags
                       IDC_Slider );  // child window ID

    m_pSlider->SetRange( 200, 300 );
    m_pSlider->SetPosition( 220 );
    m_pSlider->ChangeLabels( TEXT( "slider title" ),
                             TEXT(  "min value" ),
                             TEXT(  "max value" ) );

    m_pSlider->Show();

One nice feature of the cSlider child window is that it responds to the keyboard. If you click on this child window you will notice a new dotted line border which shows that this control now has keyboard focus. While in this state the cSlider control will respond to the cursor control keys, to the Page Up and Page Down keys, and to the Home and End keys (try it!). You can also use the mouse to click and drag on the thumb. Or you can click away from the thumb and cause the same amount of slider motion as the Page Up and Page Down keys cause.

cStaticText

This class offers a child window that can display short text messages such as labels. It is not terribly useful by itself but it can provide a label or a title or instructions for the use of a control (such as a slider) located in an adjacent child window. The principal member functions are listed below and additional member functions can be found in the public portion of the class declaration.

    void    Set( TCHAR * pszContents );
    BOOL    SetFont( TCHAR * szFaceName,
                     COLORREF crText,
                     int iDeciPtHeight,  // 10 times desired point size
                     int iDeciPtWidth = 0, // typically can be set = 0
                     BOOL bBold = false,
                     BOOL bItalic = false,
                     BOOL bUnderline = false,
                     BOOL bStrikeout = false,
                     BOOL bLogRes = true );  // normally set to TRUE
    BOOL    ChooseFont();

The Set( ) member function allows you to specify the message. The SetFont( ) member function allows you to specify the typeface, text color, text size, and special effects such as underlining. The ChooseFont( ) member function causes the standard Windows "Choose Font" dialog to appear and this dialog allows the operator to change the typeface, text color, and text size.

Typical usage of the cStaticText control is shown below. The lines in black are the lines generated for you by the New Project Wizard and the lines in red are the lines you have to type:

    m_pStaticText->Create( this,     // parent window class
                           0,0,0,0,  // x,y,cx,cy
                           0,        // extra window style flags
                           IDC_StaticText );  // child window ID

    m_pStaticText->Set( TEXT( "my label" ) );
    m_pStaticText->SetFont( TEXT( "Arial" ),
                            RGB( 0, 255, 0 ),  // green
                            120 );             // 12 point

    m_pStaticText->Show();

The cStaticText class encapsulates the standard Microsoft static control. This is both good and bad. It is good because we get to use a control already prepared and debugged by someone else. But it is bad because it means we don't get to write the WndProc( ) function and hence we don't get the freedom to design this child window according to our preferences.

For example, the cStaticText child window cannot offer a built-in right-click context menu. But such a context menu can appear if the parent window will provide a little assistance. You see, Microsoft designed the static control's WndProc( ) function to issue the WM_CONTEXTMENU message to the parent. So the parent can detect this message in his WndProc( ) function and then display any context menu he wants. The only context menu choice that appeals to me is a "Choose Font" possibility. If our context menu is only going to have a single entry, then we really don't need to show the context menu at all but instead can just proceed to that choice. And this is very easy since the cStaticText class offers a ChooseFont( ) member function which allows the operator to change the font typeface and color. I'll show you the necessary code below.

As a further consequence of our lack of access to the static control's WndProc( ) function, this child window cannot control its own text color without help from its parent. Fortunately, all the parent window need do is to detect the WM_CTLCOLORSTATIC message and respond by calling the OnCtlColorStatic( ) member function of the cStaticText class.

All you need to do is to add the following code to your top-level window's WndProc( ) function:

LRESULT CALLBACK cTopLevel::WndProc( HWND   hWndMain,
                                     UINT   msg,
                                     WPARAM wParam,
                                     LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_CONTEXTMENU:
        {
            pThis->m_pStaticText->ChooseFont();
            break;
        }
        case WM_CTLCOLORSTATIC:  // STATIC control is about to repaint
        {
            return (LONG) pThis->m_pStaticText->OnCtlColorStatic( (HDC) wParam,
                                                                  (HWND) lParam );
        }
        ...
    }
}

cStatusBar

This class offers a child window that encapsulates the standard status bar found along the bottom edge of most top-level windows. It does not offer a right click context menu but you can programmatically control how many panes it displays and separately control those panes. The number of panes and the width of those panes is determined by the passed parameters of the cStatusBar::Create( ) member function, whose simplified function prototype is shown below:

    BOOL    Create( cBaseWindow * pParent,
                    DWORD dwWS_Flags,   // extra window style flags
                    UINT  uiChildID,
                    UINT  nParts,
                    int * piRightEdges,
                    UINT  uiMaxPBValue );

The nParts passed parameter controls how many panes will appear on the status bar. The piRightEdges passed parameter is an array of ints that specify the right edges of these individual panes in client coordinates (i.e., pixels). These ints are NOT widths hence they need to increase monotonically. The special value of -1 indicates that that pane should extend to the right hand edge of the client window, and hence will be the last pane. The uiMaxPBValue serves double duty. It not only controls whether a progress bar will appear at the far right hand edge of the status bar but it also sets the maximum value of this progress bar (the min value is always 0). If uiMaxPBValue does not equal zero then a progress bar will appear on the status bar and will have a range from 0 to uiMaxPBValue.

The New Project Wizard generates a call to the cStatusBar::Create( ) function which looks like the following:

    int iSBRightEdges[] = { -1 };     // right edge coords for status bar

    m_pStatusBar->Create( this,                   // parent window class
                          0,                      // extra window style flags
                          IDC_MainWndStatusBar,   // control ID
                          1,                      // nParts
                          iSBRightEdges,          // piRightEdges
                          0 );                    // uiMaxPBValue

    m_pStatusBar->Show();             // make the window visible

Consequently if you don't modify the code generated by the New Project Wizard you will get a single pane and no progress bar. If you modify the call to cStatusBar::Create( ) to look like the following:

    UINT  nParts = 4;
    int   iSBRightEdges[] = { 300, 400, 500, -1 };
                                   // default cumulative right edge coords
                                   // for the status bar, assumes m_nParts = 4

    m_pStatusBar->Create( this,                   // parent window class
                          0,                      // extra window style flags
                          IDC_StatusBar,          // control ID
                          nParts,                 // nParts
                          iSBRightEdges,          // piRightEdges
                          0 );                    // uiMaxPBValue

    m_pStatusBar->UpdatePane( 0, TEXT( "first pane" ) );
    m_pStatusBar->UpdatePane( 1, TEXT( "second pane" ) );

    m_pStatusBar->Show();             // make the window visible

then you will get 4 panes with the widths shown below:

<--------------------><-------><-------><-------------->
         300            100      100      all the
        pixels          pixels   pixels   remaining space

Incidentally, you can see the famous hidden this pointer used in the above code snippets. It is this this pointer that we have to re-create (using a cast) as the pThis pointer in the various WndProc( ) functions.

The principal member functions of the cStatusBar class are:

    void CDECL   UpdatePane( UINT nPane, TCHAR const * pszFormat, ... );
    void         SetProgressBar( UINT uiBarValue );

The UpdatePane( ) function works just like the conventional printf( ) function except the destination is the pane of the status bar indicated by the first passed parameter (which counts from 0). The SetProgressBar( ) member function allows you to control the progress bar (assuming you requested one). Here is an example of the code needed to place a progress bar on the status bar:

    UINT  nParts = 4;
    int   iSBRightEdges[] = { 300, 400, 500, -1 };
                                   // default cumulative right edge coords
                                   // for the status bar, assumes m_nParts = 4

    m_pStatusBar->Create( this,                   // parent window class
                          0,                      // extra window style flags
                          IDC_StatusBar,          // control ID
                          nParts,                 // nParts
                          iSBRightEdges,          // piRightEdges
                          100 );                  // uiMaxPBValue

    m_pStatusBar->UpdatePane( 0, TEXT( "first pane" ) );
    m_pStatusBar->UpdatePane( 1, TEXT( "second pane" ) );

    m_pStatusBar->SetProgressBar( 80 );  // set to 80 out of 100

    m_pStatusBar->Show();             // make the window visible

To animate the progress bar you merely need to repetitively call the cStatusBar::SetProgressBar( ) member function.

Remember that the New Project Wizard gives every project an instance of the cStatusBar class (and therefore you never list the cStatusBar class in Step #3 of the New Project Wizard). I have shown you how the View menu allows the operator to hide the status bar if he doesn't want to see it. The code necessary to hide the status bar is provided by the New Project Wizard and is completely functional.

cToolBar

This class offers a child window that encapsulates the standard toolbar found just below the menu bar in most top-level windows. This class is more complicated than the other child window classes because we need to intermix toolbar images designed by Microsoft with other toolbar images we design ourselves.

The simplified function prototype for the cToolBar::Create( ) member function is shown below:

    BOOL     Create( cBaseWindow * pParent,
                     DWORD dwWS_Flags,   // extra window style flags
                     UINT  uiChildID,
                     LPCTBBUTTON ptbb,   // toolbar button descriptions
                                         //   taken from HINST_COMMCTRL,
                                         //   IDB_STD_SMALL_COLOR
                     UINT  nButtons,     // # of buttons in ptbb
                     int   nCBSeparator = -1 );
                                         // 0 based index into tbb[] of
                                         // the separator "button" used
                                         // for the toolbar COMBOBOX
                                         // (-1 for no COMBOBOX)

The New Project Wizard generates the following call to the cToolBar::Create( ) function. Don't panic, the first portion is just the initialization of an array that we need for a passed parameter to the Create( ) function. It is this array that allows us to specify the exact set of toolbar buttons we desire for our program.

    TBBUTTON tbb[] = {

//  bitmap          command           initial         button      res app string
//   ID               ID               state          style       erv data  ID
{STD_FILENEW,   IDM_FILE_NEW,     TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{STD_FILEOPEN,  IDM_FILE_OPEN,    TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{STD_FILESAVE,  IDM_FILE_SAVE,    TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{   0,          IDM_SEP1,         TBSTATE_ENABLED, TBSTYLE_SEP,   0,0, 0,   0 },
{STD_PRINT,     IDM_FILE_PRINT,   TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{   0,          IDM_SEP2,         TBSTATE_ENABLED, TBSTYLE_SEP,   0,0, 0,   0 },
{STD_CUT,       IDM_EDIT_CUT,     TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{STD_COPY,      IDM_EDIT_COPY,    TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{STD_PASTE,     IDM_EDIT_PASTE,   TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{STD_DELETE,    IDM_EDIT_DELETE,  TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{   0,          IDM_SEP3,         TBSTATE_ENABLED, TBSTYLE_SEP,   0,0, 0,   0 },
{STD_UNDO,      IDM_EDIT_UNDO,    TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
{   0,          IDM_SEP4,         TBSTATE_ENABLED, TBSTYLE_SEP,   0,0, 0,   0 },
{STD_PROPERTIES,IDM_VIEW_OPTIONS, TBSTATE_ENABLED, TBSTYLE_BUTTON,0,0, 0,   0 },
}; // end of tbb[]

    m_pToolBar->Create( this,                  // parent window class
                        0,                     // extra window style flags
                        IDC_MainWndToolBar,    // control ID
                        tbb,                   // button descriptions taken
                                               //   from HINST_COMMCTRL,
                                               //   IDB_STD_SMALL_COLOR
                        NUMELE( tbb ),         // number of buttons
                        -1 );                  // 0 based index into tbb[]
                                               //   for separator "button" used
                                               //   for toolbar COMBOBOX (-1
                                               //   for no toolbar COMBOBOX)

In the code snippet seen above NUMELE( ) is a macro that returns the number of elements in the tbb array. The tbb variable is an array of TBBUTTONs, which is a struct defined by Microsoft. Notice that you are free to request separators, which are nothing but vertical lines, between any of the toolbar buttons.

You can modify this wizard generated code to get the toolbar you want. During the call to the cToolBar::Create( ) function you are limited to the toolbar images designed by Microsoft. These are the 15 following toolbar images which you have undoubtedly seen many times:

To request these Microsoft designed toolbar images you specify the following identifiers in your tbb array:

STD_FILENEW
STD_FILEOPEN
STD_FILESAVE
STD_PRINT
STD_PRINTPRE
STD_CUT
STD_COPY
STD_PASTE
STD_DELETE
STD_UNDO
STD_REDOW
STD_FIND
STD_REPLACE
STD_PROPERTIES
STD_HELP

During the call to cToolBar::Create( ) you get as many of the Microsoft designed button images as you want on your toolbar and then via a subsequent call to the cToolBar::AddCustomTBButtons( ) member function you add additional button images that you designed yourself. You can use Microsoft Paint or any other picture editor to create your own toolbar images. All you need to do is to create a .BMP file holding a picture which is n*16 pixels wide by 15 pixels tall where n is the number of images in the bitmap. For example, in the CppIde\res folder you can find the CppIdeToolbar.bmp file which holds the toolbar images that I designed for use on CPPIDE's toolbar. The contents of this .BMP file are shown below:

There are 18 toolbar images in this .BMP file and hence the image in this .BMP file is 288 (18*16) pixels wide and 15 pixels tall.

Shown below in red is the code you will need to type in order to add 3 additional custom designed toolbar buttons. For this example I add the 3 custom toolbar buttons seen on CPPIDE's toolbar: the Compile button, the Execute button, and the Enter Debugger button. You insert the following code in the top-level window's .CPP file just after the wizard generated call to cToolBar::Create( ) (shown below):

    m_pToolBar->Create( this,                  // parent window class
                        0,                     // extra window style flags
                        IDC_MainWndToolBar,    // control ID
                        tbb,                   // button descriptions taken
                                               //   from HINST_COMMCTRL,
                                               //   IDB_STD_SMALL_COLOR
                        NUMELE( tbb ),         // number of buttons
                        -1 );                  // 0 based index into tbb[]
                                               //   for separator "button" used
                                               //   for toolbar COMBOBOX (-1
                                               //   for no toolbar COMBOBOX)

    // Add some additional buttons to the toolbar from a custom bitmap.
    // The order that you place the button definitions in this upcoming
    // TBBUTTON array determines the order they will appear (from left
    // to right) on the toolbar.  I know of no way to change this order
    // at a later time.

    int  iFirstCustomImage = m_pToolBar->GetNextButtonIndex();

    TBBUTTON  tbb2[] = {
        //  WARNING: do not repeat any of the TBBUTTON.idCommand entries
        //           or the tool tips will begin acting screwy.

        { iFirstCustomImage + 0,  // +0 for first image in .BMP file

          IDM_BUILD_COMPILE,
          TBSTATE_ENABLED,
          TBSTYLE_BUTTON,
          0,0, 0, 0 },

        { iFirstCustomImage + 1,  // +1 for second image in .BMP file

          IDM_BUILD_EXECUTE,
          TBSTATE_ENABLED,
          TBSTYLE_BUTTON,
          0,0, 0, 0 },

        { iFirstCustomImage + 8,  // +8 for ninth image in .BMP file

          IDM_BUILD_ENTERDEBUGGER,
          TBSTATE_ENABLED,
          TBSTYLE_BUTTON,
          0,0, 0, 0 },
    };

    UINT     nButtonImages;
    HBITMAP  hBitmap = LoadBitmap( hInst, MAKEINTRESOURCE( IDB_TBButtons ) );

    if ( hBitmap )
    {
        BITMAP    bm;
        GetObject( hBitmap, sizeof ( bm ), &bm );

        if ( bm.bmWidth % 16 != 0 )
        {
            MessageBox( m_hWnd,
               TEXT( "Toolbar bitmap width is not a multiple of 16 pixels !!" ),
               g_pWinApp->GetAppName(),
               MB_ICONERROR );
        }
        nButtonImages = bm.bmWidth / 16;
        DeleteObject( hBitmap );
    }

    m_pToolBar->AddCustomTBButtons( IDB_TBButtons,  // resource ID
                                    nButtonImages,  // # button images in bitmap
                                    (LPCTBBUTTON) &tbb2,
                                    NUMELE( tbb2 ) );

    m_pToolBar->Show();            // make the window visible

This new code will not compile unless you define IDM_BUILD_COMPILE, IDM_BUILD_EXECUTE, and IDM_BUILD_ENTERDEBUGGER in your top level Resource.h file. For example, you could add the following lines to your top level Resource.h file:

#define  IDM_BUILD_COMPILE       701
#define  IDM_BUILD_EXECUTE       702
#define  IDM_BUILD_ENTERDEBUGGER 703

#define  IDB_TBButtons          1000

Furthermore, the .BMP file holding the new toolbar button images must be specified in your top level .RC file,

IDB_TBButtons  BITMAP  DISCARDABLE  CppIdeToolbar.bmp

and this .BMP file must be physically located in your project folder (the folder holding the .RC file).

When you recompile, your new toolbar buttons will appear, but they will not be functional. You connect them to your program just as you connect all the other toolbar buttons: by watching for and responding to WM_COMMAND messages. I have shown you the necessary code once already in the Peak Detector example program and I will be covering this code in more detail in an upcoming Breakout5 example program. In the meantime you can find the TODO markers for these spots by using the Find tool (on CPPIDE's toolbar) to search for occurrences of IDM_FILE_NEW, IDM_FILE_OPEN, etc. You will just have to add 3 new case statements for IDM_BUILD_COMPILE, IDM_BUILD_EXECUTE, and IDM_BUILD_ENTERDEBUGGER.

The 3 new toolbar buttons will lack the tool tip pop-up text messages that the other toolbar buttons possess. But this is easily remedied. You merely need to add a text string to the STRINGTABLE section of the top-level window's .RC file. Examples of the necessary new statements are shown below in red:

STRINGTABLE DISCARDABLE
BEGIN
    IDM_FILE_NEW              "Creates a new document"
    IDM_FILE_OPEN             "Opens an existing document"
    IDM_FILE_SAVE             "Saves the active document"
    IDM_FILE_SAVE_AS          "Saves active document with a new name"
    IDM_FILE_PAGE_SETUP       "Changes page layout settings"
    IDM_FILE_PRINT_PRE        "Displays a simulation of printed output"
    IDM_FILE_PRINT            "Prints the active document"
    IDM_FILE_EXIT             "Quits the application"
    IDM_EDIT_UNDO             "Reverses the last action"
    IDM_EDIT_CUT              "Cuts selection and puts it on Clipboard"
    IDM_EDIT_COPY             "Copies selection and puts it on Clipboard"
    IDM_EDIT_PASTE            "Inserts Clipboard contents at insertion point"
    IDM_EDIT_DELETE           "Deletes the selection"
    IDM_EDIT_SELECT_ALL       "Selects the entire document"
    IDM_EDIT_FIND             "Finds the specified text"
    IDM_EDIT_FINDNEXT         "Repeats the last find in the forward direction"
    IDM_EDIT_FINDPRIOR        "Repeats the last find in the backward direction"
    IDM_EDIT_REPLACE          "Replaces specific text with different text"
    IDM_VIEW_TOOLBAR          "Shows/Hides the toolbar"
    IDM_VIEW_STATUS_BAR       "Shows/Hides the status bar"
    IDM_VIEW_OPTIONS          "Changes options"
    IDM_HELP_HELP             "Displays the on-line documentation"
    IDM_HELP_ABOUT            "Displays program info, version #, and copyright"
    IDM_BUILD_COMPILE         "Compiles your program"
    IDM_BUILD_EXECUTE         "Runs your program"
    IDM_BUILD_ENTERDEBUGGER   "Activates the debugger"
END

The cToolBar class has the capability of incorporating a combo box. In CPPIDE I use this toolbar combo box for the Find tool. In order to add a combo box to the toolbar you need to specify a location and a width. You control the width of the toolbar combo box by specifying the width for a particular separator which really will be replaced by the combo box (I know this is a strange trick but it was invented by Microsoft, not me). You control the location of the toolbar combo box by where in the sequence of toolbar button definitions you request this special separator. For example, in order to get a toolbar combo box that is 125 pixels wide and which occurs just prior to the custom toolbar buttons we just added you will need to add the code shown below in red.

    // Add some additional buttons to the toolbar from a custom bitmap.
    // The order that you place the button definitions in this upcoming
    // TBBUTTON array determines the order they will appear (from left
    // to right) on the toolbar.  I know of no way to change this order
    // at a later time.

    int  iFirstCustomImage = m_pToolBar->GetNextButtonIndex();

    TBBUTTON  tbb2[] = {
        //  WARNING: do not repeat any of the TBBUTTON.idCommand entries
        //           or the tool tips will begin acting screwy.

        { 125,                    // width of toolbar COMBOBOX
          IDM_SEP5,               // this separator, which is the toolbar
          TBSTATE_ENABLED,        //   button with index=14, is where we
          TBSTYLE_SEP,            //   will request the toolbar COMBOBOX
          0,0, 0, 0 },

        { iFirstCustomImage + 0,  // +0 for first image in .BMP file
          IDM_BUILD_COMPILE,
          TBSTATE_ENABLED,
          TBSTYLE_BUTTON,
          0,0, 0, 0 },

        { iFirstCustomImage + 1,  // +1 for second image in .BMP file
          IDM_BUILD_EXECUTE,
          TBSTATE_ENABLED,
          TBSTYLE_BUTTON,
          0,0, 0, 0 },

        { iFirstCustomImage + 8,  // +8 for ninth image in .BMP file
          IDM_BUILD_ENTERDEBUGGER,
          TBSTATE_ENABLED,
          TBSTYLE_BUTTON,
          0,0, 0, 0 },
    };

    UINT     nButtonImages;
    HBITMAP  hBitmap = LoadBitmap( hInst, MAKEINTRESOURCE( IDB_TBButtons ) );

    if ( hBitmap )
    {
        BITMAP    bm;
        GetObject( hBitmap, sizeof ( bm ), &bm );

        if ( bm.bmWidth % 16 != 0 )
        {
            MessageBox( m_hWnd,
               TEXT( "Toolbar bitmap width is not a multiple of 16 pixels !!" ),
               g_pWinApp->GetAppName(),
               MB_ICONERROR );
        }
        nButtonImages = bm.bmWidth / 16;
        DeleteObject( hBitmap );
    }

    m_pToolBar->AddCustomTBButtons( IDB_TBButtons,  // resource ID
                                    nButtonImages,  // # button images in bitmap
                                    (LPCTBBUTTON) &tbb2,
                                    NUMELE( tbb2 ) );

   // The IDM_SEP5 separator "button" is where we will request a
   // toolbar COMBOBOX.  For separators, the .iBitmap member of
   // the TBBUTTON struct specifies the separator width.
   // Because of the order we defined our toolbar buttons, the
   // index (counting from 0) of the separator we intend for our
   // COMBOBOX is 14.

    m_pToolBar->AddComboBox( 14 );  // nCBSeparator

    m_pToolBar->Show();             // make the window visible

When you recompile and run you will observe a 125 pixel wide combo box on the toolbar just in front of the 3 new toolbar buttons that we added previously. This combo box will be empty however, meaning that when you open it the drop-down list box will hold no strings. You use the cToolBar::AddCBEntry( ) function to populate this list box, as demonstrated below in red:

    m_pToolBar->AddComboBox( 14 );  // nCBSeparator

    m_pToolBar->AddCBEntry( "first string" );
    m_pToolBar->AddCBEntry( "second string" );
    m_pToolBar->AddCBEntry( "third string" );

    m_pToolBar->Show();             // make the window visible

When you recompile and run and then open the toolbar's combo box you will now observe these 3 strings. The operator can populate the edit control portion of the combo box by clicking with the mouse on one of these strings. Alternatively, the operator can type directly into the edit control portion of the combo box.

Your program detects that the operator has manipulated the combo box by watching for 2 messages. When the operator opens the combo box and then clicks on one of the strings in the drop-down list box, your program will receive the CBN_SELCHANGE flavor of the WM_COMMAND message. When the operator types directly into the edit control portion of the combo box, your program will receive the CBN_EDITCHANGE flavor of the WM_COMMAND message. Unfortunately, the code is rather messy. This is a good demonstration of why the WIN32 API is called a low-level interface: it means you must write a lot of code to accomplish the big (high-level) activities. I find the WIN32 API a bit too low-level for my taste and that is why I prefer to encapsulate bigger pieces of functionality (such as entire child windows) in C++ classes.

LRESULT CALLBACK cTopLevel::WndProc( HWND   hWndMain,
                                     UINT   msg,
                                     WPARAM wParam,
                                     LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_COMMAND:
        {
            WORD     wNotifyCode = HIWORD( wParam ); // notification code
            WORD     wID         = LOWORD( wParam ); // menu item, control, or
                                                     //   accelerator identifier
            HWND     hWndChild   = (HWND) lParam;
            HMENU    hMenu       = GetMenu( hWnd );

            switch ( wID )
            {
                case IDC_cToolbarComboBox:
                {
                    switch( wNotifyCode )
                    {
                        case CBN_SELCHANGE:
                        {
                            LONG    lItem =
                            SendMessage(
                                   (HWND) lParam, //HWND of COMBOBOX
                                   CB_GETCURSEL, 0, 0);

                            // The CB_GETLBTEXT message that we are
                            // about to generate has a flaw: no
                            // passed parameter allows us to specify
                            // the size of our receive buffer.  Hence
                            // we must dynamically allocate this buffer
                            // to the necessary size which we can learn
                            // by sending the CB_GETLBTEXTLEN message.

                            int cchTemp = SendMessage(
                                   (HWND) lParam, //HWND of COMBOBOX
                                   CB_GETLBTEXTLEN,
                                   (WPARAM) lItem, 0);

                            TCHAR  * pszTemp;
                            while ( ( pszTemp = new TCHAR[cchTemp + 1] )
                                                                      == NULL )
                            {
                                int   nCode = MessageBox(
                                    NULL,
                                    TEXT( "ERROR: insufficient memory.\n" )
                                    TEXT( "Close other running applications\n" )
                                    TEXT( "and then hit 'Retry'" ),
                                    g_pWinApp->GetAppName(),
                                    MB_RETRYCANCEL | MB_ICONERROR );
                                if ( IDCANCEL == nCode )
                                    break;
                            }
                            if ( pszTemp )
                            {
                                SendMessage(
                                   (HWND) lParam, //HWND of COMBOBOX
                                   CB_GETLBTEXT,
                                   (WPARAM) lItem,
                                   (LPARAM) pszTemp );
#ifdef _DEBUG
g_DbgConsole.printf( TEXT( "CBN_SELCHANGE: %s\n" ), pszTemp );
#endif
                                delete[] pszTemp;
                            }
                            return 0;
                        }
                        case CBN_EDITCHANGE:
                        {
#define MAX_SELECTION_CHARS 64
                            TCHAR   szTemp[MAX_SELECTION_CHARS];

                            SendMessage(
                                   (HWND) lParam, //HWND of COMBOBOX
                                   WM_GETTEXT,
                                   (WPARAM) (sizeof( szTemp )/sizeof( TCHAR )),
                                   (LPARAM) szTemp );

                            // We will receive the CBN_EDITCHANGE message
                            // each time the user types a new letter or
                            // backspaces to delete a letter.
#ifdef _DEBUG
g_DbgConsole.printf( TEXT( "CBN_EDITCHANGE: %s\n" ), szTemp );
#endif
                            return 0;
                        }
                    }
                }  // end of case IDC_cToolbarComboBox
            } // end of switch ( wID )
        } // end of case WM_COMMAND

The code shown above will not compile because the IDC_cToolbarComboBox manifest constant is not defined. But this macro is defined in the WinFrameLibResource.h file so all you need to do is add the following statement to your top-level window's Resource.h file:

#include "WinFrameLibResource.h"

The code that I show above does nothing in response to the CBN_SELCHANGE and CBN_EDITCHANGE messages other than print diagnostic sentences to the debug console (i.e., the g_DbgConsole instance of the cDbgConsole class).

Remember that for both the Microsoft defined toolbar buttons, your custom toolbar buttons, the toolbar combo box (if requested), and the toolbar separators; you must define the control identifiers such as IDM_FILE_NEW, IDM_FILE_OPEN, IDM_SEP5, etc. in your top-level window class's Resource.h file. The New Project Wizard provides a lot of this code for you but it is your responsibility to add control identifiers for whatever new buttons you add.

Remember also that the New Project Wizard gives every project an instance of the cToolBar class (and therefore you never list the cToolBar class in Step #3 of the New Project Wizard). I have shown you how the View menu allows the operator to hide the toolbar if he doesn't want to see it. The code necessary to hide the toolbar is provided by the New Project Wizard and is completely functional.

cWavePlayer

This class offers a child window that can play any song located in a .WAV ("wave") file. Such files hold uncompressed PCM (pulse code modulation) encodings of sound samples. .WAV files can hold either a 1 channel (mono) or 2 channel (stereo) recording made at various resolutions (bits per sample) and sampling frequencies (samples per second). You can use the right-click context menu to specify the .WAV file you want to open. You then control the song using a set of pushbuttons for Play, Pause, Rewind, etc. You can also control playback programmatically via the following class member functions:

    BOOL     OpenWaveFile( TCHAR * pszFileName );
    BOOL     StartPlayback();  // same consequence as the Play pushbutton
    BOOL     StopPlayback();   // same consequence as the Stop pushbutton

Summary

Remember that while I consider these child window classes to be fully functional, you can view them as raw materials since the C++ language allows you to inherit from any class and thereby add new functionality. Inheritance also allows you to replace any portion of the existing functionality that you're not happy with. I have already demonstrated this technique when I showed you how to improve the cBreakout class by using it as the base class for a cBetterBreakout derived class.


( 51 )

Default Function Arguments

The child window classes employed in the CSL Framework have to be standardized somewhat in order that they can be managed by the New Project Wizard. Each of these child window classes must provide a Create( ) member function which causes the child window to appear on its top-level window. That is, this member function causes the child window to paint its screen area.

Because the Create( ) member function has been standardized between all the child window classes, the New Project Wizard can manage to write the code that calls this function. When you request that the New Project Wizard create a brand new child window class, you will be able to look in the .H header file and observe that the Create( ) member function has been prototyped as shown below:

    BOOL    Create( cBaseWindow * pParent,
                    UINT x, UINT y, UINT cx, UINT cy,
                    DWORD dwWS_Flags,   // extra window style flags
                    UINT  uiChildID,    // used for the hMenu param
                                        //   passed to CreateWindow()
                    TCHAR * pszObjName = NULL );
                                        // arbitrary component name

You will also be able to look in the top-level window class's .CPP file and find where the Create( ) member function of that new child window class is called. The code that the New Project Wizard provides for the call to the Create( ) member function is shown below:

    // Create the child window.

    m_pChild->Create( this,           // parent window class
                      0,0,0,0,        // x,y,cx,cy
                      0,              // extra window style flags
                      IDC_Child );    // child window ID

    m_pChild->Show();                 // make the window visible

There is some funny business going on here. You can observe that the .H header file states that the Create( ) member function takes 8 passed parameters yet the call to the Create( ) member function found in the .CPP file only supplies 7 passed parameters. Furthermore, you can observe that the function prototype for the Create( ) member function employs the equals sign with the final passed parameter.

This all has to do with a language feature known as default function arguments. This feature is only available in the C++ language so don't try to use it in a C program. But it is a great feature as I am about to demonstrate.

In the function prototype for the Create( ) member function a default value of NULL is specified for the pszObjName passed parameter. This means that if the caller doesn't supply a value for this passed parameter then the function will receive the value of NULL because this is the default function argument. This is why the code generated by the wizard compiles and runs even though the call to the Create( ) member function supplies only 7 passed parameters.

The rules for using default function arguments are rather simple:

  • All of the function parameters that include default values must appear to the right of all of the function parameters that don't include default values. That is, once you provide a default value for some passed parameter you must provide default values for all of the remaining parameters. Hence the following function prototype is illegal:
    int MyFunc( int iFirst, int iSecond = 2, int iThird );
  • The default value must be specified only once. Therefore if you specify a default argument value in the function prototype found in the .H header file then you must NOT list the default argument again in the function prototype found in the .CPP source file. You can look at the .CPP file produced by the New Project Wizard to verify that this rule is obeyed.

With the rules out of the way, I'll now convince you how useful this language feature is.

The Create( ) member function is responsible for opening a window and painting it and activating that window's message loop. All of this is accomplished by the Create( ) member function. But think about it. Each of the child window classes has unique characteristics. How can a standardized Create( ) member function be sufficient? How can the cBreakout class know how many rows of bricks you want? How can the cPaintGrid class know what colors you want? How can the cSlider class know whether you want the tool tip that displays the slider's current position to be visible? How can the cEditBox class know whether you want to employ Microsoft's standard multi-line edit control or the newer rich edit control?

These questions are unique to a specific child window class. Yet they are all supposed to be answered during the call to that class's Create( ) member function. And the New Project Wizard provides standardized code for the calls to all of the Create( ) member functions. How can this work?

Via default function arguments, of course. Shown below is the actual function prototype for the cBreakout::Create( ) member function. as seen in the cBreakout.h header file:

    BOOL    Create( cBaseWindow * pParent,
                    UINT x, UINT y, UINT cx, UINT cy,
                    DWORD dwWS_Flags,   // extra window style flags
                    UINT  uiChildID,
                    UINT  nRows = 2,
                    UINT  nCols = 12,
                    COLORREF  crForeground = crRed,
                    COLORREF  crBackground = crWhite,
                    TCHAR * pszObjName = NULL ); // arbitrary component name

You can see that this Create( ) member function has additional passed parameters that specify the unique characteristics of this child window class. But default function arguments are supplied for each of the function parameters that the New Project Wizard doesn't know about and hence the code produced by the wizard will compile. We can see that when the New Project Wizard writes the call to the Create( ) member function, the Breakout game board will contain 2 rows of 12 bricks which are red on a white background.

The way that I think about this language feature is that it allows the person who writes the class to help write the call to the class. Usually these are two different people. For example, I wrote the cBreakout class and you will be calling the member functions of the cBreakout class when you employ this child window on the top-level window of some program you construct. But if you don't override my default function arguments, then my code will receive the values that I declared to be the default values.

So I should emphasize that just because a default function argument is supplied, you are not required to employ it. You can call the cBreakout::Create( ) member function as shown below:

    // Create the child window.

    m_pChild->Create( this,           // parent window class
                      0,0,0,0,        // x,y,cx,cy
                      0,              // extra window style flags
                      IDC_Child,      // child window ID
                      3,              // nRows
                      12,             // nCols
                      crGreen );      // crForeground

    m_pChild->Show();                 // make the window visible

Because you overrode the default value of 2 for the nRows passed parameter, your Breakout game board will have 3 rather than 2 rows of bricks. You also requested that these bricks be green instead of red. You didn't want to change the default value of 12 for the nCols passed parameter but you had to provide it in order to get a chance to override the crForeground passed parameter. You were happy with all the default values for all of the passed parameters beyond crForeground and hence you didn't specify any value for these parameters in your call to the cBreakout::Create( ) member function. All of the remaining parameters will be defaulted.

The crRed and crGreen values that you see in these code snippets are simply macro names for certain colors. You can look in the Common.h header file (found in the CppIde\include folder) to observe that crRed and crGreen are just aliases for particular invocations of the RGB( ) macro:

    #define crRed   (RGB( 0xFF, 0x00, 0x00 ))
    #define crGreen (RGB( 0x00, 0xFF, 0x00 ))
    #define crBlue  (RGB( 0x00, 0x00, 0xFF ))
    #define crWhite (RGB( 0xFF, 0xFF, 0xFF ))
    #define crBlack (RGB( 0x00, 0x00, 0x00 ))

Many of the child window classes that I wrote for the CSL Framework don't require any customization of the Create( ) member function. But the cPeakMeter class can put this language feature to good use, as demonstrated by the signature for the cPeakMeter::Create( ) member function:

    BOOL    Create( cBaseWindow * pParent,
                    UINT x, UINT y, UINT cx, UINT cy,
                    DWORD dwWS_Flags,   // extra window style flags
                    UINT  uiChildID,
                    BOOL  bHorizontal = true,  // bar graph horizontal
                    BOOL  bAnnotate = true,    // show annotations
                    TCHAR * pszObjName = NULL ); // arbitrary component name

The presence of the bHorizontal passed parameter (a Boolean) should convince you that you can modify the call to the Create( ) member function produced by the New Project Wizard and thereby achieve a vertically oriented LED bar graph. I'll let you look through the various .H header files and find out what other capabilities exist in the child window classes that I provide with the CSL Framework.

Before I end this page, I am sure you are wondering about the fact that the call to the Create( ) member function as written by the New Project Wizard supplies an x value of 0, a y value of 0, a width of 0, and a height of 0 for the child window. This can be seen below:

    // Create the child window.

    m_pChild->Create( this,           // parent window class
                      0,0,0,0,        // x,y,cx,cy
                      0,              // extra window style flags
                      IDC_Child );    // child window ID

    m_pChild->Show();                 // make the window visible

How can this possible be right? Well, it's not right, but it's also not permanent. The Create( ) member function offers the x, y, cx (width), and cy (height) passed parameters in case you never again plan to specify the position and size of the child window. You could get away with this only if you don't allow the top-level window to be resized. If the top-level window is never going to be stretched then the locations and sizes of all the child windows can be specified once [during the call to Create( )] and then forgotten.

But most top-level windows employ what Microsoft calls the thick border (this is the WS_THICKFRAME setting) which allows the operator to grab the border with the mouse and stretch the window. If the top-level window offers this capability then your program is going to have to be always ready to recompute a new position and size for each of the child windows. You would perform these recomputations each time you received the WM_SIZE message via your WndProc( ) function.

So now we are faced with the prospect of providing the code that determines the child window position and size at that location in our program where we call the Create( ) member function and at that location in our program where we respond to the WM_SIZE message.

This brings up the following general rule of programming:

Anytime you must provide a certain capability, place this code in only one location in your program.

If you violate this rule you will not only be doing more typing but you will be introducing the possibility that the code in these two locations could someday diverge. That is, when you decide 2 years in the future to make a change, will you remember to change both locations?

In my CSL Framework I elect to place the resizing code in an OnSize( ) member function which can be called in response to the WM_SIZE message. Now here is the great part (programmers love these situations): I am guaranteed that my call to the Create( ) member function is going to produce a WM_SIZE message to my WndProc( ). Hence I am guaranteed that my OnSize( ) member function is going to be called in the near future. Therefore it really doesn't matter what values I supply for x, y, cx, and cy in the call to the Create( ) member function. So I do the easy thing and use zeros.

Let me point out another place where Windows programs obey the general principal of "one purpose, one place". Just as the OnSize( ) member function is called in response to the WM_SIZE message, each child window class includes an OnPaint( ) member function which is called by the WndProc( ) each time the WM_PAINT message is received from the operating system. You never know when you are going to receive the WM_PAINT message. The operator might initiate some other program and move its window such that it partially or completely covers your program's window. This doesn't result in a WM_PAINT message until the operator moves or closes that other window, thus re-exposing your program's window. The screen now needs to be repainted so the Windows operating system sends your program a WM_PAINT message. This message is sent to your program since you are now the owner of those particular pixels on the screen.

The OnPaint( ) member function is likely to be rather complicated, since drawing is a rather complex activity. You definitely don't want to place the code that accomplishes the painting of your window in multiple locations in your program. You want this code in one and only one place, and the OnPaint( ) member function is the logical place.

But then how do you cause painting to occur at other times? For example, think back on the cScore class that we wrote that draws a number in a child window using the style of a 7 segment display. We decided to give the cScore class the following member function:

    BOOL cScore::SetScore( UINT uiNewScore )
    {
            m_uiCurrentScore = uiNewScore;
            InvalidateRect( m_hWnd, NULL, true );  // force a repaint
    };

Whenever this SetScore( ) member function is called the score changes and hence the child window needs to be repainted. The Windows operating system has no idea that we need to repaint our window at this moment and hence won't be issuing the WM_PAINT message.

The intelligence to repaint our window resides in our OnPaint( ) member function. Theoretically we could make a direct call to this OnPaint( ) member function as part of the SetScore( ) function. But it is better to convince the operating system to issue the WM_PAINT message and thereby allow the OnPaint( ) function to be initiated in response to a WM_PAINT message. This is a better organization because then the operating system can coalesce multiple WM_PAINT messages and thereby help our program run faster.

The WIN32 API offers a function that you can call when you want to force the operating system to issue the WM_PAINT message. This is the InvalidateRect( ) function and you can see that we do call it from within the cScore::SetScore( ) function.


Resource Files

( 52 )

Resource (.RC) Files

Microsoft invented the Windows Resource (.RC) file to hold a textual description of the non-executable resources needed for Windows programs. A Resource file is comprised of only a few major sections. The most important sections are the MENU, ACCELERATORS, STRINGTABLE, ICON, BITMAP, CURSOR, LANGUAGE, and VERSIONINFO sections. You can think of these as keywords of the language of .RC files.

We will start by examining the MENU section which describes the menu that you want in your top-level window. Getting your menu to appear in the window is probably the simplest problem you face in creating a Windows program. Microsoft takes care of all of the painting, based upon the contents of the MENU section of your .RC file. Shown below is the default MENU section that the New Project Wizard constructs for the top-level window's .RC file. By comparing these statements to the actual menu that results (once you compile) you should be able to infer how the MENU section works.

/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

IDR_MainMenu MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&New...\tCtrl+N",        IDM_FILE_NEW
        MENUITEM "&Open...\tCtrl+O",       IDM_FILE_OPEN
        MENUITEM "&Save\tCtrl+S",          IDM_FILE_SAVE
        MENUITEM "Save &As...",            IDM_FILE_SAVE_AS
        MENUITEM SEPARATOR
        MENUITEM "Page Set&up...",         IDM_FILE_PAGE_SETUP
        MENUITEM "Print Pre&view",         IDM_FILE_PRINT_PRE
        MENUITEM "&Print...\tCtrl+P",      IDM_FILE_PRINT
        MENUITEM SEPARATOR
        MENUITEM "E&xit\tCtrl+E",          IDM_FILE_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo\tCtrl+Z",          IDM_EDIT_UNDO
        MENUITEM SEPARATOR
        MENUITEM "Cu&t\tCtrl+X",           IDM_EDIT_CUT
        MENUITEM "&Copy\tCtrl+C",          IDM_EDIT_COPY
        MENUITEM "&Paste\tCtrl+V",         IDM_EDIT_PASTE
        MENUITEM "&Delete\tDel",           IDM_EDIT_DELETE
        MENUITEM SEPARATOR
        MENUITEM "Select &All\tCtrl+A",    IDM_EDIT_SELECT_ALL
        MENUITEM SEPARATOR
        MENUITEM "C&hoose Font...",        IDM_EDIT_CHOOSEFONT
    END
    POPUP "&View"
    BEGIN
        MENUITEM "&Toolbar",               IDM_VIEW_TOOLBAR, CHECKED
        MENUITEM "&Status Bar",            IDM_VIEW_STATUS_BAR, CHECKED
        MENUITEM "&Options...",            IDM_VIEW_OPTIONS
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&Help...",               IDM_HELP_HELP
        MENUITEM "&About cChild",          IDM_HELP_ABOUT
    END
END

The words BEGIN, END, MENUITEM, POPUP, SEPARATOR, DISCARDABLE, etc. are additional keywords in the language of .RC files. But things like IDM_FILE_NEW and IDM_EDIT_PASTE are application specific manifest constants. They are nothing more than macro names for numbers that are all unique. You place the declarations of these numbers in a header file. Microsoft's MFC convention is to name this header file resource.h so you will see that filename mentioned in some books. I prefer to name this file after my top-level window so, for example, my Breakout example program has a top-level window class named cBreakoutDbg and I name its resource.h file cBreakoutDbgResource.h.

The contents of a Resource.h file will look like:

#define   IDM_FILE_NEW    101
#define   IDM_FILE_OPEN   102
#define   IDM_FILE_SAVE   103
...

You put these manifest constants into a separate header file because you need to #include this header file both in your .RC file and in your .CPP file. When the operator selects a particular menu entry Microsoft's code sends you a WM_COMMAND message that is accompanied by a WPARAM that is one of these values. You receive this message via the WndProc( ) function of your top-level window. The necessary code will look like what is shown below. Of course, the code shown below will not compile unless you provide the new member functions such as cTopLevel::OnFileNew( ) etc. We will be writing this type of code on the next page when we get to our next example program.

LRESULT CALLBACK WndProc( HWND   hWnd,
                          UINT   msg,
                          WPARAM wParam,
                          LPARAM lParam )
{
    switch ( msg )
    {
        case WM_COMMAND:
        {
            WORD     wNotifyCode = HIWORD( wParam ); // notification code
            WORD     wID         = LOWORD( wParam ); // menu item, control, or
                                                     //   accelerator identifier
            switch( wID )
            {
                case IDM_FILE_NEW:
                {
                    // TODO: Call a member function to handle this msg
                    pThis->OnFileNew();   // respond to the IDM_FILE_NEW msg
                    return 0;
                }
                case IDM_FILE_OPEN:
                {
                    // TODO: Call a member function to handle this msg
                    pThis->OnFileOpen();  // respond to the IDM_FILE_OPEN msg
                    return 0;
                }
                < LOTS MORE case STATEMENTS HERE >
            }
        }
        < LOTS MORE case STATEMENTS HERE >
    }
}

I have already explained about the pThis pointer on the Static Member Functions page so return there if you skipped it. Otherwise you should recognize from the code shown above that this top-level window class calls its OnFileNew( ) member function whenever it receives the IDM_FILE_NEW flavor of the WM_COMMAND message.

You can search on the TODO marker left by the New Project Wizard to find these locations in the WndProc( ) function where you'll need to provide application specific code.

Each menu item mentioned in the MENU section of the .RC file should have an access key and may have an accelerator key defined for it. For example, in the definition of the "File/New" menu choice seen below:

    MENUITEM "&New...\tCtrl+N",        IDM_FILE_NEW

the access key is the letter N and the accelerator key is CTRL in combination with the letter N. The ampersand that identifies the access key will not be displayed when Microsoft paints the menu. Instead, the access key (which is the letter following the ampersand) will be drawn underlined. Access keys predate the invention of the mouse. Back when the computer had no mouse the operator had to employ the ALT key to open the menu. He then typed a single letter on the keyboard and that column of the menu would open. He then typed another single letter on the keyboard and that menu item would be selected.

Accelerator keys are a newer invention that avoids the need to open the menu and proceed into a particular column. Instead, the accelerator keystroke immediately selects the particular menu choice. If you are following this explanation then you can appreciate that access keys only need to be unique within their column of the menu while accelerator keys must be unique within the entire menu. The letters "\t" seen in the MENUITEM above represent a TAB that causes the "Ctrl+N" portion to be right justified and hence nicely separated from the menu item name ("New..." in this case).

The ellipsis (...) seen in some MENUITEMs don't do anything but merely indicate to the operator that he should expect that menu choice to lead to a dialog box. MENUITEMs without ellipsis immediately take some action without giving you a chance to back out.

The accelerator keystroke mentioned in the MENUITEM doesn't do anything either unless you include an ACCELERATORS section in your .RC file to actually define your accelerated keystrokes. The default ACCELERATORS section that the New Project Wizard constructs for the top-level window's .RC file is shown below. The string "^N" indicates that the accelerator keystroke is CTRL in conjunction with N.

IDR_MainAccelerator ACCELERATORS DISCARDABLE
BEGIN
    "^N",           IDM_FILE_NEW,           ASCII, NOINVERT
    "^O",           IDM_FILE_OPEN,          ASCII, NOINVERT
    "^S",           IDM_FILE_SAVE,          ASCII, NOINVERT
    "^P",           IDM_FILE_PRINT,         ASCII, NOINVERT
    "^E",           IDM_FILE_EXIT,          ASCII, NOINVERT
    "^Z",           IDM_EDIT_UNDO,          ASCII, NOINVERT
    VK_BACK,        IDM_EDIT_UNDO,          VIRTKEY, ALT, NOINVERT
    "^X",           IDM_EDIT_CUT,           ASCII, NOINVERT
    VK_DELETE,      IDM_EDIT_CUT,           VIRTKEY, SHIFT, NOINVERT
    "^C",           IDM_EDIT_COPY,          ASCII, NOINVERT
    VK_INSERT,      IDM_EDIT_COPY,          VIRTKEY, CONTROL, NOINVERT
    "^V",           IDM_EDIT_PASTE,         ASCII, NOINVERT
    VK_INSERT,      IDM_EDIT_PASTE,         VIRTKEY, SHIFT, NOINVERT
    VK_DELETE,      IDM_EDIT_DELETE,        VIRTKEY, NOINVERT
    "^A",           IDM_EDIT_SELECT_ALL,    ASCII, NOINVERT
    VK_F1,          IDM_HELP_HELP,          VIRTKEY, NOINVERT
END

The reason that the IDM_EDIT_CUT, IDM_EDIT_COPY, etc. choices are listed twice in this ACCELERATORS section is that Windows programs need to support both Microsoft's old method of cutting and pasting text (involving SHIFT+DEL, SHIFT+INS, etc.) and Microsoft's new method (involving CTRL+X, CTRL+V, etc.).

The very last line of this ACCELERATORS section shows how you request that the F1 keystroke should result in the IDM_HELP_HELP flavor of the WM_COMMAND message, and hence has an identical consequence to the Help/Help choice in the menu.

We see the use of the same manifest constants such as IDM_FILE_NEW in the ACCELERATORS section as we did in the MENU section. These constants appear in one additional place in the .RC file, which is the STRINGTABLE section. Shown below is the default STRINGTABLE section that the New Project Wizard constructs:

STRINGTABLE DISCARDABLE
BEGIN
    IDM_FILE_NEW              "Creates a new document"
    IDM_FILE_OPEN             "Opens an existing document"
    IDM_FILE_SAVE             "Saves the active document"
    IDM_FILE_SAVE_AS          "Saves active document with a new name"
    IDM_FILE_PAGE_SETUP       "Changes page layout settings"
    IDM_FILE_PRINT_PRE        "Displays a simulation of printed output"
    IDM_FILE_PRINT            "Prints the active document"
    IDM_FILE_EXIT             "Quits the application"
    IDM_EDIT_UNDO             "Reverses the last action"
    IDM_EDIT_CUT              "Cuts selection and puts it on Clipboard"
    IDM_EDIT_COPY             "Copies selection and puts it on Clipboard"
    IDM_EDIT_PASTE            "Inserts Clipboard contents at insertion point"
    IDM_EDIT_DELETE           "Deletes the selection"
    IDM_EDIT_SELECT_ALL       "Selects the entire document"
    IDM_EDIT_FIND             "Finds the specified text"
    IDM_EDIT_FINDNEXT         "Repeats the last find in the forward direction"
    IDM_EDIT_FINDPRIOR        "Repeats the last find in the backward direction"
    IDM_EDIT_REPLACE          "Replaces specific text with different text"
    IDM_EDIT_CHOOSEFONT       "Selects a new font"
    IDM_VIEW_TOOLBAR          "Shows/Hides the toolbar"
    IDM_VIEW_STATUS_BAR       "Shows/Hides the status bar"
    IDM_VIEW_OPTIONS          "Changes options"
    IDM_HELP_HELP             "Displays the on-line documentation"
    IDM_HELP_ABOUT            "Displays program info, version #, and copyright"
END

You can see that I use this STRINGTABLE section to hold the explanatory messages that the Microsoft operating system automatically displays when you allow the mouse cursor to loiter over a toolbar icon (the technology that accomplishes these pop-up help bubbles is called the tool tip). These same explanatory messages are displayed on the status bar as you navigate through the menu (I described the bipolar nature of the status bar here).

There is no section of the .RC file dedicated to the toolbar, evidently because the .RC file language was formalized before Microsoft invented the toolbar. But the toolbar is closely integrated with the menu. For example, when the operator selects the "File/Open..." menu choice, the Windows operating system sends your WndProc( ) function an IDM_FILE_OPEN message (this is really a flavor of the WM_COMMAND message). And when the operator clicks on the manila folder pushbutton seen on CPPIDE's toolbar, the Windows operating system sends your WndProc( ) function this very same IDM_FILE_OPEN message. Your WndProc( ) won't even be able to tell whether the operator employed the menu or the toolbar to make this selection.

All it takes to define a right-click context menu on a child window is to include a MENU section in the .RC file of the child window. Note the difference: the top-level window's menu is defined in the top-level window's .RC file while the right-click context menu is specific to each child window and hence is defined in the child window's .RC file. The default context menu that the New Project Wizard invents for each new child window class is shown below:

IDR_cSomeChildContextMenu MENU DISCARDABLE
BEGIN
    POPUP "Dummy"
    BEGIN
        MENUITEM "Context Menu Entry #1...",  IDM_cSomeChildContext1
        MENUITEM "Context Menu Entry #2...",  IDM_cSomeChildContext2
        MENUITEM "Context Menu Entry #3...",  IDM_cSomeChildContext3
    END
END

You receive these messages (e.g., the IDM_cSomeChildContext1 flavor of the WM_COMMAND message) via the WndProc( ) function of the child window. You can search for the TODO marker left by the New Project Wizard in the child window's .CPP file to find these locations where you will want to add application specific code. You can rename the macros such as IDM_cSomeChildContext1 so that they better fit your purposes. Just remember to make these changes in the child window's .CPP file, the child window's .RC file, and the child window's Resource.h file.

All of the dialog boxes that a program will display are described in separate DIALOG sections of the .RC file. For example, the default About Box created by the New Project Wizard is due to the following statements in the top-level window's .RC file:

IDD_About DIALOG DISCARDABLE  0, 0, 160, 140
                           // x, y, width, height
STYLE WS_POPUP | WS_CAPTION | DS_3DLOOK | WS_SYSMENU
CAPTION "About cTopLevel"
FONT 10, "MS Shell Dlg"
BEGIN
    ICON            IDI_cTopLevel, IDC_STATIC,
                    10,10,32,32
    CTEXT           "cTopLevel.EXE"
                    IDC_STATIC, 30,15,100,15
    CTEXT           "version 1.0"
                    IDC_STATIC, 30,25,100,15
    CTEXT           "Copyright (c) 2005"
                    IDC_STATIC, 30,40,100,15
    CTEXT           "by John Kopplin"
                    IDC_STATIC, 30,50,100,15
    CTEXT           "Distributed by:"
                    IDC_STATIC, 30,70,100,15
    CTEXT           "Computer Science Lab Inc."
                    IDC_STATIC, 30,80,100,15
    CTEXT           "Home page:"
                    IDC_STATIC, 30,90,100,15
    CTEXT           "www.computersciencelab.com"
                    IDC_STATIC, 30,100,100,15

    PUSHBUTTON      "OK", IDOK, 60,120,40,14
END

You get to specify the exact set of controls that should appear on each dialog. The main difficulty is in selecting locations and dimensions for these controls such that everything looks professional. The CTEXT statements that you see in this example DIALOG are "centered text" statements that cause a particular text string to appear in a particular spot on the dialog. The generic form of the CTEXT statement is:

   CTEXT   message, identifier, x, y, width, height

There is also an LTEXT (left justified text) and an RTEXT (right justified text) statement that you can use in your dialogs.

The ICON statement that you see used in the prior DIALOG section refers to a particular .ICO file. This .ICO file must be identified in an ICON section such as the following:

IDI_cTopLevel      ICON  DISCARDABLE  <filename>.ico

The picture in this .ICO file will appear on your About Box dialog. It will also appear in the upper left corner of your program's title bar.

It is easy to replace the default icon that I provide in the CppIde.ico file. The only difficulty is creating an .ICO file in the first place. Unfortunately the Paint program that Microsoft includes with all flavors of their Windows operating system cannot create an .ICO file. There are a number of free (or free during a 30 day evaluation period) programs available from the Internet. One such program is IconCool which is available from www.iconcool.com. The important thing to remember when creating an .ICO file is that you want to place both a 16 by 16 pixel, 16 color image and a 32 by 32 pixel, 16 color image into the .ICO file. The Windows operating system will employ both of these images at various times. For example, the 16 by 16 pixel image will be seen at the extreme upper left hand corner of the program's title bar and the 32 by 32 image will be seen in Windows Explorer if you select "View/Large Icons" from Windows Explorer's menu. The large icon is also seen whenever you press ALT+TAB to switch between whatever programs you have running.

Besides the About Box, the New Project Wizard also generates a fully functional Options dialog for the top-level window. This dialog includes a selection of check boxes, radio controls, edit controls, and pushbuttons. You can see how everything is accomplished by studying the top-level window's .RC file and its .CPP file. This code is there just to help get you started toward the particular Options dialog that makes sense for your program. This place-holder code assumes that the purpose of the Options dialog is to allow the operator to influence the m_bDemo1Value, m_bDemo2Value, and m_bDemo3Value data members of the top-level window class. These 3 data members (declared in the top-level window's .H file) have the data types BOOL, UINT (unsigned int), and enum respectively. The Options dialog offers a checkbox so the operator can specify the BOOL variable, an edit control so the operator can specify the UINT, and a radio control so the operator can specify the enum variable.

Every dialog box must have a DlgProc( ) ("dialog procedure"), which has many similarities yet is not identical to the WndProc( ) function that must be associated with every window. You can find the function prototype for the About Box's DlgProc( ) and the Options DlgProc( ) in the top-level window's .H file:

    static BOOL CALLBACK AboutDlgProc( HWND hWndDlg, UINT msg,
                                       WPARAM wParam, LPARAM lParam );
    static BOOL CALLBACK OptionsDlgProc( HWND hWndDlg, UINT msg,
                                         WPARAM wParam, LPARAM lParam );

Just as with the WndProc( ) function, the DlgProc( ) function cannot be a normal class member function and hence must be a static member function. This means you will see the pThis pseudo this pointer used in these functions.

The most important messages which are delivered to a DlgProc( ) function are the following:

msg name | msg purpose
WM_INITDIALOG | This message is the first message which a dialog box procedure will receive. It gives the dialog box the opportunity to initialize its controls with whatever default values the program prefers.
IDOK | This message is really a flavor of the WM_COMMAND message. Microsoft sends you this message when the operator clicks on the "OK" pushbutton to close the dialog.
IDCANCEL | This message is another flavor of the WM_COMMAND message. Microsoft sends you this message when the operator clicks on the "Cancel" pushbutton to abort from the dialog.

So how do you get a dialog box to appear in the first place? This is accomplished with a call to the WIN32 API's DialogBoxParam( ) function. For example, the code shown below would be placed in the top-level window's WndProc( ) function in order to respond to the View/Options menu (or toolbar) selection:

LRESULT CALLBACK WndProc( HWND   hWnd,
                          UINT   msg,
                          WPARAM wParam,
                          LPARAM lParam )
{
    switch ( msg )
    {
        case WM_COMMAND:
        {
            WORD     wNotifyCode = HIWORD( wParam ); // notification code
            WORD     wID         = LOWORD( wParam ); // menu item, control, or
                                                     //   accelerator identifier
            switch( wID )
            {
                case IDM_VIEW_OPTIONS:
                {
                    // Bring up the Options dialog.

                    DialogBoxParam( g_pWinApp->GetInst(),
                                    MAKEINTRESOURCE( IDD_Options ),
                                    hWndMain,          // hWndParent
                                    OptionsDlgProc,
                                    (LPARAM) pThis );  // smuggle the "this" ptr

                    return 0;
                }

It should come as no surprise that the DialogBoxParam( ) function requires a passed parameter that identifies the particular dialog box you want displayed and the name of the DlgProc( ) function which will begin receiving the messages generated by the operator's manipulation of the controls on the dialog box. In the code shown above the particular dialog box is described by the IDD_Options passed param which you will also find used in the top-level window's .RC file since that is where the controls for this particular dialog box are defined. And the OptionsDlgProc passed param identifies the particular DlgProc( ) function that should receive the messages. With this introduction and a study of the code generated by the New Project Wizard you should be able to get the hang of dialog boxes.

If you want to create your own button images for use on the toolbar then you will need to include a BITMAP section in your .RC file. This section can look like the following:

IDB_TBButtons  BITMAP  DISCARDABLE  <filename>.bmp

where IDB_TBButtons is a macro name for a manifest constant that you define in your Resource.h file. I describe the necessary code on the page entitled The Built-In Classes.

When you want to become an expert on .RC files I suggest you read the comments in the .RC files I have provided. And then you will want to dig into the Petzold book. Remember that I have provided all the infrastructure that allows you to compile Petzold's example programs from within CPPIDE. Full details on how to do this can be found here.


( 53 )

Interconnecting The Child Windows

When I introduced the cScore class (which paints a 7 segment display in a child window) I mentioned that I wanted to employ this child window in my Breakout game in order to display the score. This goal introduces the complication of getting one child window (an instance of the cBreakout class) to communicate with another child window (an instance of the cScore class). I'll show you how to accomplish this further down on this page after we get through some preliminaries.

At this time open the Breakout5 example program. You will observe that it consists of 11 source files. We see the usual AppMain.cpp file and the 4 files (.H, .CPP, .RC, and Resource.h) associated with the top-level window. For this program I decided to name the top-level window class cTopLevel. We also see the cBetterBreakout.h and cBetterBreakout.cpp files associated with the cBetterBreakout class. So I lied in the previous paragraph when I said we would be employing an instance of the cBreakout class. The cBreakout class is the defective class that allows the ball to pass through the walls, the bricks, and the paddle. In the Breakout4 example program we used the cBreakout class as a base class from which we derived a cBetterBreakout class that solved these problems. But because of the nature of inheritance, an instance of the cBetterBreakout class is also an instance of the cBreakout class and hence I really didn't lie.

If we had actually employed the cBreakout class then we wouldn't have seen the cBreakout.h and cBreakout.cpp files listed in the Files listbox shown in the left-hand pane of CPPIDE. This is because the cBreakout class is part of the CSL Framework and hence its code is already compiled into the libCSL.a file with which we link our program. The New Project Wizard understands all this. But the cBetterBreakout class is not already compiled into the libCSL.a file and hence this code (the cBetterBreakout.h and cBetterBreakout.cpp files) must appear in our Breakout5 project.

This brings up an organizational problem. Have you noticed that the Breakout2, Breakout3, and Breakout4 example programs all included copies of the cBetterBreakout.h and cBetterBreakout.cpp files? This was acceptable in those example programs because we were incrementally adding functionality and hence those files really aren't identical. And in this new Breakout5 example program we are again going to be adding functionality (specifically, the ability to communicate with a cScore instance) and hence it is acceptable that this project includes yet another copy of these files.

But consider the cScore.h, cScore.cpp, cScore.rc, and cScoreResource.h files. We want to employ the exact versions of these files that we developed for the ThreePanes example program. The easiest thing to do is to make copies of these files and place them in the Breakout5 project folder. This is easy but not smart. Anytime you allow multiple copies of a file to exist you could start to experience drift between the contents of those files. For example, you might modify the cScore.cpp file while working on the ThreePanes example program and then later you will wonder why the changes that you clearly remember making are not appearing in the Breakout5 example program.

Hence it is better to maintain only a single copy of your source files. When you want to re-employ an existing class in a new project you want to point the compiler to the original location of those files rather than making another copy of those files in the current project's folder. This better approach is demonstrated in the Breakout5 program in its handling of the cScore files. In the Files listbox shown on the left-hand pane of CPPIDE you can see that all of the cScore filenames begin with double dots (..). These leading double dots are part of what is called a filename with a relative path.

Here's the one paragraph explanation of relative paths. The files on the hard drive are placed in folders which themselves are organized in a upside down tree structure. The root of the entire hard drive is indicated by a backslash (\). Any filename that begins with a leading backslash is called a filename with an absolute path. For example, if you selected the default options when you installed Computer Science Lab then the absolute path to the Breakout5 project folder is given by "\Computer Science Lab\CppIde\Breakout5". You can use an absolute path to describe any file on the hard drive. But because I don't know what choices you will make when you install the Computer Science Lab CD-ROM, I can't employ an absolute path to describe the location of the cScore files. Luckily, I can employ a relative path. A relative path is any filename that does not begin with a leading backslash. Relative filenames express a new location relative to a particular starting location known as the current directory (the cd command from MS-DOS changes the current directory). Hence the filename "AppMain.cpp" indicates a .CPP file located in the current directory. The filename "..\ThreePanes" indicates the file (it's actually a folder) named ThreePanes that resides in the parent folder of the current directory. The filename "..\ThreePanes\cScore.h" indicates the cScore.h header file found in the ThreePanes folder that resides in the parent folder of the current directory. You can think of the double dots as meaning "go up 1 level" in the directory tree.

The following illustration should help you understand the relative path in the "..\ThreePanes\cScore.h" filename:

At this point I hope it is clear how the entries in the Files listbox in the left-hand pane of CPPIDE identify files taken from another project, thereby avoiding the need to make a copy of these files. You can use Windows Explorer to look at the contents of the Breakout5 project folder and you will not find these files. Yet the code compiles!

Unfortunately, the Microsoft guys couldn't bring themselves to adopt the relative filename notation already in use in the computer industry. The older UNIX operating system employs the double dots but uses forward rather than backward slashes to delimit the various parts of the relative path. The Gnu C++ compiler works better if we employ UNIX notation in the makefile and hence when you look there you will observe that the backslashes have been replaced with forward slashes.

Now let's get back to the construction of the Breakout5 example program. I used the New Project Wizard to originate the files for this project. My plan for the next few paragraphs is to describe each of the modifications I had to make to the wizard generated code in order to arrive at the end result you see before you now. I am hoping you will then feel comfortable enough with the customization process to use the New Project Wizard for your own projects.

In Step #1 of the New Project Wizard I chose to create a project named Breakout5 that would be a Windows program based on the CSL Framework and written in the C++ language. In Step #2 I elected to name my top-level window class cTopLevel. In Step #3 I requested the 3 child windows: cStaticText, cScore, and cBetterBreakout. Because the New Project Wizard knows that the cStaticText class is part of the libCSL.a file it did not add a cStaticText.h, cStaticText.cpp, cStaticText.rc, or cStaticTextResource.h file to the project.

But because the New Project Wizard has not heard of the cBetterBreakout class it constructs a cBetterBreakout.h, a cBetterBreakout.cpp, a cBetterBreakout.rc, and a cBetterBreakoutResource.h file and places these files in the project folder. For the same reason, the New Project wizard constructs a cScore.h, a cScore.cpp, a cScore.rc, and a cScoreResource.h file and places these files in the project folder. Consequently, after the New Project Wizard had finished the Files listbox showed the following 15 entries:

AppMain.cpp
cTopLevel.h
cTopLevel.cpp
cTopLevel.rc
cTopLevelResource.h
cScore.h
cScore.cpp
cScore.rc
cScoreResource.h
cBetterBreakout.h
cBetterBreakout.cpp
cBetterBreakout.rc
cBetterBreakoutResource.h
Breakout5.prj
Breakout5.mak

I knew that the cBetterBreakout class could just employ the .RC file of the cBreakout class since I wasn't going to be adding any new Windows Resources in the derived class. Hence I could eliminate both the cBetterBreakout.rc and cBetterBreakoutResource.h files. I used CPPIDE to edit the .PRJ file and the .MAK file to eliminate all references to these files and then I used Windows Explorer to delete the unnecessary files from the project folder. Remember that anytime you edit your .PRJ file you will need to re-open that project in order to observe your changes.

Next I used Windows Explorer to overwrite the remaining cBetterBreakout.h and cBetterBreakout.cpp files with copies of the versions used in the Breakout4 example program. We will be modifying these files in a moment at which time they will no longer be copies.

Next I used CPPIDE to again edit the .PRJ and .MAK files so that the 4 cScore files would be described by relative paths. After making this change I no longer needed the 4 boilerplate cScore files that had been created by the New Project Wizard so I used Windows Explorer to delete these 4 files from the project folder. At this point the project folder held only the 9 files:

AppMain.cpp
cTopLevel.h
cTopLevel.cpp
cTopLevel.rc
cTopLevelResource.h
cBetterBreakout.h
cBetterBreakout.cpp
Breakout5.prj
Breakout5.mak

I was now finished modifying the .PRJ and .MAK files but I still had to make a few more changes in the source files. The following statement placed in the cTopLevel.h file by the New Project Wizard:

    #include "cScore.h"

had to be changed to:

    #include "../ThreePanes/cScore.h"

since this filename now needed to include a relative path. Similarly, the following 2 statements placed in the cTopLevel.rc file by the New Project Wizard:

    #include "cScore.rc"
    #include "cBetterBreakout.rc"

had to be changed to:

    #include "../ThreePanes/cScore.rc"
    #include "cBreakout.rc"

At this point the code compiled and I was ready to begin adding the new functionality. These preparatory steps may seem like a hassle but they accomplish a very important goal: code re-use. The code that you write the fastest is the code you wrote yesterday. The code that has the fewest bugs is the code you have been using all year.

The code prepared by the New Project Wizard placed the 3 child windows in separate rows. I wanted the cStaticText instance and the cScore instance to share the top row. In order to accomplished this I merely had to modify the wizard generated code located in the top-level window's OnSize( ) member function. These changes are very straightforward so I won't spoon feed them to you. You can find them in the cTopLevel.cpp file. When I recompiled the project the cStaticText instance and the cScore instance were sharing the top row and the splitter bar was functional.

At this point I wanted to connect the menu to the cBetterBreakout child window. Notice that I had to decide which of the child window panes the top-level window's menu should influence. That is, the top-level window menu has an entry called "File/New". If the operator selects this menu item should the cStaticText instance be modified in some way? Or should the cScore instance or the cBetterBreakout instance be modified in some way? There is no right answer. It might be appropriate to modify a number of the child windows. You have to decide. You are the designer.

In my case, I decided that I wanted the top-level window's menu to show the word "Game" rather than the more customary "File". And under the Game heading I wanted the choices Start, Pause, Resume, and Exit. Therefore I modified the MENU section of the cTopLevel.rc file as shown below:

IDR_MainMenu MENU DISCARDABLE
BEGIN
  POPUP "&Game"
  BEGIN
    MENUITEM "&Start\tCtrl+S",         IDM_GAME_START
    MENUITEM "&Pause\tCtrl+P",         IDM_GAME_PAUSE
    MENUITEM "&Resume\tCtrl+R",        IDM_GAME_RESUME
    MENUITEM SEPARATOR
    MENUITEM "E&xit\tEsc",             IDM_GAME_EXIT
  END
  POPUP "&View"
  BEGIN
    MENUITEM "&Toolbar",               IDM_VIEW_TOOLBAR, CHECKED
    MENUITEM "&Status Bar",            IDM_VIEW_STATUS_BAR, CHECKED
    MENUITEM "&Options...",            IDM_VIEW_OPTIONS
  END
  POPUP "&Help"
  BEGIN
    MENUITEM "&Help...",               IDM_HELP_HELP
    MENUITEM "&About Breakout",        IDM_HELP_ABOUT
  END
END

To accompany the new menu structure, I also had to change the accelerators selected by the New Project Wizard:

IDR_MainAccelerator ACCELERATORS DISCARDABLE
BEGIN
    "^S",        IDM_GAME_START,    ASCII, NOINVERT
    "^P",        IDM_GAME_PAUSE,    ASCII, NOINVERT
    "^R",        IDM_GAME_RESUME,   ASCII, NOINVERT
    "^O",        IDM_VIEW_OPTIONS,  ASCII, NOINVERT
    VK_ESCAPE,   IDM_GAME_EXIT,     VIRTKEY, NOINVERT
    VK_F1,       IDM_HELP_HELP      VIRTKEY, NOINVERT
END

These changes require that I define the new IDM_GAME_START, IDM_GAME_PAUSE, etc. macros in the cTopLevelResource.h file. I also deleted the old IDM_FILE_NEW, IDM_FILE_OPEN, etc. macros because I knew that this would cause the compiler to issue error messages at all the other locations where the code was still employing the old manifest constants. Sure enough, I got compiler error messages both in the boilerplate code generated by the New Project Wizard for the toolbar and in the WndProc( ) function for the WM_COMMAND message.

I modified the WM_COMMAND section of the cTopLevel::WndProc( ) function as summarized below. Notice how easy it was to get the top-level window's menu to act upon the cBetterBreakout child window.

LRESULT CALLBACK cTopLevel::MainWndProc( HWND   hWndMain,
                                         UINT   msg,
                                         WPARAM wParam,
                                         LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_COMMAND:
        {
            WORD     wNotifyCode = HIWORD( wParam ); // notification code
            WORD     wID         = LOWORD( wParam ); // menu item, control, or
                                                     //   accelerator identifier
            HWND     hWndChild   = (HWND) lParam;
            HMENU    hMenu       = GetMenu( hWnd );

            switch ( wID )
            {
                case IDM_GAME_START:
                  pThis->m_pBetterBreakout->StartGame();
                  return 0;

                case IDM_GAME_PAUSE:
                  pThis->m_pBetterBreakout->PauseGame();
                  return 0;

                case IDM_GAME_RESUME:
                  pThis->m_pBetterBreakout->RestartGame();
                  return 0;

                case IDM_GAME_EXIT:
                  SendMessage( hWndMain, WM_CLOSE, 0, 0 );
                  return 0;

                default:
                    break;

            } // end of switch (wID)

            break;
        }
        ...
    }
}

I was also getting compiler errors in the STRINGTABLE section of the cTopLevel.rc file because it still employed the obsolete IDM_FILE_NEW, IDM_FILE_OPEN, etc. macro names. I modified the STRINGTABLE so that it looked like:

STRINGTABLE DISCARDABLE
BEGIN
    IDM_GAME_START            "Initiates a new game"
    IDM_GAME_PAUSE            "Pauses the game"
    IDM_GAME_RESUME           "Resumes a paused game"
    IDM_GAME_EXIT             "Quits the game"
    IDM_VIEW_TOOLBAR          "Shows/Hides the toolbar"
    IDM_VIEW_STATUS_BAR       "Shows/Hides the status bar"
    IDM_VIEW_OPTIONS          "Changes options"
    IDM_HELP_HELP             "Displays the on-line documentation"
    IDM_HELP_ABOUT            "Displays program info, version #, and copyright"
END

At this point I had the project back in a state where all the files would compile. The rule in the programming industry is that if you achieve this condition at any time after 4:00 pm on a Friday afternoon DO NOT make any more changes or something mysterious will suddenly appear, you will feel compelled to fix it before starting your weekend, this will not prove easy, time will drag on, you will get home late for dinner, and you'll be one more demerit closer to a divorce. Major new additions should wait for Monday morning. Write documentation or test cases until it's time to go home.

The next thing to do is to replace the boilerplate Options dialog written by the New Project Wizard with one that makes sense for this particular program. In this case I was lucky that all of this code was already available in the Breakout4 example program and hence I only needed to cut and paste. In the cTopLevel.h file I eliminated the following data members:

// TODO: The Demo1 check box, Demo2 edit control, and Demo3 radio buttons
//       are only temporary place-holders intended to show you the code
//       that accomplishes a typical Options dialog.  You should replace
//       these items with something appropriate for your specific application.

    BOOL             m_bDemo1Value;     // used with the Demo1 checkbox
    UINT             m_uiDemo2Value;    // used with the Demo2 EDIT control
    enum  Demo3Values { eFirstVal, eSecondVal, eThirdVal };
    Demo3Values      m_Demo3Value;      // used with the Demo3 radio controls

It undoubtedly seems strange that I didn't have to replace these boilerplate data members with new ones that keep track of the options associated with the Breakout game. But it turns out that the cBreakout (actually the cBetterBreakout) instance holds these data members and hence I don't need to duplicate them. Instead the top-level window can call functions such as cBreakout::GetTrainingMode( ) whenever it wants to know whether the Breakout game is operating in its training mode. You can look at the cTopLevel::OptionsDlgProc( ) function in the cTopLevel.cpp file to see the code that implements the Options dialog. You can also look in the cTopLevel.rc file for the statements that describe the layout of the controls on the Options dialog. These statements haven't changed since the Breakout4 example program so I am not bothering to repeat them here.

Next I wanted to customize the call to the cBetterBreakout::Create( ) member function. The New Project Wizard doesn't know about the additional passed parameters that I can include in order to dictate the game board layout. I modified the code as shown below in order to demonstrate that you can request 3 rows of bricks:

#define NROWS  3        // actually found in the cTopLevel.h file
#define NCOLS 12

    // Create the cBetterBreakout child window.

    m_pBetterBreakout->Create(
                         this,               // parent window class
                         0,0,0,0,            // x,y,cx,cy
                         0,                  // extra window style flags
                         IDC_BetterBreakout, // child window ID
                         NROWS,              // nRows
                         NCOLS,              // nCols
                         crBlue );           // foreground brush color

    m_pBetterBreakout->Show();                        // make the window visible

At this point the program again compiled and I tried to play the game. But the ball sprite didn't start moving when I selected Game/Start from the menu or when I typed the CTRL+S accelerator keystroke or when I clicked on the blank sheet of paper on the toolbar (if I wasn't so lazy I should have drawn a new image for this toolbar button). After an embarrassing amount of study I realized that the reason I wasn't seeing the ball sprite move was that the game board completely lacked a ball sprite.

It was then that I remembered I designed this game in a manner that allowed you the freedom to customize the artwork for the ball sprite and to customize the sounds. Hence I needed to copy the Sprite.bmp, Paddle.wav, Brick.wav, Bumper.wav, and Lose.wav files from the Breakout4 project folder into the Breakout5 project folder. You can replace any of these 5 files in order to alter the style of the game. You can read more about how to do this here.

At this point the ball obeyed the menu and accelerator keys and I heard sound effects. But I couldn't move the paddle.

The cursor control keys on the keyboard (including the Home and End keys) are supposed to move the paddle. By now you have heard me say a number of times that the Windows operating system delivers the keyboard keystrokes to whatever window currently possesses the keyboard focus. When we initiate the Breakout5 example program it is the top-level window that is assigned keyboard focus. Hence the keystrokes are delivered (as WM_KEYDOWN messages) to the WndProc( ) function of the top-level window. The cBreakout class will move the paddle when its WndProc( ) function receives these keystrokes. Hence the problem is that the top-level window is not passing the WM_KEYDOWN messages on to the appropriate child window. The solution is to add the following code to the top-level window's WndProc( ) function:

LRESULT CALLBACK cTopLevel::MainWndProc( HWND   hWndMain,
                                         UINT   msg,
                                         WPARAM wParam,
                                         LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_KEYDOWN:
        {
            // We have to watch for the characters that control the
            // breakout game and pass them along to our child window.

            switch ( wParam )
            {
            case VK_RIGHT:
            case VK_LEFT:
            case VK_HOME:
            case VK_END:
                SendMessage( pThis->m_pBetterBreakout->GetWnd(),
                             WM_KEYDOWN, wParam, lParam );
                return 0;

            case VK_ESCAPE:                 // fall through
            case VK_F12:
                PostMessage( pThis->m_hWnd, WM_CLOSE, 0, 0 );
                break;

            default:
                break;
            }

            break;
        }

Okay, we are finally through all the preliminaries of initiating a new program and configuring its child windows. I hope you appreciated my blow-by-blow description of the steps involved in customizing the code produced by the New Project Wizard because once you get the hang of this technique it will be a real productivity enhancer.

Our final goal is to convince the cScore child window to continually display the score as the ball obliterates the bricks. The cScore class offers an AdvanceScore( ) member function that is perfect for this job. Any code that holds an instance of the cScore class can use it to call this member function. At the moment only our top-level window class (cTopLevel) holds an instance of the cScore class. But the top-level window has no information about when the bricks disappear on the cBetterBreakout child window. This is a situation where we need direct communication from one child window to another child window.

This could be easily accomplished if the cBetterBreakout class could get the pointer to the cScore instance. We don't want the cBetterBreakout class to instantiate (create) another cScore instance, we want the cBetterBreakout class to have access to the cScore instance created by the top-level window. So the top-level window should just make a copy of its m_pScore data member available to the cBetterBreakout instance.

We could invent a new member function for the cBetterBreakout class, perhaps something called SetcScorePointer( ) which the top-level window would call and -- via a passed parameter -- deliver the m_pScore pointer. I didn't favor this approach because it allows the person writing the top-level window class to forget to call this function. Whenever possible I try to design my code in a way that prevents future mistakes. The top-level window has to call the cBetterBreakout's Create( ) member function and hence I decided to add a new passed parameter to this function. This passed parameter has no default value and hence I had to insert it before all of the parameters that do include default values. The new function prototype is shown below:

    BOOL    Create( cBaseWindow * pParent,
                    UINT x, UINT y, UINT cx, UINT cy,
                    DWORD dwWS_Flags,   // extra window style flags
                    UINT  uiChildID,
                    cScore * pScore,    // our link to the child window
                                        //   that displays the score
                    UINT  nRows = 2,
                    UINT  nCols = 12,
                    COLORREF  crForeground = crRed,
                    COLORREF  crBackground = crWhite,
                    TCHAR * pszObjName = NULL );
                                        // arbitrary component name

You can compare this to the original cBreakout::Create( ) member function found in the cBreakout.h file in the CppIde\include folder. Because this code is now using the cScore data type I have to allow the compiler to see the definition of this data type. This is accomplished by adding a #include "../ThreePanes/cScore.h" statement to the cBetterBreakout.h file.

I also added a data member to the cBetterBreakout class as shown below:

class cBetterBreakout : public cBreakout
{
public:
    // Constructors
    //   We don't have to list a constructor or destructor because
    //   we are happy with the base class's constructor and destructor.

    // Interface
    //   We override the Create() member function of the base class
    //   (cBreakout) and keep all of its other member functions.

    BOOL           Create( cBaseWindow * pParent,
                           UINT x, UINT y, UINT cx, UINT cy,
                           DWORD dwWS_Flags,   // extra window style flags
                           UINT  uiChildID,    // used for the hMenu param
                                               //   passed to CreateWindow()
                           cScore * pScore,    // our link to the child window
                                               //   that displays the score
                           UINT  nRows = 2,
                           UINT  nCols = 12,
                           COLORREF  crForeground = crRed,
                           COLORREF  crBackground = crWhite,
                           TCHAR * pszObjName = NULL );
                                               // arbitrary component name

    // Callbacks
    virtual void CALLBACK  UpdateSprites( cCallbackObj * pPublisherObj,
                                          void * pPublisherData,
                                          void * pSubscriberData );

protected:
    // Data
    cScore     * m_pScore;
};

This new data member is initialized in the cBetterBreakout::Create( ) function as shown below. Note that in the earlier Breakout4 example program the cBetterBreakout class did not override the cBreakout::Create( ) function but now it must in order to accommodate the new passed parameter. Check out how we still leave most of the work to the original function simply by calling it (the base class's version) from within the derived class's version.

///////////////////////////////////////////////////////////////////////////////
// cBetterBreakout::Create()
//
// This function replaces the Create() function found in the cBreakout
// class.  The difference is the new passed parameter which delivers
// a pointer to a cScore instance.
///////////////////////////////////////////////////////////////////////////////

BOOL cBetterBreakout::Create(
                         cBaseWindow * pParent,
                         UINT x, UINT y, UINT cx, UINT cy,
                         DWORD dwWS_Flags,    // extra window style flags
                         UINT  uiChildID,     // used for the hMenu param
                                              //   passed to CreateWindow()
                         cScore * pScore,     // our link to the child window
                                              //   that displays the score
                         UINT  nRows,
                         UINT  nCols,
                         COLORREF  crForeground,
                         COLORREF  crBackground,
                         TCHAR * pszObjName ) // arbitrary component name
{
    m_pScore = pScore;                // store the pointer to the cScore instance

    return cBreakout::Create( pParent, x, y, cx, cy,   // then call the base class's
                              dwWS_Flags, uiChildID,   //   version of this same
                              nRows, nCols,            //   function
                              crForeground, crBackground,
                              pszObjName );

} // cBetterBreakout::Create()

Due to the new passed parameter, we must modify the code in cTopLevel.cpp where the top-level window calls the cBetterBreakout::Create( ) function:

    // Create the cBetterBreakout child window.

    m_pBetterBreakout->Create(
                         this,               // parent window class
                         0,0,0,0,            // x,y,cx,cy
                         0,                  // extra window style flags
                         IDC_BetterBreakout, // child window ID
                         m_pScore,           // pointer to cScore child window
                         NROWS,              // nRows
                         NCOLS,              // nCols
                         crBlue );           // foreground brush color

    m_pBetterBreakout->Show();                        // make the window visible

At this point the program again compiles, yet the cScore child window does not advance. But that's because nobody is calling its AdvanceScore( ) member function. Our final modification is to add the following inside the cBetterBreakout::UpdateSprites( ) function:

        if ( IntersectRect( &rcOverlap, &rcBrick, &rcBall ) )
        {
            m_uiColumnData[col] &= ~( 1 << row ); // remove brick

            m_pScore->AdvanceScore( 100 );

            if ( m_bMakeSound )
            {
                PlaySound( BRICKSNDFILENAME, NULL,
                           SND_FILENAME | SND_ASYNC );
            }

Voila, we have a working game! As things stand now, the score keeps accumulating between balls. I charge you with the job of figuring out how you can clear the score each time the player loses his ball (hint: it only takes 1 new statement). Remember also that the cScore child window boasts a right-click context menu that allows you to change the color scheme of the 7 segment display. And the View menu allows you to hide the toolbar and/or the status bar. All of this is functional.

We haven't yet done anything with the cStaticText child window. This is prime screen real estate so let's do the obvious thing and slap some advertising there. This can be accomplished by adding the statements shown below in red to the cTopLevel.cpp file. These statements request the 20 point "Comic Sans MS" font. If that font is not installed on your computer the operating system will automatically substitute a similar font so you should still see something. Microsoft's WordPad program (included in all versions of the Windows operating system) will show you the names of all the fonts installed on your computer and you might enjoy trying a few crazy ones.

    // Create the cStaticText child window.

    m_pStaticText->Create( this,             // parent window class
                           0,0,0,0,          // x,y,cx,cy
                           0,                // extra window style flags
                           IDC_StaticText ); // child window ID

    m_pStaticText->Set( TEXT( "CPPIDE Rocks!" ) );

    m_pStaticText->SetFont( TEXT( "Comic Sans MS" ),  // typeface
                            RGB( 0xFF, 0x00, 0x00 ),  // text color = red
                            200 );                    // 10 times desired point size

    m_pStaticText->Show();                   // make the window visible

We request that the cStaticText child window employ a red font but this won't happen unless we add the statements that I listed for the cStaticText class on the Built In Classes page. These statements, which are repeated below (slightly modified), are added to the top-level window's WndProc( ) function. Due to how Microsoft designed the static control, we won't get the color we desire on the child window unless the parent window's WndProc( ) function responds to the WM_CTLCOLORSTATIC message.

When the operator right clicks within the cStaticText child window we want to display the standard Windows "Choose Font" dialog. A right-click within a child window causes a WM_CONTEXTMENU message to be issued to that window's WndProc( ) function. The cStaticText class encapsulates the standard Microsoft static control which means that Microsoft wrote the WndProc( ) function. This is nice because it saves us work but it is also limiting since we can't specify the code that will respond to the WM_CONTEXTMENU message. Fortunately, Microsoft designed this WndProc( ) function such that it relays the WM_CONTEXTMENU message to the parent window's WndProc( ) function. Hence we can respond to this message by placing code in the parent window's WndProc( ) function, which is what you see below.

Now since we only want a single item on this right-click context menu I don't even bother displaying that entry but instead I proceed directly to the Choose Font dialog. I merely need to call the cStaticText::ChooseFont( ) member function to display this standard dialog.

The modification that I referred to earlier is the if statement that looks at the wParam passed parameter that accompanies the WM_CONTEXTMENU message and which identifies the HWND ("window handle") of the window in which the right click occurred. Without this if statement you would discover that a right-click over the Breakout game board would also lead to the Choose Font dialog. The code casts the wParam to the correct data type, which in the case of the WM_CONTEXTMENU message is the HWND data type. Every child window class in the CSL Framework has a GetWnd( ) member function that returns the HWND of that child window. You'll find this function prototyped in the cBaseWindow.h header file found in the CppIde\include folder. Remember that since every child window class inherits from cBaseWindow, every child window class gains all of the functionality of the cBaseWindow class. This is the power of inheritance.

LRESULT CALLBACK cTopLevel::MainWndProc( HWND   hWndMain,
                                         UINT   msg,
                                         WPARAM wParam,
                                         LPARAM lParam )
{
    switch ( msg )
    {
        case WM_CREATE:
        {
            <some stuff>
            return 0;
        }
        case WM_CONTEXTMENU:
        {
            HWND    hWnd = (HWND) wParam;

            if ( hWnd == pThis->m_pStaticText->GetWnd() )
                pThis->m_pStaticText->ChooseFont();

            break;
        }
        case WM_CTLCOLORSTATIC:
        {
            return (LONG) pThis->m_pStaticText->OnCtlColorStatic(
                                                      (HDC) wParam,
                                                      (HWND) lParam );
        }


Wrap Up

( 54 )

Wrapping Up

I hope you have enjoyed studying my three part computer programming curriculum. If you have made it this far you definitely know a thing or two about programming and are ready for bigger adventures ahead. CPPIDE can continue to be your integrated development environment for any console-mode or Windows programs in the C or C++ language that you want to write or that you find on the Internet or in a book.

Here is a list of C++ topics that I don't cover but which you might want to pursue next:

  • Iostreams
  • Operator Overloading
  • Exceptions
  • Namespaces
  • Virtual Functions
  • Templates
  • Multiple Inheritance

I don't teach iostreams and operator overloading because I detest them. Their complexity way outweighs their worth. You can find some more comments about iostreams in this appendix.

I also don't like exceptions because they are a technology that slows down the execution of your programs without providing a large benefit. Those who favor exceptions claim (probably rightly) that too few programmers rigorously check the return codes returned by the functions they call and they further claim (probably wrongly) that exceptions can make up for this poor programming practice. I say the solution is to rigorously check return codes. This way you can fix the problem at the point it is detected. The design of the exception mechanism takes you into a different section of code where it is difficult to continue with your original goal. Consequently most people use exceptions just to shut down the program. This is not an accomplishment worth slowing down the program.

Namespaces are pretty trivial and you can learn what you need to know in about 5 minutes. I like virtual functions (probably because I like indirection and pointers) and I encourage you to look into these. I only occasionally use templates because I have seen many instances where a deep inheritance tree and the use of templates have resulted in incomprehensible, bloated, slow code. If you think the C++ compiler's error messages are cryptic just wait till you get a few from template code. Multiple inheritance -- although occasionally useful -- introduces some black holes into the language and was therefore eliminated in Java.

Now that I have spewed I'll go ahead and recommend two books that cover these topics and all the other dark corners of the C++ language:

Deitel and Deitel: C++ How To Program
Bruce Eckel: Thinking In C++

These are such good books that I have taken the time to develop the infrastructure (namely, the .PRJ and .MAK files) that allow you to use CPPIDE to compile all 754 example programs demonstrated in these books. Click on the hyperlinks above for full details.

One utility that every programmer needs is a tool that compares files and visually highlights their differences. This tool is handy for comparing different revisions of the same file. Perhaps your program compiled yesterday and today you just can't figure out why it is not compiling. As long as you set aside a copy yesterday then you merely need to diff (difference) the two files. Microsoft's version of this utility is called WinDiff. Microsoft includes WinDiff on the Windows 98 operating system CD in the tools\reskit\files directory Note that windiff.exe won't run unless its necessary companion DLL, gutils.dll, is in the same folder. Another way to get WinDiff is to purchase the Platform SDK which is a CD-ROM that Microsoft sells for about $10.

I am hoping that someday I will be able to post on my web site at:

www.computersciencelab.com

some projects created by my students. You could send me a child window class you were willing to distribute. They say learning is a circle so I look forward to learning from one of your programs.


( 55 )

The Example Programs

I have prepared a number of example programs to help you learn to program in the C++ language. Each program has comment statements that explain its purpose and how it accomplishes that goal. I would definitely recommend that you study every one of these programs. I have carefully designed them to introduce new topics in a logical order and at a pace that hopefully won't rip your head from your shoulders. The example programs are integrated into these help pages. That is, read these help pages in order from the beginning and you will be told the appropriate time to explore each of the example programs.

Some of the programs I call "skeletons". These are the programs you will have to finish in order to develop your programming skills. I have provided the skeleton to save you typing and get you started on the right foot. But there is always something missing or just plain wrong in the programs I have labeled as skeletons. You can employ the usual "cut and paste" procedure to bring in code from any place you desire. However this will probably not be necessary as the number of changes that you must make to the skeleton programs is very small. I'm not kidding: initially, you will be asked to make 1 character changes and only later will you have to bear the burden of providing an entire new line of code! So don't fret. You can do this. Don't just skip past these exercises or you won't learn the material. Think for a while and try some wild guesses if the solution isn't immediately obvious. Watch your program run in the debugger. Your observations on what the program is and what it is not doing will give you ideas on what to try next. You can't hurt your computer or the CPPIDE program. Only when you are exhausted do I give you permission to look at my solutions to the skeleton exercises which you can find in the "Solutions" folder.

The Hello World Example Program

This program is the traditional introduction to any new programming language. It is about as short and sweet as any program can be. It is a console-mode program which means that it is limited to a textual user interface. It introduces the printf( ) function which is part of the standard library that comes with any C or C++ compiler.

The Hello World 2 Example Program

One of the most commonly asked questions that appears in the various programming newsgroups is, "Why does my console-mode window open and close so fast that I can't even read it?" CPPIDE (and Microsoft's Visual C++ IDE) incorporate a feature that prevents this behavior, but you can still observe it if you initiate a console-mode program by double-clicking its .EXE filename when displayed by Windows Explorer. This Hello World 2 example program shows you one method to keep your console-mode program's window open and visible until you explicitly dismiss it. The trick involves a call to the _getch( ) function which keeps the process running until the operator hits a key on the keyboard. As long as the process keeps running the Windows operating system will keep the program's window open.

The Factorial Example Program

This program demonstrates the algorithm that can compute the factorial of any positive integer. Unfortunately, the particular positive integer is hard-coded in the program and hence this program can only compute the one result of 5 factorial. This program introduces the while statement.

The Factorial 2 Example Program

This program is identical to the prior Factorial program except I have deleted most of the comments so you can better read the code while single-stepping the program with the Gnu GDB debugger.

The Factorial 3 Example Program

This program builds on the previous program by introducing a new temporary variable so that the original value of the operand is still available at the conclusion of the program.

The Factorial 4 Example Program

This program builds on the previous program by introducing the scanf( ) function that allows a console-mode program to accept input from the human operator.

The Factorial 5 Example Program

This program builds on the previous program by demonstrating how to protect your program from bad input typed by the human operator. Unfortunately, it doesn't quite work. And you have the assignment to fix it! This program introduces the exit( ) function.

The Factorial 6 Example Program

This program builds on the previous program by introducing a nested loop which allows the program to keep offering to compute yet another factorial. Unfortunately this program has a bug that causes all of its results (except for the first result) to be wrong. Together we will find this bug and fix it by working through the upcoming Factorial7 example program.

The Factorial 7 Example Program

This program is identical to the previous program except we have added printf( ) statements as debugging aids to help us find the bug that we observed in the Factorial 6 program.

The Factorial 8 Example Program

This program eliminates the bug that was observed in the Factorial 6 and 7 programs. The problem was that a variable needed re-initialization inside the outer loop.

The Factorial 9 Example Program

This program employs the same algorithm as the previous Factorial programs but it restructures the code into 2 functions. This is the first time that we have gotten to choose a function signature since the signature for the main( ) function is mandated by the compiler.

The Factorial 10 Example Program

This program demonstrates that the C language uses call by value to pass parameters to functions.

The Swap Example Program

This program demonstrates how call by value isolates a function from the code that calls the function.

The Swap 2 Example Program

This program demonstrates how the use of pointers for the passed params allows a function to directly manipulate variables owned by another scope. Pointers are simply variables that hold data memory addresses. Given the data memory address of a variable, you can certainly muck about with it.

The Factorial 11 Example Program

This program marks the first time we have employed multiple .CPP files to hold our program.

The Factorial 12 Example Program

This program marks the first time we have written a .H header file.

The Scale Example Program

This program introduces our first C++ class. You will enjoy the cScale class, which is defined in cScale.h and implemented in cScale.cpp, because it will allow you to play an arbitrary melody.

The Scale 2 Example Program

This is our first skeleton program, meaning it is incomplete. I have previously asked you to make small changes in my code, but this is the first time I have given you code that would not compile. As always, I provide my solution in the "Solutions" folder.

The Scale 3 Example Program

This is another skeleton program. You must again add a new member function to the cScale class. Then you'll be ready to hear Jimi and John, Paul, George, and Ringo.

The Errors Example Program

The only purpose of this project is to give us some code we can mutilate in order to observe the error messages produced by the compiler. It happens to hold the same code as the Scale project but I keep it separate so that no matter what you do here, the Scale project will remain healthy.

The Password Example Program

This program demonstrates how to ask the operator to enter a password and then how to check that character string for correctness. This program introduces the fgets( ) library function which, like printf( ) and exit( ), is included with any C/C++ compiler.

The Password 2 Example Program

This program introduces the strlen( ) library function.

The Password 3 Example Program

This program introduces the strcmp( ) library function.

The Colored Text Example Program

This program demonstrates how you can request any of 16 different colors for the text produced by the printf( ) function.

The Centered Text Example Program

This is another skeleton program that I ask you to complete. Your assignment is to center whatever text the operator chooses to type.

The Static Variables Example Program

This program introduces the static keyword and explains its purpose in the C language (we will encounter another usage of this same keyword in the C++ language).

The Command Params Example Program

This program introduces the argc and argv parameters that can optionally be passed into the main( ) function.

The Maze Solver Example Program

In this program we need to help a mouse navigate through a maze to find the hidden cheese. But this program employs the same faulty algorithm that we originally saw when exploring the maze with our 8051 microprocessor and hence the poor little mouse gets trapped in a vortex. Rather than fixing the algorithm, your assignment is to add new code which will halt the search anytime 4 consecutive left turns have been detected. I suspect this will be a tough assignment for you. No peaking at my solution in the Solutions folder!

The Maze Solver 2 Example Program

This program contains the better search algorithm that improves the mouse's diet. To see how fast your computer really is, remove the call to the Sleep( ) function located within the while(true) loop in the WinMain( ) function.

The Signboard Example Program

This program gives us a chance to learn more about arrays. It draws letters on an electronic scrolling signboard (which some people call a "reader board").

The Signboard 2 Example Program

You have the assignment to complete this program by providing the implementation for a new PaintLetter( ) function. Once this new function is available the logic in the WinMain( ) function will look a lot cleaner. To accomplish the solution I am allowing you to re-define the signboard variable (which is an instance of the cSignboardDbg class) as a global variable. You can find my solution in the Solutions folder, natch.

The Signboard 3 Example Program

This program improves the previous program by eliminating the global variables.

The Static Data Members Example Program

This program introduces the concept of a static data member, which is a data member that is shared by all instances of a class.

The Signboard 4 Example Program

This program converts the font information shared by all instances of the cSignboardDbg class into a static data member which is the ideal way to share data between all instances of a class.

The Peak Detect Example Program

The peak detector child window contains both a set of tape deck like pushbuttons and a dual-channel LED bar graph. This window has the intelligence to play any .WAV file while making the audio samples available to a callback function. You need to supply the code that receives these audio samples, computes the peak excursions, and then displays the musical peaks on the LED bar graph. You can find my solution in the Solutions folder.

The Breakout Example Program

This program is the start of our journey to develop a Breakout type of video game. Unfortunately, the moving ball goes right through the paddle, the walls, and the bricks.

The Breakout 2 Example Program

This program demonstrates class inheritance which is how we are going to add new functionality to the cBreakout class that implements the child window that hosts the Breakout game.

The Breakout 3 Example Program

This program comes with the code necessary to detect collisions between the moving ball and the top and bottom walls of the game board. Your assignment is to provide the additional code that detects collisions between the ball and the left and right walls. You will only need to change the UpdateSprites( ) member function of the cBetterBreakout class.

The Breakout 4 Example Program

This program successfully detects collisions between the ball and the walls, between the ball and the paddle, and between the ball and the bricks. Furthermore, the bricks disappear as they are hit. And a splitter bar allows you to adjust the relative sizes of the child windows.

The Dynamic Memory Example Program

This console-mode program doesn't do much other than introduce pointers to pointers and illustrate a bug caused by a function that foolishly returns the address of an automatic (stack based) variable.

The Three Panes Example Program

This Windows program demonstrates how CPPIDE's New Project Wizard coupled with the CSL Framework allow you to create arbitrary child window components that are then re-used in whatever top-level windows you desire. This program demonstrates how to componentize the Windows programs illustrated in the Petzold book.

The Breakout 5 Example Program

This example program gives me the opportunity to show you the full process of customizing the code produced by the New Project Wizard. And I demonstrate the technique that allows direct communication from one child window to another child window.


( 56 )

CPPIDE Options

This section describes the user-configurable options seen on the "Options" dialog that appears when you select "View/Options" from the CPPIDE menu or toolbar.

The first column lists options that apply to the Source code editor. The first option in the first column offers a checkbox that will cause line numbers to appear to the left of your source code statements. These line numbers are not part of your C++ code (some older programming languages required each statement to be numbered) but they are convenient since the error messages refer to these line numbers. Nonetheless, you don't need to display these line numbers because you only need to RIGHT click on any compiler error message printed in red to be taken to that very line (or just press F4 to be taken to your first 6 error locations).

The next option in the first column is another checkbox that will cause all white space (space characters and tab characters) to be made visible. This is occasionally useful while you are tracking down a compiler error message.

The next option in the first column is an edit control that allows you to specify how many spaces should be inserted into your source document each time you hit the TAB key on your computer's keyboard. TAB characters are automatically converted to spaces because, otherwise, the vertical alignment that you have worked so hard to achieve will be lost if you view the document on a different word processor or whenever the TAB effect is set to a different value.

The final editor option is a pair of checkboxes that allow you to add supplemental info in page headers and page footers when you print your source code.

At the bottom of the first column is a compiler option that allows you to select between performing a Debug build or a Release build. Only Debug builds can be debugged with the debugger and hence that is the default. A Release build results in a smaller executable (.EXE) file. This is the type of code you would release to your customers.

The second column lists options that apply to the debugger. The first option in the second column is a radio control that affects the relative window placement enforced between the debugger and a windows program that is being debugged. This radio control allows you to override a setting that is normally controlled by the .PRJ file. Note that this option has no effect when you are debugging console-mode programs.

The next option in the second column is an edit control that allows you to specify the number of program steps you want to perform every time you click on the Step Into N toolbar button. By default, this N value is set to 25 instructions. Below this is a checkbox that allows you to observe each of these 25 steps individually or else run them faster by not repainting the window until they have all completed.

The final option allows you to specify arguments that you want passed on the command line to your program when you initiate that program. It is rare for a Windows program to employ command line arguments but this is a common technique with console-mode programs. It is this option that allows you to affect the argc and argv parameters passed to your main( ) function.


Compiler Options

( 57 )

Gnu Compiler Options

The Gnu Compiler is known as gcc when it is used to compile C programs and as g++ when it is used to compile C++ programs. Any C++ compiler must be an incredibly sophisticated program. And that means complex. I document below the command line switches that I employ with the g++ compiler in the makefiles I include with my example programs. You can probably still find on the Internet the complete Gnu compiler manual here.

Switch | Explanation
-g | place debugging information in the executable (.EXE) file, thereby allowing the program to be debugged with the Gnu GDB debugger
-o <filename> | place the output in the specified file
-c | compile the source file (producing a .O object file) but do not link (which would produce an .EXE file)
-I <directory_name> | add the specified directory to the head of the list of directories to be searched for header files
-lXYZ | instructs the linker to search in the library named libXYZ.a for any unresolved identifiers such as function names
-L <directory_name> | add the specified directory to the list of directories to be searched for the libraries modules (.A files) that you specify using the -l switch. This list automatically contains several standard system directories such as mingw\lib.
-mwindows | You won't find this switch mentioned in the usual Gnu documentation, because the Gnu tools such as the C++ compiler are intended to produce UNIX executables. But this switch was added by the MinGW team to allow the Gnu compiler to produce Windows executable files.

Windows resource (.RC) files are not compiled by g++ but rather by a separate program called windres. Hopefully you can still find on the Internet the manual for the Gnu windres resource compiler here And I provide an appendix that describes the content of .RC files here.


( 58 )

Makefiles

make is a general purpose program that builds targets from prerequisites. We will only employ make to build executable (.EXE) files, but the reason that the makefile language is so arcane is that make has the ability to build many different things, not just executable files.

Since we will only employ make to build executables, the only types of prerequisites that we need to consider are C++ code (.CPP) files, C++ header (.H) files, and Windows Resource (.RC) files.

The advantage of using make is that it automatically knows how to check the timestamps of the various components and only recompile those object (.O) files which are older than their corresponding source files. That is, if you have changed the Main.cpp file then make knows that it can no longer re-use the existing Main.o file but must instead recompile Main.cpp into a newer Main.o file. Conversely, if you have not changed Main.cpp then make will re-use the existing Main.o and thereby shorten the time required to recompile the executable (.EXE) file.

The directions to the make program are stored in a plain text file which on UNIX computers is often called "makefile" or "Makefile". But on Windows computers it is more common to name this file with the project name followed by a .MAK suffix. This text file is called the makefile and it should be located in the same directory as the source files.

The heart of any makefile is a section of paired statements called rules. The first statement in each rule is called the dependency line and the second statement is called the command line. An example of a rule is shown below:

  Factorial12.exe: Main.o  SecondFile.o SecondFile.h
    g++ -o Factorial12.exe  Main.o  SecondFile.o

The target is the name prior to the colon on the dependency line and the prerequisites are the names following the colon. This dependency line states that the executable Factorial12.exe depends upon the 3 prerequisites Main.o, SecondFile.o, and SecondFile.h. This statement tells make that if any of the 3 dependent files have changed then it will have to regenerate Factorial12.exe. The entire dependency must be expressed with a single statement, but if there is a lot of text to type you are allowed to end a line with a backslash (\) and then continue on the next line.

The dependency line tells make WHEN to regenerate Factorial12.exe while the command line tells make HOW to regenerate Factorial12.exe. All command lines MUST begin with a tab !! You can list the dependency/command line pairs in any order you wish: make is smart enough to figure out the hierarchical relationships.

Make supports parameterless macros just like those that are available with the C preprocessor. You define a macro name (convention dictates that you employ only upper case letters) using an equals sign such as is shown below:

  OBJS = Main.o SecondFile.o
  HDRS = SecondFile.h

To continue a macro definition to a new line, end the prior line with a backslash (\). You retrieve the macro's value using a dollar sign and parentheses around the macro name, as seen in the example below:

  Factorial12.exe: $(OBJS) $(HDRS)
    g++ -o Factorial12.exe $(OBJS)

No error is generated if you request an undefined macro: instead a null string is substituted. For example, you might see in a makefile a command line like the following:

    g++ -c $(DEBUG) -o Main.o Main.cpp

but not be able to find any definition for the DEBUG macro in the makefile. This means that the makefile will build what's called a release build by default and if you want a debug build then it is up to you to define the DEBUG macro via a switch on the make command line such as:

  make DEBUG="-g"

All of the makefiles for the example programs that accompany my CPPIDE application support both Debug and Release builds.

The bare command:

  make

with no argument will make the first target listed in the makefile (more precisely, the first target whose name does not begin with a period). You could also specify any target as a switch on the make command line. However the targets which you will typically see used as arguments to the make command are phony targets used for various administration activities. For example, I always include a "clean" target in my makefiles so you can type:

  make clean

to remove all of the object (.O) and executable (.EXE) files that were created by compiling the source. This make clean command returns the folder to the condition it was in when you first installed CPPIDE.

As with any programming language, there are syntax rules for makefiles:

  • Command lines MUST begin with a tab (or else must follow a semicolon on the same line as the target specification).
  • Dependency lines (and macro definitions) CANNOT begin with a tab.
  • A hash mark (#) anywhere on a line starts a comment that continues for the rest of the line.
  • A command line is normally printed before it is executed. However if the command is preceded by an at sign (@) it is not echoed when executed. Most typically you will see this used with the echo command which already generates output to stdout.
  • A hyphen (-) before a command (but after the tab) instructs make to continue on with the rest of the makefile even if that command generates an error (normally make would bail on the first error).
  • The "include" command can be used to incorporate other makefiles into the current makefile.
  • A backslash (\) at the end of the line causes the command to continue on to the next line. This is important because, although make allows shell commands to be included in a makefile, make invokes a unique shell for each shell command. A shell command like cd is pointless if the succeeding command is not executed in the same shell.
  • File names mentioned in a makefile CANNOT employ the 4 characters = : @ or $
  • The * and ? characters are wildcards which may be used in the conventional manner for filename completion.
  • The value of a macro is requested by the notation: $(<macro_name>).

Some of the special symbols that you may see in a makefile are:

  • $< which means the component file needed to make the current target (this notation can only be employed in a pattern rule),
  • $* which means the current target's filename after the suffix has been stripped off,
  • $@ which means the current target's full filename (the name of the file being made), this notation can only be used on a command line,
  • $$@ which means the current target (this notation can only be used on a dependency line),
  • $? which means the list of components that are younger than the current target,
  • $$ which means a single dollar sign (similar to %% in a printf( ) statement),
  • $** which means the list of dependents (prerequisites)

With this introduction, I invite you to inspect the makefile used for the Factorial12 project. Because Factorial12.exe depends upon Main.o (which depends upon Main.cpp), SecondFile.o (which depends on SecondFile.cpp), and SecondFile.h; you might expect to be able to find rules in Factorial12.mak similar to the following:

Factorial12.exe: Main.o  SecondFile.o SecondFile.h
    g++ -o Factorial12.exe  Main.o  SecondFile.o

Main.o : Main.cpp
    g++ -c -o Main.o Main.cpp

SecondFile.o : SecondFile.cpp SecondFile.h
    g++ -c -o SecondFile.o SecondFile.cpp

These rules would work, but they can be simplified by using macros as shown below:

PROGRAM = Factorial12.exe

CC      = g++
CFLAGS  =

OBJS    = Main.o SecondFile.o
HDRS    = SecondFile.h

LIBDIRS =
INCDIRS =
LIBS    =

$(PROGRAM): $(OBJS)
    $(CC) -o $(PROGRAM) $(OBJS) $(LIBDIRS) $(LIBS)

%.o : %.cpp $(HDRS)
    $(CC) $(CFLAGS) -c $(INCDIRS) -o $@ $<

The final rule seen above is an example of a pattern rule. It describes the methodology for converting any .CPP file into the corresponding .O file. The percent sign symbols are basically wild cards and hence the text "%.o" would match both Main.o and SecondFile.o. This one pattern rule would cause Main.o to be built from Main.cpp and it would also cause SecondFile.o to be built from SecondFile.cpp.

By using pattern rules I can avoid hard-coding filenames in the rules. This allows the rules section of the makefile to remain unchanged as I port the makefile to a new project: only the definitions of the OBJS and HDRS macros needs to change.

But what you actually find in the Factorial12.mak makefile is slightly different, due to the need to support both Debug builds and Release builds:

ifdef DEBUG
ODIR   = Debug
CFLAGS = -g -D_DEBUG
else
ODIR   = Release
CFLAGS =
endif

SRCDIR = .

OBJS = $(ODIR)/Main.o $(ODIR)/SecondFile.o
HDRS = $(SRCDIR)/SecondFile.h

$(ODIR)/$(PROGRAM): $(OBJS)
    $(CC) -o $(ODIR)/$(PROGRAM) $(OBJS) $(LIBDIRS) $(LIBS)

$(ODIR)/%.o : %.cpp $(HDRS)
    $(CC) $(CFLAGS) -c $(INCDIRS) -o $@ $<

Hopefully with this introduction you can decipher the makefiles I provide with CPPIDE. Remember that my "New Project" utility prevents you from needing to touch a makefile for all but the most complicated situations.


( 59 )

Hungarian Prefixes

Hungarian prefixes on variable names help you remember the data type of that variable. The list of standard Microsoft prefixes is shown below:

Prefix | Data Type
a | array (gets combined with another prefix)
b | BOOL (Boolean)
by | BYTE (8 bit unsigned integer)
ch | char (one character or an 8 bit signed integer)
dw | DWORD (32 bit unsigned integer)
fn | function pointer (not used with function names)
i | int (32 bit signed integer)
j | BYTE (8 bit unsigned integer)
l | long int (32 bit signed integer)
p | pointer (address)
s | short int (16 bit signed integer)
sz | string (NULL terminated array of chars)
ui | UINT (32 bit unsigned integer)
w | WORD (16 bit unsigned integer)

Because Hungarian prefixes are so useful, they have grown beyond their original purpose of denoting the data type, and are now also used to denote the purpose of a variable.

Prefix | Purpose
c | count (e.g., cb indicates count of bytes)
cx,cy | x axis width and y axis width
g_ | global variable
f | flag (usually a BOOL)
h | HANDLE
m_ | class data member
n | number (as in "number of")
s_ | static variable
x,y | x axis and y axis coordinates


( 60 )

Math Functions

Sophisticated mathematical operations such as trigonometric functions and logarithms are not built into the C or C++ languages. But that only means that they must be supplied via an add-on library. Like all C and C++ compilers, the Gnu C/C++ compiler provides a complete collection of math functions. Typically these functions involve the double data type, which means that floating point numbers are represented using 64 bits. This allows for about 15 decimal places of precision in all calculations.

A partial list of these functions is shown below. The only thing you need to do to be able to employ any of the following math functions in your program is to #include the math.h header file via the following statement:

#include <math.h>

int abs( int x );

returns the absolute value of x

double acos( double x );

returns a double value representing the trigonometric arccosine of x in the range 0 to pi radians

double asin( double x );

returns a double value representing the trigonometric arcsine of x in the range -pi/2 to pi/2 radians

double atan( double x );

returns a double value representing the trigonometric arctangent of x in the range -pi/2 to pi/2 radians

double atan2( double x, double y );

returns a double value representing the trigonometric arctangent of the ratio y/x in the range -pi to pi radians. This function can figure out the correct quadrant thanks to being able to see both the numerator and denominator.

double ceil( double x );

returns a double value representing the smallest integer that is greater than or equal to x

double cos( double x );

returns a double value representing the trigonometric cosine of x where x is expressed in radians

double exp( double x );

returns a double value representing the number e (2.718) raised to the x power

double fabs( double x );

returns the absolute value of x

double floor( double x );

returns a double value representing the largest integer that is less than or equal to x

double log( double x );

returns the natural logarithm of x

double log10( double x );

returns the common logarithm of x

double modf( double x, double *intptr );

splits a floating-point value into its fractional and integer parts. Note the fact that one of the "passed" parameters is a pointer. A function can only have 1 return value and this particular function needs to return two different values. Hence one of the "passed" parameters is really a return parameter. This can be accomplished with the use of a pointer. Typical code that employs this function would look like:
    double    dNumb = 3.7;
    double    dIntPart, dFracPart;
    dFracPart = modf( dNumb, &dIntPart );
    printf( "The fractional portion of %lf is %lf\n"
            "while the integer portion is %lf\n",
            dNumb, dFracPart, dIntPart );
Another thing to note is that the data type of the integer part is a double. If you really want an int then you need to do something like:
    double    dNumb = 3.7;
    double    dIntPart, dFracPart;
    dFracPart = modf( dNumb, &dIntPart );
    int       iIntPart = (int)dIntPart;     // use of a cast avoids a compiler warning
    printf( "The fractional portion of %lf is %lf\n"
            "while the integer portion is %d\n",
            dNumb, dFracPart, iIntPart );

double pow( double x, double y );

returns the number x raised to the y power

int rand( void );

returns a pseudo-random integer in the range 0 to RAND_MAX where RAND_MAX is a macro that you can find defined as shown below in the stdlib.h header file:
    #define	RAND_MAX	0x7FFF

double sin( double x );

returns a double value representing the trigonometric sine of x where x is expressed in radians

double sqrt( double x );

returns a double value representing the square root of x

double tan( double x );

returns a double value representing the trigonometric tangent of x where x is expressed in radians


( 61 )

Iostreams

In many of the C++ programming books you can find a version of the Hello World program that looks like:

#include <iostream>
using namespace std;

int main()
{
    cout << "Hello World!" << endl;
}

You will know the book is older than the C++ standard, which was finally ratified in 1998 after a decade of work, if the Hello World program looks like:

#include <iostream.h>

int main()
{
    cout << "Hello World!" << endl;
}

The new notation that you see in these programs is cout which represents the output stream object associated with the video display. The word "stream" is just supposed to connote an endless procession of characters, with no obvious demarcation points among them. The console-mode programs that I have shown you have generated their output stream via the printf( ) function. The back-to-back less than symbols (<<) are known as the output operator. And endl is just the C++ way of representing the newline character '\n'.

There is also a cin keyword which represents the input stream object associated with the keyboard. cin is employed with the input operator >>. The console-mode programs that I have shown you have read from the input stream via the scanf( ) or fgets( ) functions.

I have not employed this new notation in my console-mode programs because I favor the older methods that originated in the C language. These are the methods such as the printf( ) and scanf( ) function that are declared in the stdio.h (standard input/output) header file.

Some people argue that it's not really a C++ program if it employs the functions declared in the stdio.h header file. But that's balderdash. You can prove this for yourself. If you type in the second version of the Hello World program shown above (the one that #includes <iostream.h>) you will observe that the compiler will tell you that you are employing deprecated features of the language. However when you compile my Hello World program that employs printf( ) rather than cout you do not get any such warning about using deprecated features. The stdio features of C are all preserved in C++.

And many people favor them over the iostream technique that was introduced in C++. Throughout the history of the computer industry, each new language would typically incorporate all the best features of the prior languages. But the iostream technique of C++ did not get selected for inclusion in Java and it did not get selected for inclusion in C#. You can see the problem for yourself, just by comparing the two following code snippets which accomplish the exact same thing. Would you rather type:

    cout << 'a' << ' ' << 1234 << ' ' << setiosflags(ios::showpoint)
    << setiosflags(ios::fixed) << 1234.5678 << ' ' <<
    setiosflags(ios::hex) << &i << ' ' << "abcd" << '\n';

or:

    printf("%c %d %f %p %s\n", 'a', 1234, 1234.5678, &i, "abcd");

The argument made to justify the complexity of the C++ iostream class is that it improves type safety, meaning that the compiler is better able to stop you from writing an incorrect program. This is true, but when you write code that incorrectly employs the simpler stdio functions you usually discover the problem the first time you run your program. These are rarely latent defects that escape your notice and get shipped to customers.

Explanation of all the intricacies of the C++ iostream formatting functions can perhaps still be found on the Internet at the following locations:

Horstmann
Eckel's Thinking In C++

This last hyperlink takes you to an on-line copy of Bruce Eckel's book Thinking In C++, which is one of the best selling C++ programming books. Mr. Eckel has allowed me to include the electronic version of this book (including the source code for the example programs it demonstrates) on my Computer Science Lab CD-ROM. Therefore you can also read about the iostream technique from that location. Just go to the following file on the Computer Science Lab CD-ROM (note that I am assuming that drive letter D: corresponds to your CD-ROM drive):

D:\Eckel Thinking In C++\Volume 1 Book\Chapter05.html

Full instructions on how you can use CPPIDE to compile all 479 of the example programs demonstrated in Bruce Eckel's C++ books can be found in the following file on my Computer Science Lab CD-ROM:

D:\Eckel Thinking In C++\Eckel.htm

and also at the following world-wide-web location:

http://www.computersciencelab.com/Eckel.htm

Another location where you can read about iostreams is the Deitel and Deitel textbook C++ How To Program. I can't provide you with this book, but you can download the source code for the 275 example programs it demonstrates from the author's web site. Full instructions on how you can use CPPIDE to compile these example programs can be found in the following file on my Computer Science Lab CD-ROM:

D:\Deitel and Deitel\Deitel.htm

and also at the following world-wide-web location:

http://www.computersciencelab.com/Deitel.htm


Java

( 62 )

The Java Language

The Java language is one of the newest programming languages and has generated a lot of hype since its introduction. It has had a funny history. It was originally created by the Sun Microsystems company for use in television set-top video decoder boxes. But that market never took off so Sun repackaged the technology as an Internet language, using the slogan, "Write once, run anywhere". This slogan resonated strongly with programmers who had for a long time wished they could write a single program and still have it run on Intel/Microsoft ("Wintel") computers, Apple computers, UNIX/LINUX computers, IBM mainframes, etc.

The problem that limited the portability of programs was the fact that they were compiled to a particular machine language. The only programs that can run on an Intel x86 microprocessor are programs in x86 machine language. The only programs that can run on a Motorola PowerPC microprocessor (used in Apple computers) are programs in PowerPC machine language.

Sun's solution to this problem was the invention of a new programming language whose machine language (they called it byte code) was not compatible with ANY existing microprocessor! Can you say, "counterintuitive"? The idea was that Sun would prepare an x86 program which would run on x86 microprocessors and interpret the Java byte code at run-time. Sun would also write a PowerPC program which would run on PowerPC microprocessors and would interpret the Java byte code on those machines. So the byte code was portable to any machine for which you had the necessary interpreter. These interpreters would study the byte code at run-time and decide what machine code operations could accomplish the necessary activity on that microprocessor.

There are two big flaws in this approach. First, by moving the activity of the compiler from compile-time to run-time you drastically slow down the program. Before each of your statements can be executed, a different program (the interpreter) must study your intent and decide upon a course of action. This guarantees a 10X performance penalty. That is, Java programs run at least 10 times slower than programs already compiled to machine language. This incredible performance cost is why there are no video games written in Java.

The second problem with Sun's approach is that there really wasn't a market need for this solution in the first place. By the time Sun started marketing Java, the Intel/Microsoft camp had already kicked Apple's butt and relegated them to a 5 % market share (this would be an impressive accomplishment save for the facts that Apple helped demolish their own market share and Microsoft used every unethical business ploy in the dirty trick book and then wrote a few new chapters to boot). Why accept a huge run-time penalty when all your customers have the same microprocessor (an x86) anyway? Even most UNIX/LINUX computers employ the x86 microprocessor!

Even Sun has had to admit that their "Write once, run anywhere" slogan has crashed and burned. Today, Sun segregates Java programs into those that require the Java 2 Micro Edition (J2ME) platform, those that require the Java 2 Standard Edition (J2SE) platform, and those that require the Java 2 Enterprise Edition (J2EE) platform. This means that you and your buddy could both have favorite Java programs but you can't trade them unless you both employ the same Java platform. This is the exact portability problem that was supposed to go away once we accepted the tremendous run-time performance penalty imposed by the interpreter.

The idea of using an interpreter was not original to Sun. There was a widespread language called UCSD Pascal promoted in the 1970's by the University of California at San Diego that employed the same methodology, except what Java calls byte code, UCSD Pascal called P code.

As far as a programming language, Java is a simplification of C++. It employs the same syntax as C and C++ but while C requires that all code reside within functions, Java additionally requires all functions to reside within classes. This is the biggest change to what you have been learning in this curriculum.

As you have learned, all Windows console-mode programs must begin in a function called main( ). This function cannot be a member function of some class. And all Windows GUI programs must begin in a function called WinMain( ) which also can't be a class member function. These restrictions exist because MS-DOS and Windows were invented before C++ and object-oriented programming became dominant. The designers of the C++ language knew they had to co-exist in a C world, so they continued to allow bare (non-member) functions.

But the Java designers wanted to rigidly enforce an object-oriented programming style so they insisted that all code reside in member functions. How does a Java program get started? You must supply a main( ) function in one of the classes you write.

The Java designers claim that they eliminated a whole class of program bugs by eliminating pointers. True, you don't find the address of (&) operator or the dereference (*) operator in the Java language, but actually Java has made pointers universal rather than eliminating them. In the C and C++ languages, you have the choice of using either stack allocation (which does not involve the new keyword or a pointer) or heap allocation (which employs the new keyword and requires a pointer). Java provides only a single way to allocate an object (an instance of a class): you must employ heap allocation via the new keyword. The language syntax obscures the fact that what you are returned by new is a pointer, but that's what it is.

The C++ language also offers a bit of syntactic sugar that allows you to make pointers look like non-pointer variables. This is done using references, which I will not teach you because I fundamentally disagree with the idea of hiding the true nature of things. My position on references is consistent with my preference for Hungarian notation, which is a notational device that constantly reminds me of the true nature of all my variables.


( 63 )
Previous Table Of Contents

About CPPIDE

CPPIDE is a Windows program that was written in C++ using Microsoft's WIN32 API. It serves as a graphical user interface (GUI) for the Gnu project's C++ compiler. I was lucky to find an open source C++ compiler because writing a compiler for a high-level language is no job for an individual. Thankfully, there are many talented and philanthropic individuals producing what is known as open source software. This term means that the software is made available to all who wish to employ it, for any purpose whatsoever, including modifying it to add new capabilities.

The two most famous examples of open source software are the Gnu utilities produced by Richard Stallman's Free Software Foundation (home page: www.gnu.org) and the LINUX operating system produced by a Finnish computer science student named Linus Torvalds. Perhaps the most impressive of the Gnu utility programs is the g++ compiler for the C++ language. This compiler is normally intended for use on UNIX/LINUX computers, but thanks to the freedom of open source software, it has been ported to Windows computers as well. This work was accomplished by the MinGW project ("Minimalist Gnu For Windows") and their home page is www.mingw.org.

The CPPIDE program also employs the Scintilla Text Editor (SciTE) which is another example of open-source software. The Scintilla text editor has features which are especially useful for editing source code. Examples of these features are the color coding of the C++ language syntax and the left-hand columns that can show breakpoint symbols or line numbers. The home page for the Scintilla project is: www.scintilla.org.

Both the Gnu project and the Scintilla project want everyone who employs their source code to prominently inform their end-users that this code is copyrighted and comes with absolutely no warranty.

My contribution was to add a modern graphical user interface to these disparate tools and thereby create an integrated development environment for the C++ language that looks like any other modern Windows program. This gets you working in a Windows environment, but doesn't help you use the C++ language to build Windows programs of your own creation. So I also contribute a C++ class library that insulates you from daunting complexity of Windows programming (the standard reference book on Windows programming -- Charles Petzold's Programming Windows Fifth Edition -- runs to 1450 pages!). This complete package gives you the power to create, edit, compile, run, and debug true Windows programs of your own design, written in the powerful C++ language. All of the Computer Science Lab programs (RPN calculator, 8051 simulator, GraphIt, and CPPIDE) are based upon this same class library.

I also provide 80,000 words of on-line Help documentation that teaches you the basics of the C++ language by walking you through a large collection of fully explained sample programs (this is the document you are reading now).

CPPIDE is compatible with Windows 95, Windows 98, Windows ME, Windows NT 4, Windows 2000, and Windows XP. CPPIDE should run acceptably on any PC purchased within about the last 6 years. Newer computers will of course run faster than older computers but this program is perfectly useable even on an early Celeron or Pentium I computer. The only example program that might require a modern computer is the Peak Detector program since this program must keep up with a .WAV file being played.