CMake for Windows: A Concrete Protobuf Example in C++

On Windows for my version of CMake the find_package searches this folder: C:\Program Files\CMake\share\cmake-4.2\Modules This is where the CMake Protobuf definitions are!

"There is no abstract art. You must always start with something.” –Pablo Picasso

Image of a map of trails.
Photo by Maëva Catteau on Unsplash

GitHub link:https://github.com/mday299/keypuncher/tree/main/C%2B%2B/CMake/cmakeWinProtoCxx

Introduction

I have been using CMake since about 2008. It has a pretty steep learning curve but I have learned to appreciate just how fast it can make development once you jump through some hoops. I have an article on CMake on this site at https://www.keypuncher.net/cmake-introduction/ but it is largely from a Linux/Ubuntu perspective.

This tutorial is intended to be Windows specific. As such it was done on Windows 11 and Git For Windows https://git-scm.com/install/windows.

Prerequisites

Visual Studio Community Edition: https://visualstudio.microsoft.com/vs/community/

Pros of CMake

  • Cross-Platform (This is the reason I like it)
    • Works on Windows, Linux, macOS, embedded systems
    • Single build configuration for all platforms
  • Industry Standard
  • Modular and Integration with Package Managers
  • Generator Agnostic
    • Can generate Visual Studio projects, Makefiles, Ninja, or Xcode projects
    • Developers can use their preferred IDE/build system

Cons of CMake

I want to make clear at the outset that other build systems have similar quirks! Alternatives to CMake include Meson https://mesonbuild.com/, Bazel https://bazel.build/, and xmake https://xmake.io/.

CMake’s variable system is global by default, which means any variable you set in one part of your project can be silently changed somewhere else. The result is that a variable you thought controlled one thing may suddenly change because:

  • a subdirectory redefines it
  • a package’s Find*.cmake file sets it
  • the CMake cache contains an old value
  • a typo creates a new variable instead of updating the one you meant

This is why CMake beginners often see “weird” behavior that magically disappears after deleting the build/ directory.

  • Steep Learning Curve, Cryptic Error Messages
  • Build System Leakage
    • CMake abstracts build systems, but sometimes you need to know details.

When to Use CMake

  • Need maximum portability between Linux, Windows, Mac, embedded, etc.
  • Using established libraries: they probably have CMake support

Consider Alternatives if:

  • Small, personal project. CMake might be overkill! That said, projects sometimes start small and grow into something that can justify CMake.
  • Pure Windows development: Visual Studio or MSBuild might suffice.

Installation

Download CMake from the website: https://cmake.org/download/. At the time of this writing, they had the release candidate version listed first so I would scroll down to the actual version. Some people like to live on the edge and if that’s your thing I don’t judge. I’m using cmake-4.2.3-windows-x86_64.msi

Note that you must close any Powershells or cmd prompts that are BEFORE the CMake installation!

Run the installer as usual. This should result in CMake getting setup in your PATH and all the usual installation hullabaloo. It also should result in a link being placed in your start menu CMake (cmake-gui). If you like, click that link but: note that we are going to do everything from the Developer PowerShell for Visual Studio so the GUI won’t be covered in this tutorial. For the interested, scroll down to the bottom of the credits and you will find some links to online content covering that GUI.

To verify cmake is in your PATH go to a PowerShell or cmd.exe prompt and type:

cmake

If you get a help menu you are golden. If not then CMake is probably not in your PATH. If that happens to be so, you can follow a tutorial online to append to the PATH environment variable for Windows.

At the time of this writing this portion of the tutorial was heavily inspired by https://github.com/Kitware/CMake/tree/master/Help/guide/tutorial/Step8

In Developer PowerShell for Visual Studio, cd into the directory:

cd <path-to>\Step8\

then

cmake -B build

this should produce a build for your compiler (presumably Visual Studio) in a folder called “build.”

To do the build:

cmake --build build --config Debug

This will place and executable file into the subdirectory or build\Tutorial\Debug\Tutorial.exe

The CMake tutorial just outputs the square root of the input. Run it like so:

.\build\Tutorial\Debug\Tutorial.exe 65

Which produces output:


INFO: Computed sqrt of 65 to be 8.06225774829855 with SSE2
The square root of 65 is 8.06225774829855
The square of 8.06225774829855 is 64.99999999999999

That’s all fine and good, but I promised at the outset that I would provide a way to build Protobuf files and I intend to do that.

CMake’s find_package()

On Windows for my version of CMake the find_package searches this folder:

C:\Program Files\CMake\share\cmake-4.2\Modules

In that folder there should be a file called FindProtobuf.cmake:

C:\Program Files\CMake\share\cmake-4.2\Modules> ls FindProtob*
Directory: C:\Program Files\CMake\share\cmake-4.2\Modules
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 1/27/2026 11:13 AM 39456 FindProtobuf.cmake

This is where the CMake Protobuf definitions are!

Protoc Code Generator

Protobuf is notoriously hard to get working with CMake, especially on Windows. You can have conflicts between old protofbuf CMake and new Protobuf CMake and you can get missing transitive dependencies if you don’t use new CMake.

Under the hood what’s really being used by CMake is a code generator for various languages (it doesn’t help much that it is widely known as the protoc compiler: see https://protobuf.dev/reference/other/). The protoc code generator doesn’t generate to machine code, it generates code into supported languages like C++, C#, Python, Java, etc. Then you compile normally.

I’m using vcpkg from Microsoft which does most of that magic for me. The main downside to this approach is it is not cross-platform friendly. However I find it is easiest for beginners to wrap their heads around. For a more cross-platform version consider MSYS2: https://www.msys2.org/. That is not covered in this article, but lightly touched on in: https://www.keypuncher.net/g-with-msys-vs-code/

vcpkg

Download vcpkg from GitHub and cd into the directory. Note that the -disable metrics flag on the .bat file cuts down on Microsoft telemetry.

cd into wherever your GitHub repos reside.
git clone https://github.com/microsoft/vcpkg
cd vcpkg
.\bootstrap-vcpkg.bat -disableMetrics 
.\vcpkg install protobuf:x64-windows

This can take a while to build. After that:

.\vcpkg integrate install

This will produce lines like:

CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=C:/Users/mday39/repos/vcpkg/scripts/buildsystems/vcpkg.cmake"
All MSBuild C++ projects can now #include any installed libraries. Linking will be handled automatically. Installing new libraries will make them instantly available.

Full Protobuf Example

Now you should be able to build using vcpkg. On my system my commands were as follows, but you WILL have to adjust for your username and repo location!

cd <path-to>\cmakeWinProtoCxx\CMakeProtobufExample
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="C:/Users/<user-name>/repos/vcpkg/scripts/buildsystems/vcpkg.cmake"
cmake -B build-debug -S . `-DCMAKE_TOOLCHAIN_FILE="C:/Users/<user-name>/repos/vcpkg/scripts/buildsystems/vcpkg.cmake" ` -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug --config Debug
.\build-debug\Debug\ProtobufExample.exe
cmake -B build-release -S . `-DCMAKE_TOOLCHAIN_FILE="C:/Users/<username>/repos/vcpkg/scripts/buildsystems/vcpkg.cmake" ` -DCMAKE_BUILD_TYPE=Release
cmake --build build-release --config Release
.\build-release\Release\ProtobufExample.exe

This should result in a rudimentary library checkout system!

Why the add_book_request.bin file?

The add_book_request.bin file is a binary file created by the example main.cpp I provided earlier. It contains a serialized protobuf message.

Specifically, it's an AddBookRequest message (from your library.proto) that has been serialized to disk. Here's what the binary code roughly does:

// Create an AddBookRequest with a Book inside it
library::AddBookRequest addRequest;
addRequest.mutable_book()->CopyFrom(book);

// Serialize (save) it to a binary file
std::fstream output("add_book_request.bin", std::ios::out | std::ios::binary | std::ios::trunc);
addRequest.SerializeToOstream(&output);

output.close();

// Later, deserialize (load) it back
library::AddBookRequest loadedRequest;
std::fstream input("add_book_request.bin", std::ios::in | std::ios::binary);
loadedRequest.ParseFromIstream(&input);

input.close();

Why is this useful?

  • Persistence - Save data to disk and load it later
  • Network transmission - Send binary data over the network (more efficient than JSON/XML)
  • Inter-Process Communication (IPC) - Share data between different processes/applications
  • Debugging - You can inspect what was serialized.

Conclusion

You’ve taken a full Windows‑native C++ project from a clean directory to a working Protobuf‑powered application using CMake and Visual Studio! Along the way, you saw how CMake discovers packages, how Visual Studio organizes multi‑target builds, and how vcpkg simplifies dependency management on Windows.

CMake can feel intimidating at first, especially on Windows where build systems and toolchains behave differently than on Linux. But once you understand how configuration, generators, and package discovery fit together, the workflow becomes predictable and fast. With this foundation in place, you can extend the example into larger systems, adapt the project for cross‑platform builds, and more!

Feedback

Have questions about this article? Email the Author

Credits

CMake website: https://cmake.org/

CMake GitHub: https://github.com/Kitware/CMake

CMake GitHub where the tutorials live: https://github.com/Kitware/CMake/tree/master/Help/guide/tutorial

CMake tutorial: https://cmake.org/cmake/help/latest/guide/tutorial/index.html

CMake GUI Tutorials: https://www.youtube.com/watch?v=0eTVeABMrzQ
and https://cs184.eecs.berkeley.edu/sp19/article/10/cmake-gui-windows-tutorial
and https://cmake.org/cmake/help/latest/manual/cmake-gui.1.html

Protocol Buffers Main Site: https://protobuf.dev/

Protocol Buffers GitHub: https://github.com/protocolbuffers/protobuf

Protocol Buffers Encoding: https://protobuf.dev/programming-guides/encoding/

Protocol Buffers C++ Reference: https://protobuf.dev/reference/cpp/api-docs/

Advanced Reading

JSON file from Step 8


CMakePresets.json is CMake’s way of capturing your build configuration in a clean, reproducible, IDE‑friendly format. Visual Studio, VS Code, CLion, and command‑line CMake all understand it, so it becomes the “source of truth” for how the project should be configured.

CmakePresets.json (NOTE THAT I HAVE MODIFIED THIS FILE FROM THE ORIGINAL ON Cmake’s web site). Main thing I did was make it work better with Visual Studio 2022.

{
"version": 4,
"configurePresets": [
{
"name": "default",
"displayName": "Default VS2022/VS2026",
"description": "Simple preset for Visual Studio Community",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"TUTORIAL_USE_STD_SQRT": "OFF",
"TUTORIAL_ENABLE_IPO": "OFF"
}
}
]
}

Think of it as a CMake configure command encoded in JSON. Instead of relying on CMake’s defaults (which vary by platform, IDE, and environment), this file gives CMake a repeatable, predictable configuration.

For example, Instead of running:

cmake -S . -B build -DTUTORIAL_USE_STD_SQRT=OFF -DTUTORIAL_ENABLE_IPO=OFF

You can run:

cmake --preset tutorial

Or in Visual Studio: Project → Configure Presets → tutorial

Breaking down the file

"version": 4

The schema version. Version 4 supports configure, build, and test presets.

"configurePresets": [...]

This section defines one or more configure presets.

"name": "default"

This is the internal name of the preset. Users can run it with:

cmake --preset default

The internal name. Used on the command line:

cmake --preset tutorial

"displayName" and "description"

Purely for Integrated Development Euvironments (IDEs) like Visual Studio.

"generator": "Visual Studio 17 2022"

This is the most important line in the whole file.

It forces CMake to use the Visual Studio generator, which:

  • enables multi‑config builds (Debug/Release)
  • uses MSBuild instead of Ninja
  • produces the standard Visual Studio directory layout
  • works in both VS2022 and VS2026

Without this line, Visual Studio often defaults to Ninja.

"binaryDir": "${sourceDir}/build"

This tells CMake:

  • Put all generated build files in <source>/build
  • ${sourceDir} is automatically the directory containing the preset file

"cacheVariables": {...}

These are the same as passing -DVAR=value on the command line.

I have:

  • TUTORIAL_USE_STD_SQRT=OFF Use your own mysqrt() instead of std::sqrt.
  • TUTORIAL_ENABLE_IPO=OFF Disable interprocedural optimization (LTO). The tutorial turns this on later.

These variables correspond directly to option() or set() commands in CMakeLists.

CMakePresets.json solves several real problems:

Keeps the file simple

Perfect for beginners — no inheritance, no build presets, no platform matrices.

Reproducibility

Everyone on the team configures the project the same way.

Guarantees Debug/Release separation

Because the Visual Studio generator is multi‑config.

Multiple configurations

You can add presets like:

  • debug-with-sanitizers
  • release-with-ipo
  • cross-compile-arm

What This Built For Me

PS C:\Users\<username>\repos\keypuncher\C++\CMake\cmakeWinProtoCxx\Step8> ls build
Directory: C:\Users\<username>\repos\keypuncher\C++\CMake\cmakeWinProtoCxx\Step8\build

Mode LastWriteTime Length Name
d-— 3/3/2026 2:02 PM ALL_BUILD.dir
d-— 3/3/2026 2:02 PM CMakeFiles
d-— 3/3/2026 2:02 PM MathFunctions
d-— 3/3/2026 2:02 PM Tutorial
d-— 3/3/2026 2:02 PM ZERO_CHECK.dir
-a— 3/3/2026 2:02 PM 54085 ALL_BUILD.vcxproj
-a— 3/3/2026 2:02 PM 320 ALL_BUILD.vcxproj.filters
-a— 3/3/2026 2:02 PM 2453 cmake_install.cmake
-a— 3/3/2026 2:02 PM 14716 CMakeCache.txt
-a— 3/3/2026 2:02 PM 10185 Tutorial.sln
-a— 3/3/2026 2:02 PM 60661 ZERO_CHECK.vcxproj
-a— 3/3/2026 2:02 PM 563 ZERO_CHECK.vcxproj.filters

The files in the build/ directory are exactly what CMake generates when you target Visual Studio on Windows. If you are used to Unix Makefiles and Ninja (like) me this felt weird at first.

CMake has created a Visual Studio solution (Tutorial.sln) and a set of MSBuild project files (*.vcxproj) that represent each target in your CMakeLists. Visual Studio/MSBuild then uses those to orchestrate https://www.databricks.com/blog/what-is-orchestration the actual compilation.

The directories

CMakeFiles/

CMake’s internal bookkeeping. Contains:

  • dependency info
  • generated rules
  • scripts for incremental builds
  • metadata about each target

You never touch this typically.

ALL_BUILD.dir/ and ZERO_CHECK.dir/

These are MSBuild target directories created by Visual Studio generators.

  • ZERO_CHECK A special target that re-runs CMake automatically if any CMakeLists.txt changes. Visual Studio always generates this.
  • ALL_BUILD A meta-target that builds everything in the solution.

These folders contain MSBuild’s intermediate files for those targets.

MathFunctions/ and Tutorial/

These correspond directly to your CMake targets:

add_subdirectory(MathFunctions)
add_executable(Tutorial tutorial.cxx)

Each gets its own:

  • .vcxproj file
  • intermediate directory
  • build rules

This is how Visual Studio organizes multi-target builds.

The files

Tutorial.sln

The Visual Studio solution file. This is the top-level container that groups all the .vcxproj projects.

*.vcxproj

MSBuild project files generated from your CMake targets.

  • ALL_BUILD.vcxproj — builds everything
  • ZERO_CHECK.vcxproj — re-runs CMake
  • MathFunctions.vcxproj — your library
  • Tutorial.vcxproj — your executable

These are what Visual Studio actually builds.

*.vcxproj.filters

These define how files appear in the Visual Studio Solution Explorer (e.g., grouping .cpp and .h files). Purely UI metadata.

CMakeCache.txt

CMake’s configuration cache. Stores:

  • compiler paths
  • options
  • detected libraries
  • generator settings

This is why you can re-run builds without re-configuring.

cmake_install.cmake

The script CMake uses when you run:

cmake --install .

Visual Studio generators produce a whole ecosystem of MSBuild projects because Visual Studio expects that structure.