Shared PCH usage sample in Visual Studio

This post was written by Olga Arkhipova and Xiang Fan

Oftentimes, multiple projects in a Visual Studio solution use the same (or very similar) precompiled headers. As pch files are often big and building them takes a significant amount of time, this leads to the popular question: is it possible for several projects to use the same pch file which would be built just once?

The answer is yes, but it requires a couple of tricks to satisfy cl’s check that command line used for building the pch is the same as the command line used for building a source file using this pch.

Here is a sample solution, which has 3 projects – one (SharedPCH) is building the pch and the static library and the other two (ConsoleApplication 1 and 2) are using it. You can find the sample code source in our VCSamples GitHub repository.

 

When ConsoleApplication projects reference the SharedPCH one, the build will automatically link the SharedPCH’s static lib, but several project properties need to be changed as well.

  1. C/C++ Additional Include Directories (/I) should contain the shared stdafx.h directory
  2. C/C++ Precompiled Header Output File (/Fp) should be set to the shared pch file (produced by SharedPCH project)
  3. If your projects are compiled with /Zi or /ZI (see more info about those switches at the end), the projects which use the shared pch need to copy the .pdb and .idb files produced by the shared pch project to their specific locations so the final pdb files contains pch symbols.

As those properties need to be changed similarly for all projects, I created the SharedPCH.props and CustomBuildStep.props files and imported them to my projects using the Property Manager tool window.

SharedPch.props helps with #1 and #2 and is imported in all projects. CustomBuildStep.props helps with #3 and is imported to consuming pch projects, but not to the producing one. If your projects are using /Z7, you don’t need CustomBuildStep.props.

 

In SharedPch.props, I defined properties for shared pch, pdb and idb files locations:

 

We wanted to have all build outputs under one root folder, separate from the sources, so I redefined the Output and Intermediate directories. This is not necessary for using shared pch since it just makes experimentation easier as you can delete one folder if something goes wrong.

 

Adjusted C/C++ ‘Additional Include Directories’ and ‘Precompiled Header Output File’ properties:

In CustomBuildStep.props I defined Custom Build Step to run before ClCompile target and copy the shared pch .pdb and .idb files if they are newer than the project’s .pdb and .idb files. Note that we are talking about compiler intermediate pdb file here and not the final one produced by the linker.

If all files in the project are using one pch, that’s all we need to do, since when the pch is changed, all other files need to be recompiled as well, so at the end of the build we’ll have full pdb and idb files.

If your project uses more than one pch or contains files that are not using pch at all, you’ll need to change pdb file location (/Fd) for those files, so that it is not overridden by the shared pch pdb.

 

I used the command line property editor to define the commands. Each ‘xcopy’ command should be on its own line:

 

Alternatively, you can put all commands in a script file and just specify it as command line.

 Background information

/Z7, /ZI and /Zi compiler flags

When /Z7 is used, the debug information (mainly type information) is stored in each OBJ file. This includes types from the header files, which means that there is a lot of duplication in case of shared headers and OBJ size can be huge.

When /Zi or /ZI is used, the debug information is stored in a compiler pdb file. In a project, the source files often use the same pdb file (this is controlled by /Fd compiler flag, the default value is $(IntDir)vc$(PlatformToolsetVersion).pdb), so the debug information is shared across them.

/ZI will also generate an IDB file to store information related to incremental compilation to support Edit and Continue.

Compiler PDB vs. linker PDB

As mentioned above, the compiler PDB is generated by /Zi or /ZI to store the debug information. Later, linker will generate a linker PDB by combining the information from the compiler PDB and additional debug information during linking. The linker may also remove unreferenced debug information. The name of the linker PDB is controlled by the /PDB linker flag. The default value is $(OutDir)$(TargetName).pdb.

Give us your feedback!

Your feedback is a critical part of ensuring that we can deliver useful information and feature. For any questions, reach out to us via Twitter at @visualc or via email at visualcpp@microsoft.com. For any issues or suggestions, please let us know via Help > Send Feedback > Report a Problem in the IDE.