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.
- C/C++ Additional Include Directories (/I) should contain the shared stdafx.h directory
- C/C++ Precompiled Header Output File (/Fp) should be set to the shared pch file (produced by SharedPCH project)
- 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.
/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 email@example.com. For any issues or suggestions, please let us know via Help > Send Feedback > Report a Problem in the IDE.
In the past, it was my understanding that a PCH was specific to the machine on which it was built, and if you attempted to copy the PCH to a different build machine, the compiler would force you to rebuild it. Is this still the case? Your blog discusses sharing the PCH between projects, but that could still mean multiple projects only on the same build machine. Can a PCH also be shared from one build box or developer’s machine to another?
This honestly does not seem to be very satisfactory, as even irrelevant differences in the PCH generate warnings. For example, the addition of an include path for a project that wasn’t part of the PCH generates a warning, even if the include path couldn’t change which files were included in the PCH.
Worse, some differences generate errors. In my projects I want static .libs to use /Z7, everything else to use /Zi. The compiler/linker do the right thing: the debug symbols from the static libs are extracted and put into the file .pdb alongside the build target. But this mix is a fatal error for the PCH.
We really need something more robust and flexible. Being able to use a common PCH makes a big difference to my build times; it’s just a real shame I can’t use it with the build settings I’m actually using.
This is great! Unfortunately this doesn’t work very well with code analysis, it looks like we cannot analyze single source files without first analyze the full projct to which it belongs.
This is working for static library and exe files. It generates errors when the compiler settings are different between projects. Is it possible to share pre-compiled header between static library and *.dll files.