Table of Contents

  1. Visual Studio Generators
  2. MSVC Toolchain Detection
  3. vcpkg Integration
  4. DLL Export Macros
  5. Windows-Specific APIs
  6. Unicode/MBCS Configuration
  7. Windows SDK Selection
  8. Debugging with Visual Studio
  9. Common MSVC Warnings Configuration
Back to CMake Mastery Series

Windows with MSVC

June 4, 2026 Wasil Zafar 12 min read

A comprehensive platform guide for building C++ projects on Windows using CMake with the Microsoft Visual C++ toolchain — generators, vcpkg, DLL exports, Unicode, and debugging.

Visual Studio Generators

On Windows, CMake's most common generator targets Visual Studio's MSBuild system. The Visual Studio generator creates .sln and .vcxproj files that can be opened directly in the IDE or built from the command line with msbuild. Unlike single-configuration generators (Makefiles, Ninja), Visual Studio generators are multi-configuration — one build tree supports Debug, Release, RelWithDebInfo, and MinSizeRel simultaneously.

# Generate Visual Studio 2022 solution (64-bit)
cmake -G "Visual Studio 17 2022" -A x64 -S . -B build

# Generate for 32-bit (rare today but still supported)
cmake -G "Visual Studio 17 2022" -A Win32 -S . -B build32

# Generate for ARM64 (Windows on ARM)
cmake -G "Visual Studio 17 2022" -A ARM64 -S . -B build-arm64

# Build from command line (Release configuration)
cmake --build build --config Release --parallel

# Build specific target
cmake --build build --config Debug --target mylib
Ninja as Alternative: Many Windows developers now prefer -G Ninja with the MSVC toolchain for faster builds. Run from a "Developer Command Prompt" or use vcvarsall.bat to set up the environment first: call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
# Using Ninja with MSVC (from Developer Command Prompt)
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -S . -B build-ninja
cmake --build build-ninja --parallel

# Or use CMake Presets to avoid manual environment setup
cmake --preset=windows-release
cmake --build --preset=windows-release

MSVC Toolchain Detection

CMake automatically detects the MSVC compiler when using a Visual Studio generator or when running from a configured environment. The detection populates variables like MSVC, MSVC_VERSION, and CMAKE_CXX_COMPILER_ID. Understanding the version mapping is critical for conditional logic:

cmake_minimum_required(VERSION 3.21)
project(WindowsDetection LANGUAGES CXX)

# MSVC version detection
if(MSVC)
    message(STATUS "MSVC version: ${MSVC_VERSION}")
    # 1920-1929 = VS 2019 (v16.x)
    # 1930-1939 = VS 2022 (v17.0-v17.9)
    # 1940+     = VS 2022 (v17.10+)

    if(MSVC_VERSION GREATER_EQUAL 1930)
        message(STATUS "Visual Studio 2022 detected")
    endif()
endif()

# Compiler ID is always "MSVC" for cl.exe
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    # Set MSVC-specific compile options
    target_compile_options(mylib PRIVATE
        /W4           # Warning level 4
        /WX           # Warnings as errors
        /permissive-  # Standards conformance
        /Zc:__cplusplus  # Correct __cplusplus macro
    )
endif()

# Detect toolset version (v143 for VS 2022)
message(STATUS "Toolset: ${CMAKE_VS_PLATFORM_TOOLSET}")

vcpkg Integration

vcpkg is Microsoft's cross-platform package manager for C++ libraries. CMake integration is achieved through a toolchain file that intercepts find_package() calls, redirecting them to vcpkg-installed libraries. The recommended approach uses manifest mode with a vcpkg.json file at your project root.

# Clone vcpkg (one-time setup)
git clone https://github.com/microsoft/vcpkg.git C:\vcpkg
C:\vcpkg\bootstrap-vcpkg.bat

# Configure with vcpkg toolchain
cmake -G "Visual Studio 17 2022" -A x64 ^
    -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake ^
    -S . -B build

# Or set as environment variable (recommended)
set VCPKG_ROOT=C:\vcpkg
cmake -G "Visual Studio 17 2022" -A x64 ^
    -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake ^
    -S . -B build
{
    "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
    "name": "my-project",
    "version-semver": "1.0.0",
    "dependencies": [
        "fmt",
        "spdlog",
        "boost-asio",
        {
            "name": "openssl",
            "version>=": "3.0.0"
        }
    ],
    "builtin-baseline": "a1a1cbc975767298ab7da9f51da175a33a56a764",
    "overrides": [
        { "name": "fmt", "version": "10.2.1" }
    ]
}
# CMakeLists.txt — vcpkg packages are found normally
cmake_minimum_required(VERSION 3.21)
project(VcpkgDemo LANGUAGES CXX)

# vcpkg integrates transparently with find_package
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(Boost REQUIRED COMPONENTS asio)
find_package(OpenSSL REQUIRED)

add_executable(app main.cpp)
target_link_libraries(app PRIVATE
    fmt::fmt
    spdlog::spdlog
    Boost::asio
    OpenSSL::SSL
    OpenSSL::Crypto
)
Windows Gotcha: When using vcpkg with Visual Studio generators, set -DVCPKG_TARGET_TRIPLET=x64-windows explicitly. The default triplet may be x86-windows which causes link errors on 64-bit builds. For static libraries, use x64-windows-static.

DLL Export Macros

Windows requires explicit symbol export/import declarations for shared libraries (DLLs). CMake's GenerateExportHeader module automates this with a generated header containing platform-appropriate macros. This eliminates the need for manual __declspec(dllexport)/__declspec(dllimport) management.

cmake_minimum_required(VERSION 3.21)
project(ExportDemo LANGUAGES CXX)

# Create shared library
add_library(mylib SHARED
    src/core.cpp
    src/utils.cpp
)

# Generate export header automatically
include(GenerateExportHeader)
generate_export_header(mylib
    EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/include/mylib/export.h"
    EXPORT_MACRO_NAME MYLIB_API
    STATIC_DEFINE MYLIB_STATIC
)

# Make the generated header accessible
target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# Enable hidden visibility (consistent with GCC/Clang default)
set_target_properties(mylib PROPERTIES
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON
    WINDOWS_EXPORT_ALL_SYMBOLS OFF  # Use explicit exports
)
// include/mylib/core.h
#pragma once
#include "mylib/export.h"

// MYLIB_API expands to __declspec(dllexport) when building,
//           expands to __declspec(dllimport) when consuming
class MYLIB_API CoreEngine {
public:
    CoreEngine();
    ~CoreEngine();
    void initialize();
    int process(const char* input);
};

// Free function with export
MYLIB_API int compute_result(int a, int b);

Windows-Specific APIs

Windows applications often need special CMake properties to configure the application subsystem, link against Windows libraries, and handle platform-specific resources like icons and manifests.

cmake_minimum_required(VERSION 3.21)
project(WindowsApp LANGUAGES CXX)

# GUI application (uses WinMain entry point, no console window)
add_executable(myapp WIN32
    src/main.cpp
    src/app.cpp
    resources/app.rc       # Windows resource file (icon, manifest)
)

# WIN32_EXECUTABLE property (alternative to WIN32 keyword)
# set_target_properties(myapp PROPERTIES WIN32_EXECUTABLE TRUE)

# Link common Windows libraries
target_link_libraries(myapp PRIVATE
    ws2_32      # Winsock2
    winmm       # Multimedia (timers)
    shlwapi     # Shell Lightweight Utility
    dbghelp     # Debugging helpers
    comctl32    # Common Controls
)

# Add version information resource
configure_file(
    "${CMAKE_SOURCE_DIR}/resources/version.rc.in"
    "${CMAKE_BINARY_DIR}/version.rc"
    @ONLY
)
target_sources(myapp PRIVATE "${CMAKE_BINARY_DIR}/version.rc")

# Application manifest for DPI awareness and admin elevation
set_target_properties(myapp PROPERTIES
    VS_DPI_AWARE "PerMonitor"
    VS_USER_PROPS ""
)
Windows Real-World Scenario
Console vs GUI Application Switching

Use a CMake option to build either a console or GUI application from the same source. This is common for applications that need console output during development but run as GUI-only in production:

option(BUILD_CONSOLE "Build as console application" OFF)

add_executable(myapp src/main.cpp)

if(BUILD_CONSOLE)
    # Console application — has stdout, uses main()
    set_target_properties(myapp PROPERTIES WIN32_EXECUTABLE FALSE)
else()
    # GUI application — no console, uses WinMain()
    set_target_properties(myapp PROPERTIES WIN32_EXECUTABLE TRUE)
    target_compile_definitions(myapp PRIVATE APP_GUI_MODE)
endif()

Unicode/MBCS Configuration

Windows APIs come in two variants: wide-character (Unicode, W suffix) and multi-byte (ANSI, A suffix). Modern applications should always target Unicode. CMake can configure this consistently across all source files:

cmake_minimum_required(VERSION 3.21)
project(UnicodeApp LANGUAGES CXX)

add_executable(app src/main.cpp)

# Define UNICODE and _UNICODE for Windows API wide-char variants
target_compile_definitions(app PRIVATE
    UNICODE
    _UNICODE
    WIN32_LEAN_AND_MEAN       # Exclude rarely-used Windows headers
    NOMINMAX                   # Prevent min/max macro conflicts
    _CRT_SECURE_NO_WARNINGS   # Suppress deprecated CRT warnings
)

# For MBCS (legacy, not recommended for new code)
# target_compile_definitions(app PRIVATE _MBCS)
NOMINMAX is Critical: The Windows headers define min and max as macros, which conflict with std::min and std::max. Always define NOMINMAX before including any Windows headers — or better, set it globally via CMake so you never forget.

Windows SDK Selection

Visual Studio generators allow you to target a specific Windows SDK version. This determines which Windows APIs are available at compile time. The SDK version is separate from the platform toolset version.

# Specify SDK version at generation time
cmake -G "Visual Studio 17 2022" -A x64 ^
    -DCMAKE_SYSTEM_VERSION=10.0.22621.0 ^
    -S . -B build

# Or target the latest installed SDK
cmake -G "Visual Studio 17 2022" -A x64 ^
    -DCMAKE_SYSTEM_VERSION=10.0 ^
    -S . -B build
cmake_minimum_required(VERSION 3.21)
project(SdkDemo LANGUAGES CXX)

# Report detected SDK
message(STATUS "Windows SDK: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")

# Conditionally use APIs based on SDK version
if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_GREATER_EQUAL "10.0.19041.0")
    target_compile_definitions(app PRIVATE HAS_WIN10_2004_APIS)
endif()

# Target minimum Windows version via _WIN32_WINNT
# 0x0A00 = Windows 10, 0x0601 = Windows 7
target_compile_definitions(app PRIVATE
    _WIN32_WINNT=0x0A00
    WINVER=0x0A00
)

Debugging with Visual Studio

CMake can configure debugging properties that appear in the Visual Studio project settings, including working directories, command-line arguments, and environment variables for debug sessions.

cmake_minimum_required(VERSION 3.21)
project(DebugDemo LANGUAGES CXX)

add_executable(app src/main.cpp)

# Set the working directory for F5 debugging in Visual Studio
set_target_properties(app PROPERTIES
    VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
    VS_DEBUGGER_COMMAND_ARGUMENTS "--config dev --verbose"
    VS_DEBUGGER_ENVIRONMENT "PATH=${CMAKE_BINARY_DIR}/lib;$ENV{PATH}\nDATA_DIR=${CMAKE_SOURCE_DIR}/data"
)

# Set app as startup project (first target added to root CMakeLists.txt)
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY
    VS_STARTUP_PROJECT app
)

# Generate PDB for Release builds (useful for crash dump analysis)
target_compile_options(app PRIVATE
    $<$<CONFIG:Release>:/Zi>
)
target_link_options(app PRIVATE
    $<$<CONFIG:Release>:/DEBUG /OPT:REF /OPT:ICF>
)

Common MSVC Warnings Configuration

MSVC uses a numeric warning level system (/W0 through /W4, plus /Wall) that differs from GCC/Clang's -Wall -Wextra flags. A professional CMake setup configures warnings per-target using generator expressions for cross-platform compatibility.

cmake_minimum_required(VERSION 3.21)
project(WarningsDemo LANGUAGES CXX)

add_library(mylib src/lib.cpp)

# Cross-platform warning configuration
target_compile_options(mylib PRIVATE
    $<$<CXX_COMPILER_ID:MSVC>:
        /W4             # High warning level
        /WX             # Warnings as errors
        /permissive-    # ISO C++ conformance
        /w14242         # Conversion narrowing
        /w14254         # Operator conversion
        /w14263         # Member function hides virtual
        /w14265         # Class has virtual functions but no virtual dtor
        /w14287         # Unsigned/negative mismatch
        /w14296         # Expression is always false
        /w14311         # Pointer truncation (64→32 bit)
        /w14545         # Expression before comma evaluates to function
        /w14546         # Function call before comma missing arg list
        /w14547         # Operator before comma has no effect
        /w14549         # Operator before comma has no effect
        /w14555         # Expression has no effect
        /w14619         # Unknown pragma warning
        /w14640         # Thread-unsafe static local initialization
        /w14826         # Conversion is sign-extended
        /w14905         # Wide string literal cast to LPSTR
        /w14906         # String literal cast to LPWSTR
        /w14928         # Illegal copy-initialization
        /wd4068         # Disable: unknown pragma (for GCC pragmas)
    >
    $<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:
        -Wall -Wextra -Wpedantic -Werror
        -Wconversion -Wsign-conversion
        -Wnon-virtual-dtor -Wold-style-cast
    >
)

# Suppress specific warnings for third-party code
target_compile_options(mylib PRIVATE
    $<$<CXX_COMPILER_ID:MSVC>:
        /external:anglebrackets  # Treat #include <...> as external
        /external:W0             # No warnings from external headers
    >
)
External Headers (MSVC 2019.10+): The /external:anglebrackets flag tells MSVC to treat all angle-bracket includes as "external" code and suppress their warnings. Combined with /external:W0, this eliminates noise from system and third-party headers while keeping strict warnings on your own code.
Windows Production Pattern
Complete Windows CMake Preset

A production-ready CMakePresets.json for Windows development combining all the patterns from this guide — Visual Studio generator, vcpkg, Unicode, strict warnings, and debugging configuration:

{
    "version": 6,
    "cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
    "configurePresets": [
        {
            "name": "windows-base",
            "hidden": true,
            "generator": "Visual Studio 17 2022",
            "architecture": { "value": "x64", "strategy": "set" },
            "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
            "cacheVariables": {
                "CMAKE_CXX_STANDARD": "20",
                "CMAKE_CXX_STANDARD_REQUIRED": "ON"
            }
        },
        {
            "name": "windows-debug",
            "inherits": "windows-base",
            "displayName": "Windows Debug",
            "binaryDir": "${sourceDir}/build/debug"
        },
        {
            "name": "windows-release",
            "inherits": "windows-base",
            "displayName": "Windows Release",
            "binaryDir": "${sourceDir}/build/release"
        }
    ],
    "buildPresets": [
        {
            "name": "windows-debug",
            "configurePreset": "windows-debug",
            "configuration": "Debug"
        },
        {
            "name": "windows-release",
            "configurePreset": "windows-release",
            "configuration": "Release"
        }
    ]
}