This document provides information about ODF's use of precompiled headers.
Table of Contents
-------------------------
• Introduction
• Precompiled Header Strategy
• Turning Off Precompiled Headers
• Default Configuration of Precompiled Headers for Parts
Introduction
ODF is a cross-platform framework that can be built with a number of different C++ development environments. We’ve found that creating portable code for C++ is not too difficult if we don’t become too dependent on recent C++ language extensions. By being a little conservative with language features, we’ve found that the biggest remaining problems have to do with differences in build environments. One of the most significant differences is with precompiled headers.
Many different mechanisms exist for precompiled headers. The mechanisms used by popular Macintosh compilers differ in some significant ways from the mechanisms used by popular Windows compilers.
Macintosh compilers in general use a simple approach where one creates a “precompiled header source file” that is run through a precompiler to produce a “precompiled header.” This precompiled header is then transparently included at the beginning of every translation unit.
Windows compilers use a similar mechanism with a subtle twist. Again, a file is used as a precompiled header source file to create a precompiled header. The difference is that generally the Windows compilers require the contents of this file to be replicated at the beginning of every source file compiled using the precompiled header. Another way to look at this is that Macintosh compilers allow precompiled headers to change the semantics of a translation unit, but most Windows compilers do not allow the semantics to change.
Programmers who have not used such a mechanism for precompiled headers might find this a bit draconian, but there are good reasons for such an approach. It’s our experience that, although we prefer to use precompiled headers on a regular basis, we occasionally need to compile without them. We’ve found that without some enforcement of guidelines for including headers, it doesn’t take very long before various .cpp files in the project will no longer build without precompiled headers.
One easy solution is to create an “ODF.h” file and include it at the beginning of every .cpp file in ODF. However, this has the unfortunate side effect of giving sources in lower level subsystems visibility to interfaces from higher level subsystems. It’s our experience that circular dependencies will sometimes creep in unless the build system helps to enforce the layering.
Clearly, there are several factors that contribute to our precompiled header strategy. The requirements we’d like it to meet are:
1) Works with all popular compilers.
2) Code still compiles when precompiled headers are turned off.
3) Doesn’t let us get lazy with layering and dependencies, in particular doesn’t hide circular dependencies across layers.
Precompiled Header Strategy
ODF uses the following scheme.
1) Each .cpp file within each layer of ODF begins with a #include of a layer-specific header file. The header file is the name of the layer with the suffix “.hpp”. For example, source files in the foundation layer begin with the following #include:
#include “FWFound.hpp”
Only comments are allowed to precede this line. No preprocessor statements or C/C++ declarations or statements are allowed to occur before this line. The .hpp file is used as the precompiled header source file for the layer. For development systems that require the precompiled header source file to have a different suffix, another file is created that simply contains the above line (with some possible additions based upon the environment, see below).
2) Each such .hpp source file is structured as two sections. The first section includes any headers external to the layer that the layer depends upon. The second section is conditioned on the preprocessor symbol FW_AGGRESSIVE_PRECOMPILE, and includes all headers for the layer itself. For example, the file FWFrameW.hpp includes all of the headers for the operating system, as well as the headers for the Foundation and OS layers of ODF and all of the OpenDoc headers in its first section. The second section of FWFrameW.hpp includes all of the headers for the Framework layer itself. If the preprocessor symbol FW_AGGRESSIVE_PRECOMPILE is defined then the precompiled header that is produced results in faster compilations but doesn’t prevent circular dependencies. If FW_AGGRESSIVE_PRECOMPILE is not defined then compilations will be slower but circular dependencies are not hidden by the precompiled header.
Note that the symbol FW_AGGRESSIVE_PRECOMPILE, if defined, must be defined externally to the file FWFound.hpp. This is always done in an environment-specific way. For command-line compilers the symbol may be defined on the command line. Some Integrated Development Environments (IDEs) allow the specification of symbols to be defined via some preferences dialog. The remaining IDEs require the use of a separate file to be used as the precompiled header source file. For example, with Metrowerks CodeWarrior we use a file FWFramework.pch++ which looks something like this:
#pragma precompile_target "ODFFramework++"
#define FW_AGGRESSIVE_PRECOMPILE 0 // Change if necessary
#define FW_USE_PRECOMPILED_HEADERS 1 // Change if necessary
#define FW_PRECOMPILE_PRIVATE_HEADERS 0 // Do not change
#include "FWFrameW.hpp"
Turning Off Precompiled Headers
The above description omits the details of how to turn off precompiled headers. For most environments, it is possible to turn off precompiled headers from the command line or a preferences dialog of the IDE. However, since every source file (.cpp) of ODF includes a .hpp file, this by itself is not enough. There must be some mechanism to disable the .hpp file itself. This is done with the symbol FW_USE_PRECOMPILED_HEADERS which is similar to the symbol FW_AGGRESSIVE_PRECOMPILE. FW_USE_PRECOMPILED_HEADERS is defined to be 1 during the precompilation process for all environments. Depending on the environment (e.g. most Windows compilers), it may need to be defined to be 1 during compilation of .cpp files, whereas in other environments (e.g. most Macintosh compilers) it can be defined to be 0. In order to completely turn off precompiled headers, we simply define the symbol to be 0, and turn off the use of the precompiled headers from the compiler command line or IDE preferences.
The ODF team will occasionally build ODF and all ODF examples with precompiled headers turned completely off. This means that ODF developers can ignore the #include of the .hpp file at the beginning of every ODF source file and assume that all definitions needed by the source file are defined by the headers that follow.
Default Configuration of Precompiled Headers for Parts
The sample projects created by PartMaker are configured to use the precompiled header created when the ODFFramework project is built (for example, ODFFramework++ for Metrowerks projects). We currently build this project such that it includes all of the headers from the ODF Foundation and OS layers, and virtually all of the OpenDoc headers. We do not include the headers from the ODF Framework layer (FW_AGGRESSIVE_PRECOMPILE is defined to be 0). Including all of the headers from the framework layer results in a significantly bigger precompiled header and makes it difficult to build ODF parts on development machines with less than 32Mb of memory. If your development machine has plenty of extra RAM you can turn on aggressive precompiling for the framework layer, rebuild the project, and the resulting precompiled header will include all of the framework layer headers.