If you're building complex Windows applications using the ObjectWindows Library (OWL), or if you're building large, multi-file DOS applications, you've probably used precompiled headers. Using precompiled headers saves compiler time because the compiler can simply store the binary image of a set of header-file symbols to a special precompiled header file (CSM) and then reload that file when it can, instead of having to recompile the same information.
Unfortunately, precompiled headers have a dark side: They occupy a great deal of disk space. For a simple DOS or Windows application, the CSM file probably won't be too large. However, on large projects with several header files and source modulesthe type of project that benefits from precompiled headersthis file may take up 2 to 5 MB of disk space.
If you create and maintain only one or two applications, the disk space you're losing to CSM files may seem trivial. But if you're building and maintaining several applications, you'll create a separate CSM file for each projectand the wasted disk space will quickly add up. (By default, the Integrated Development Environment, or IDE, creates a CSM for each new project in the project's main directory and then assigns to that file the same name as the current project.)
Instead, you can create one or two precompiled header files and
share those files among your different projects. In fact, that's
how Borland configured the example projects that ship with Borland
C++ version 4.0. In this article, we'll show how you can
create common CSM files for your projects.
When you enable the Precompiled Headers option and begin building
a project, the compiler gradually builds a set of symbols for
the information that appears in the header files. Every time the
compiler finishes processing the header files for a given source
file, it stores the symbols for that source file in a distinct
section of the CSM file. Along with the symbols, the compiler
stores the following compiler environment settings:
This information, along with the symbols themselves, comprises a symbol set.
As the compiler scans subsequent source files, it first checks to see if the compiler environment settings match one of the symbol sets in the current CSM file. If the settings match, the compiler will reload the symbols from that symbol set in the current CSM file.
If the settings don't match, the compiler will repeat this process for any other symbol sets in the current CSM file. If the compiler reaches the end of the CSM file without finding a symbol set whose compiler environment matches, it will process the header files normally and then store the information in a new symbol set at the end of the CSM file, as shown in Figure A.
Figure A - The compiler stores source files' symbols in symbol sets within the project's CSM file.
Fortunately, there are some space-saving exceptions to this scenario.
For example, if the list of header files stays the same but the
compiler settings change, the compiler will replace the previous
symbol set with the one that uses the new compiler settings. Similarly,
if one source file uses a set of header files that's the
same as the set another source file uses, except that it contains
some additional header files, the compiler will merge the symbols
from the additional files with the previous symbol set.
Since many of your projects will probably use the same or a very similar set of header files, you can force those projects to share a single CSM file. If multiple projects use the same compiler environment settings, you'll find that when you create new projects that use those settings, the compiler won't have to process the header files at all.
To force the compiler to use a specific CSM file for a given project, you can do one of three things: set the -H command-line option for BCC, add the #pragma hdrfile directive to the first source file in your project, or specify the file on the Precompiled Headers page of the Project Options dialog box.
For the OWL example projects that Borland includes with version
4.5 of the compiler, they've configured each project to
use a precompiled header file named OWLWI.CSM in the BC45\LIB
directory. If you've built any of the example projects,
you can simply use this file as your common precompiled header
file.
OWL projects take advantage of a powerful set of classes that simplify many aspects of Windows programming. However, these classes require a large number of header files.
Interestingly, most OWL projects will use the same set of OWL header filesthe ones that declare the application, window, and graphics classes. Rather than specifying the entire list of OWL header files each time you create an OWL project, you can instead use the OWLPCH.H file.
The OWLPCH.H file contains #include directives for all
the standard OWL header files. Since the header files you create
probably aren't as big as the OWL header files, you probably
won't see a significant speed penalty if you tell the compiler
to recompile only your header files and then reload the OWL header-file
data from the CSM file. (We'll discuss this in more detail
shortly.)
For those projects that aren't OWL-based, you may want to use a precompiled header file that's separate from the one you use for OWL projects. By doing so, you'll be able to keep the symbols for these projects, which are generally smaller, out of the precompiled header file that contains all the OWL project symbols. Also, by keeping two common precompiled header files, you'll reduce the effects of an accidental Build All or Build Node command (see the section titled "Use Build All or Build Node with Caution," at the end of this article).
If you don't create a project file for a simple application, the IDE uses the BCWDEF.IDE default project file. This file specifies a precompiled header file named, not surprisingly, BCWDEF.CSM.
Each time you create an application without an explicit project,
the IDE will use the BCWDEF.CSM file to store any precompiled
header information. Since many non-OWL projects will probably
have header file combinations similar to those of these "no
project file" projects, you may want to use BCWDEF.CSM as
your common precompiled header file for those projects as well.
Unless you delete a project's CSM file, the compiler will simply add new symbol sets to the file as they appear in different source files. As we mentioned earlier, if a symbol set repeats in two source files, the compiler will simply read the symbols for the second source file from the CSM file.
Obviously, if you use a new sequence of header files or environment
settings for each project, and they all use a common CSM file,
that CSM file will keep growing until you stop creating projects
or until you run out of disk space. This leads some programmers
to believe that the precompiled headers option is wasteful and
inefficient. However, if you use a consistent sequence of header
files and environment options, it really isn't.
One way to ensure that the compiler doesn't encounter unique symbol sets is to make sure you don't add volatile header files to the CSM file. In other words, if you place only information from unchanging header files into the CSM file, the compiler will have a better opportunity to reuse that information. In contrast, if the CSM file contains header files with class declarations, and the class declarations change, the compiler will interpret those changed header files as a new symbol set, and it will append those symbols to the existing CSM file, causing it to grow.
As we mentioned earlier, your header files probably won't contain nearly as many symbols as the OWL library header files or other library header files do. If you force the compiler to process your header files each time you compile, you won't increase the build time significantly. The secret, then, is to be able to select the files the compiler will add to the CSM file. By design, the compiler will begin collecting symbols for the CSM file as it begins processing each source file.
However, you can tell the compiler to stop collecting this information by adding the #pragma hdrstop directive to your source files. To learn how you can tell the compiler when it should stop adding symbols to the CSM file, see Limiting the precompiled header file.
If you place the header file #include directives for
a library (for example, OWLPCH.H) at the beginning of your source
file, then insert the #pragma hdrstop directive, and
finally add the #include directives for the application's
specific header files, you'll keep your header file information
out of the CSM file. In addition, you'll be processing
the library header files (which probably take the most time to
scan) as infrequently as possible. Now, let's create a
simple example program that uses a common CSM file.
To begin, launch the Borland C++ 4.x IDE. When the IDE's main window appears, choose New from the File menu, and enter the code from Listing A. When you finish entering this code, choose Save from the File menu, enter \pch_test.cpp in the File Name entry field of the Save File As dialog box, and click OK.
Listing A: PCH_TEST.CPP
#include <iostream.h> #include <stdlib.h> int main() { cout << "This is only a test" << endl; return 0; }
Next, right-click the editing window for PCH_TEST.H, and choose TargetExpert from the pop-up menu. In the TargetExpert dialog box, choose EasyWin from the Target Type list box and click OK.
Now, let's set the CSM filename for this default project. (Since you haven't explicitly created a project file, this example uses the default project file.) To do so, choose Project... from the Options menu, select the Precompiled Headers subtopic from the Compiler section of the Topics list box, and enter C:\COMMON.CSM in the Precompiled Header Name entry field, as shown in Figure B.
Figure B - You'll use the Project Options dialog box to set the precompiled header filename.
Since the IOSTREAM.H file directly and indirectly (by referencing other header files) pulls in more information than most of the other common header files, we'll tell the compiler to stop adding data to the CSM file after it processes that file. To do so, enter IOSTREAM.H in the Stop Precompiling After Header File entry field. Click OK to save these changes to the default project.
To build the PCH_TEST application, choose Build All from the Project menu. As the compiler builds the application, notice that it processes both of the header files.
When the build process is complete, click OK in the Compile Status dialog box, click on the editing window for the PCH_TEST.CPP file, and choose Close from the File menu. Choose New from the File menu to create a new editing window. In this window, enter the code from Listing A again. When you finish, choose Save from the File menu, enter \pch_tst2.cpp in the File Name entry field of the Save File As dialog box, and click OK.
Now, build this new application by choosing Make All from the Project menu. This time, you'll notice that the compiler doesn't process the IOSTREAM.H file, but it does process the STDLIB.H file as it did in the previous build.
(By the way, if you're using a Pentium or fast 80486 system,
this small project will compile so quickly that it may be difficult
to notice whether or not it processes the IOSTREAM.H file. If
this is the case for your system, enter STDLIB.H in the
Stop Precompiling After Header File entry field of the Project
Options dialog box. This will force the compiler to reload all
the header file information from the CSM file, which will make
it easier for you to notice when it's not processing header
files.)
If, when you build the release version of an application, you
change the debug and diagnostic compiler settings (as you should),
the compiler will rewrite the symbol set to reflect those new
settings. To keep the compiler from doing so, change the CSM file
setting to a new file in the project directory. Since the compiler
setting changes would force the compiler to rebuild the CSM file
anyway, you won't lose any time building a new one from
scratch.
If you choose Build All from the Project menu or Build Node from the Project window's pop-up menu, the IDE will delete the current precompiled header file before rebuilding the project. If you're using a common precompiled header file and you don't mind rebuilding all the symbol images that exist in that file, you can use the Build All or Build Node command.
However, if you simply want to force a rebuild on each node, you
can just use the TOUCH.EXE program to change the time/date stamp
on the source files for the project. To do so, enter
touch *.cpp
in the main directory of the project. Then, you can use the Make
All or Make Node commands to force the IDE to recompile and relink
each of the project nodes.
Precompiled headers can save you a great deal of time by allowing
the compiler to reload information that it's already processed.
By sharing CSM files (the files that contain the precompiled symbols)
among similar projects, you'll reduce the amount of space
that CSM files occupy on your system, and you'll save time
compiling the next project of that type.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.