20.9 Building blocks of a complete program


Now that we've talked about the mechanics of building a program, let's say a little more about the kinds of component files that go into making a Windows program.

First come the files which are involved in building the code of the main Windows executable.

  • Project file: *.sln and *.vcproj [ *.dsw and *.dsp in Version 6]

  • Source-code files: *.cpp and *.c

  • Header files: *.h

  • Object code files: *.obj

  • Static library files: *.lib

  • Executable file: *.exe , and dynamic link library files: *.dll

Next come the files which are used for specifying the graphic user interface of the program.

  • Resource files: *.rc , and compiled resource files: *.res

Finally we want to mention the files that are involved in creating the standard Windows help file.

  • Help project files, source text files, and help files

Let's say a few words about each of these kinds of files.

Project files: *.sln and *.vcproj [ *.dsw and *.dsp (Version 6.0)]

As we mentioned in Section 3.3, in order to build an executable file from a collection of source code and resource files, we need a project file to orchestrate how the files are to be combined. A Visual Studio project is described by two levels of files, a primary higher-level project file originally called a workspace file, and one or more secondary lower-level files simply called project files. Generically any or all of these kinds of files may occasionally be termed 'project files.'

Microsoft changed the standard file extensions for their project and workspace files when they replaced the Visual Studio, Version 6.0, by Visual Studio.NET, also known as Version 7.0. The older *.dsp and *.dsw are now *.vcproj and *.sln (see the table summarizing this in Section 3.3). Also Microsoft has replaced the word 'workspace' with 'solution.' But for the rest of this section, we will simply use 'workspace' file to mean either the older *.dsw file or the newer *.sln file.

Whenever you are building a program which uses more than one file, you need a project file to list the names of the various code modules that you will use. In addition to keeping a list of the component modules, a project file saves a rather large number of compiler and linker settings. These settings control such decisions as whether or not to include debugging information, whether to optimize the code for smaller size or for faster speed, how many kinds of warnings to display, and so on. The Microsoft project and solution [or workspace] files are in fact simple text files, and you can open them in your word-processor to see what's in there. Occasionally you can clear up a persistent problem more easily by a direct edit like this than by trying to get at the project files through the Visual Studio interface.

Source-code files: *.cpp and *.c

Source code files are ASCII text files which your C++ code compiler processes into binary object code.

Beginning programmers tend to want to put all of their code into one large module. It is in fact better to use many small modules. Generally you want to put all the functions of one type into one module together; this makes things easier to find. Another gain of using multiple modules is that, when you change something in your program, you only need to recompile the single, small module that you changed. Another reason not to use only one module is that, sooner or later, this module will get so big that the compiler will refuse to handle it.

The C++ language contains all of the C language. This means that any C code can also be regarded as C++ code. The convention is to use *.cpp (for 'C Plus Plus') as the file extension of C++ code modules. If you have a *.c module of C code, you can simply change the file's extension to *.cpp and then your file will be compiled as C++ code. In this book, we are only going to use C++ files, so from now on we'll assume that all of our source code files are *.cpp .

A peculiarity of MFC is that when you are doing an MFC build, every single *.cpp file must have as its very first line the line

 #include "stdafx.h" 

If you leave out this line, you will get a confusing error message.

Header files: *.h

A header file is a file which is included into another file by lines like this.

 #include "types.h"  #include <windows.h> 

If the file name after #include is in quotes, the compiler will look for the file in the directory where the project file lives; and if the include file is in pointy brackets, the compiler will look for the file in whatever directories the project file has set to be places to look for include files “ typically the standard location for include files is the INCLUDE subdirectory of the directory where the compiler lives, but sometimes you will want to add other directories to the standard search path .

The #include directive is a preprocessor directive; it tells the compiler to do something before running. What the #include directive tells the compiler to do is to replace the #include "whatever.h" line with a full, exact copy of the Whatever.h file, just as if you had used your text editor to block copy the whole Whatever.h file and paste it in.

Note that the compiler is not sensitive to the case of the letters used in the names of the include files.

In general any line of C++ that starts with # is a preprocessor directive; we have more information about these in Chapter 22: Topics in C++.

In C++, we put the definitions of our classes in header files with extension *.h , and we put the code implementing the classes' methods in *.cpp files. Some programmers like to use the file extension *.hpp for these files, but this practice is discouraged by the Microsoft compiler and is not very common.

Another use for header files is to #define some macros or compiler switches in a header file, and to include this in many different files. We also sometimes typedef type variables in a header. And headers can also be used to #define labels for things such as buttons in dialog boxes.

Many headers include header files themselves . For each header file, we list the names of the header files which are included by a line like #include "critter.h" as well as the names of the classes that are declared by a forward class declaration like class cGame. The best practice is to only include a header in a header when we really have to, for instance if the header mentions an instance or method of a class that's defined in another header. If the header only mentions a pointer to a class instance, then just a forward declaration will do.

There are two problems you can get when we #include header files: the circular include and the double include.

The circular include happens like this. If A includes B and B includes A, then A includes A, so the compiler will go into an endless loop when it tries to compile A. Let's go over this again. Suppose you have a circular.h file whose first line is #include "circular.h" . When the preprocessor tries to handle this, it will get into an endless regress. It must replace the first line of the file with a copy of the file in which it must replace the first line by a copy in which, etc. Usually, of course, a circular include isn't quite this obvious. You might for instance have a chicken.h file that starts out with the line #include "egg.h" and an egg.h file that starts out with the line #include "chicken.h" . But the same infinite regress will arise. When you have a circular include the compiler will give you some odd error messages, but it won't flat-out tell you that a circular include is your problem.

The way to avoid circular includes is to be very stingy about putting include statements. Don't use them unless you really have to. If possible, put your include statements into the *.cpp files rather than into the *.h files. If all you need is a pointer to a class called, say, SomeClass , you can just put the line class SomeClass; into your header file rather than a full #include "someclass.h" . The line class SomeClass; alerts the compiler that you will eventually define a class with this name without specifying yet what it will look like. And you can put the #include "someclass.h" into the *.cpp file where you actually need to use the inner details of SomeClass .

The double include happens if you happen to include the same header file twice. This can cause problems because if, for instance, a class is defined in the included header file, then including the file twice means you have two definitions of the same class, and the compiler won't allow this. To avoid double includes, we use the #define and #ifdef as discussed in Section 22.15.

If any *.cpp file happens to include some header file twice, you'll get a lot of error messages. The compiler will be upset because you are 'redefining' things that you've already defined. To prevent this from happening, you should make a habitual, automatic practice of including two standard lines at the start of each header file, and one standard line at the end. The lines to use in, for instance, ANYHEADER.H , should be as follows .

 //--------------------ANYHEADER.H START------ #ifndef ANYHEADER_H  #define ANYHEADER_H  ...  #endif //ANYHEADER_H  //---------------------ANYHEADER.H END------ 

The idea is that the first time you hit ANYHEADER.H it gets included, and if you try and include it again, ANYHEADER_H is already defined so then the code gets skipped over.

As a matter of interest, you may be wondering how it could happen that you would include the same *.h file twice. It could happen like this: your main module might use two classes, a cWorld class and a cCritter class, and it could be that there is a special cVector class which is used both in the definition of the cCritter class and in the definition of the cWorld class. You would put definitions for these classes in, respectively, a World.h ,a Critter.h , and a Vector.h file. And your Critter.h and your World.h files might each have this line:

 #include "Vector.h" 

When the preprocessor goes over Critter.h or over World.h , it will actually replace that line by a full copy of the Vector.h file.

Meanwhile, your View or Doc module will have these lines:

 #include "World.h"  #include "Critter.h" 

Now, when the preprocessor works on theses two lines it replaces the first #include line by a copy of World.h , and replaces the second #include line by a copy of Critter.h . But, as mentioned above, the World.h file includes a full copy of the Vector.h file, and so does Critter.h . So your main module gets two full copies of Vector.h . And then the compiler complains because it looks as if your cVector class is getting defined twice. This is why the #ifndef trick is needed!

One specific header file which is used by every MFC program is called StdAfx.h . You might do well to open up and look at the StdAfx.h in the Pop Framework; there are some useful comments in there.

Any normal program is going to have lots of different files, and it's important to be including the same version of, say, afxwin.h in all of the different modules. It's a wise practice to have the #include afxwin.h line inside of a StdAfx.h file that everyone includes, so that everyone will have the same switch settings turned on before the #include afxwin.h and other include lines. As we mentioned above, when we use MFC, every single anyfile.cpp in the MFC is actually required to start with #include "stdafx.h" .

A note on precompiled header files

Many of your files include the same header files as other files. Visual Studio speeds up the build process by saving off a 'precompiled header file' with the *.pch extension, writing this to either the Debug or the Release subdirectory. Having the precompiled header file available avoids having to recompile code for each lines of the form, say, #include stdafx.h .

The *.pch file is quite large (on the order of ten megabytes), so it's important not to leave old *.pch files on your hard drive. Be sure and run our clean.bat file to remove it when you're done or use the Build Clean Solution [which is Build Clean in Version 6.0].

If you are running MFC over a network, be sure to copy your code and project files to the local machine's hard drive (perhaps the C:\Temp drive if you're in a lab). Build your program on the local hard drive only. When you are done for the day, close Visual Studio, clean your code, and copy it back up to your network drive.

The reason you want to build on the local drive is that doing an MFC build involves writing that ten megabyte precompiled header file someplace, and you don't want that place to be at the other end of a (possibly slow) network wire.

Object code files: *.obj

Your compiler package has a C ++ Code compiler which converts your text *.cpp files into binary *.obj executable code files. Once all your modules have been compiled to make *.obj files, your linker combines these *.obj files and some *.lib files together to make an executable *.exe file.

There are three alternative ways you can ask your compiler to compile a file. You can Compile , Build or Rebuild All . You'll find these options on the Build menu for both Visual Studio .NET and Visual Studio Version 6.0.

We summarize these options below.

  • Compile current.cpp. Compile only the file currently in the edit window. The Developer Studio helpfully puts the name of this file right into the menu selection; here we write 'current.cpp' to stand for the name.

  • Build current.exe. Compile all *.cpp source code files whose *.obj files are out of date, and link these together into a new executable. Again, the Developer Studio fills in the name of the current target executable.

  • Rebuild All Compile all *.cpp files in the project list, whether or not they are out of date, and link these together into a new executable.

To understand the distinction between the second two selections, you need to understand the idea of a *.obj file being 'out of date.' A compiled *.obj file is said to be out of date if (a) it doesn't exist yet, or (b) you have recently made any changes to the source code files used for this particular compiled file. The source-code files used to make, say, current.obj , will include not only current.cpp , but also any header file which current.cpp accesses by means of a #include . The #include relation is transitive; that is, if current.cpp has #include "critter.h" and Critter.h has #include "point.h" , then both Critter.h and Vector.h are viewed as source-code files used to make current.obj . If you change Vector.h , then current.obj becomes out of date.

How exactly does the compiler package tell if a *.obj is out of date? The compiler's text editor affixes a fresh date and time stamp to a file every time you change it. In addition, the compiler affixes a date and time stamp to each compiled *.obj or *.res file. By comparing source code files like current.cpp , Critter.h , and Vector.h to the compiled object version current.obj , the compiler can see if the source code, or one of its include files, has a date and time later than the date and time of the compiled file of the same name. If this is the case, then the *.obj file is out of date and needs to be recreated by a compile operation.

When you ask for a compile or a build, the C++ compiler will save all open files, and check dates on the header #include files. It expects to find them either (a) in the directory where your current project file lives, or (b) in the INCLUDE subdirectory of the directory where your compiler lives.

A problem that most programmers will run into at least once a year is that somehow one of your source code files will get a bad date on it, a date which is off sometime in the future. Every time you go to build your program, the compiler will look at that file and say, 'This source file has a date stamp later than the date stamp on my most recent *.obj file based on it, therefore the source file must have changes in it, so I have to recompile and make a fresh *.obj .' If the file with the bad date happens to be a header file that's included in a lot of other files, then all of those files will be rebuilt as well. This is mystifying and time-consuming . When you work on a team, there will sometimes be one particular team member whose machine puts bad dates on your source files; this can be because this individual has carelessly let his or her machine's calendar get set to some crazy year date like 2100.

The fix for the bad date problem is, first of all, to get the right date onto your files by opening them, making a trivial change (such as typing a space and then deleting it) and then saving the file. (Or you can do this from the outside if you happen to have access to the Unix touch utility.) Second of all, try and eliminate the source of the bad date problem. Make sure that all your team members have computers set to the correct date, and check the time too, as a bad time can sometimes cause the same kinds of problems.

If your compiler can't find the include files you will get a lot of error messages at the compile stage; usually the first one will say something like 'windows.h not found.' This is might happen, for instance, if you are running your compiler over a network. To fix this problem, you need to explicitly tell your project where to find the INCLUDE subdirectory of the directory where the compiler lives.

In Microsoft Visual Studio, you can set the INCLUDE directory by opening the Tools Options dialog and going to the Directories tab. On the Show directories for... drop-down box, select Include files . Then type in the correct drive and directory. If you're not sure what the correct directory is, minimize the compiler package and use Windows Explorer (File Manager in Windows 3.1) to find out where the compiler package lives.

Static library files: *.lib

Once your C++ compiler has turned your source code modules into *.obj files, these files need to be linked together to make up a full executable *.exe file. The tool that accomplishes this is called the linker , and it is part of your compiler package.

The linker needs more than what it will find in your *.obj files. It also needs the definitions of all the various standard C and Windows functions that you used without writing code for. These function definitions are found in *.lib files that come with your compiler package.

You don't need to specifically list the names of the necessary *.lib files in your project file listing; in fact usually you won't know these names by heart. The linker will go out and look for the *.lib files it needs on your hard drive. Ordinarily it expects to find them in the LIB subdirectory of the directory where your compiler lives.

If your linker can't find the libraries you will get a lot of "Unresolved external..." error messages at the link stage, saying that various functions are not defined. As with the INCLUDE directory, this is most likely to happen if you are running your compiler package over a network, for instance in a student lab. To fix this problem, you need to explicitly tell your project where to find the LIB subdirectory of the directory where the compiler lives.

Note, however, that if you only get one or two "Unresolved external..." message this probably just means that you forgot to put code for some of your methods.

In Microsoft Visual Studio, you can edit the directory paths as described in the table in Appendix C. Once you have the Directories dialog open, you can select Library files . On the Show directories for... drop-down box, select Library files . Then type in the correct drive and directory. As before, if you're not sure what the correct directory is, minimize the compiler package and use Windows Explorer to find out where the compiler package lives.

Executable files: *.exe , and dynamic link library files: *.dll

When you build a Windows project, there are actually two successive *.exe files that get made. First is the 'usual' kind of *.exe that is gotten by linking together the *.obj and *.lib files. Then a 'special' *.exe is made by adding in the resource code, as we describe in the next subsection.

Before doing that, let's say a little about *.dll files (for 'Dynamic Link Library'). Instead of putting shared function code into each *.exe file, you can leave it in a *.dll file on your hard drive and hope that your *.exe can find the *.dll whenever it needs one of the functions whose code is in them. It's common, for instance, for programs to use standard Windows *.dll code for managing the dialog boxes used to open and to save files. When all goes well, this is painless and invisible.

Ordinarily the *.dll files will be found in your Windows directory or in the System or System32 subdirectories of your Windows directory. But sometimes they are not found, and the program dies at start-up with something like a 'file not found' message.

When you use the MFC, there are a number of special function implementations which you can bind into your *.exe as *.lib files. An alternate approach is to let the *.exe dynamically look for the function code on the host computer at run time. In this alternate case, your *.exe will look for the missing code among the computer's various dynamic link libraries, which have the *.dll extension. It is not a good idea to distribute code that looks for MFC libraries in *.dll files. The reason is that many poeple will have the latest MFC *.dll files on their computer and an exe that depends on these dynamic link libraries will be unable to run. For distribution, always bind in the *.lib files rather than expecting your users to have the right *.dll . Testing your release on a range of other machines will point out this issue if you've forgotten about it.

A completely different topic regarding *.dll files is that you can arrange it so that users of your package can create their own *.dll files so as to add on pieces of code to your program, and make your product more programmable. See the SJSU Capow code for an example of this, at http://www.mathcs.sjsu.edu/capow.

Resource description files: *.rc and compiled resource files *.res

Like a C++ file, a *.rc resource description file is an ASCII text file. The resource compiler turns a *.rc file into a binary file that represents such graphical user interface features as: menus , dialog boxes, bitmaps, and program icons.

The compiled version of a *.rc file is called a *.res file. This is analogous to the way in which the C++ compiler turns a *.cpp text file into a binary *.obj file of machine code.

Once you have the *.res file, the resource compiler can then be called again, and this time it performs a linker-like task and attaches the *.res file to the executable *.exe file to make a new *.exe file that includes the desired resources, the dialogs, menus, bitmaps, etc.

The resource compiler is called RC.EXE and it comes with your compiler package. Normally your *.rc file is part of your project file list, and the resource compiler gets called automatically when you build your program using, for instance, the Build Build or Build Rebuild All selections in the Visual Studio.

To design your *.rc file, you use a tool called a Resource Editor which comes with your compiler package. This tool is of the WYSIWYG (for 'What You See Is What You Get') variety “ you create the dialog boxes and so on for your *.rc file by dragging around buttons, checkboxes, edit boxes, scroll-bars and the like.

If you open a *.rc file by double-clicking on it, the compiler package will run the *.rc file through the resource compiler and give you the WYSIWYG view. Once in a while you may want to edit a *.rc file as a text file. To do this, you need to use the File Open dialog to open the *.rc file, and you have to select the Text option in the Open As combo box at the bottom of the box. This is a good idea if you want to check up on exactly how your resources are being defined, or if you want to quickly makes copies of a large number of items.

Your *.rc resource file will define a lot of buttons and menu items. Each of them has an identifier number that is associated with a mnemonic ID that normally starts with 'IDR_'. There are a lot of #define statements that associate the IDs with the integers, and these statements live in a header file which is usually called Resource.h. Rather than looking into the Resource.h file directly, you usually use View Resource Symbols... to work with it. Due to the way MFC treats messages, you usually don't really need to worry too much about the ID symbols in any case. The reason is that you usually use MFC to add a 'message handler' function for each menu item or button without having to actually know the name or value of the associated ID.

Help project file: *.hpj , rich text files *.rtf , and help files *.hlp

As well as the C ++ code compiler and the resource compiler, there is a third kind of compiler, the help compiler . The help compiler is used to build help files, which have the extension *.hlp. The help compiler is invoked through a Windows interface called the Help Workshop. The help compiler needs a help project file to work on. The result of a successful help project compilation is a help file Myprog.hlp .

In the Help Workshop, you can create, select and edit your *.hpj file. The help project file lists the names of the document files you are using to make the target *.hlp file. The help project file can also include special information relating to the hypertext structure of your help file.

The 'source-code' files used by the older help compiler are text files in the special *.rtf ('rich text format') format. A high-end word-processor such as Word Perfect or Microsoft Word for Windows can convert an ordinary formatted *.doc file into a *.rtf file. The newer HTML help files are based on *.html .



Software Engineering and Computer Games
Software Engineering and Computer Games
ISBN: B00406LVDU
EAN: N/A
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net