MSVC Toolchain and Windows Native Build
The Windows native build stack end to end: cl.exe and link.exe, the developer environment, MSBuild versus CMake, the CRT variants and the mismatched-runtime crash, DLL exports, PDBs, and pinning a toolset in CI.
Overview
A full second toolchain
Windows native C++ is a full second toolchain, not a porting detail. "Strong knowledge of MSVC libraries" means debugging a build with no Unix tooling: cl.exe compiles, link.exe links, the standard library is Microsoft's, symbols live in a separate file, and nothing is exported from a DLL unless you say so.
- The toolchain does not know where it is
MSVC depends on PATH, INCLUDE, LIB, LIBPATH and many more variables, set by a batch file generated for your specific installation.
- VsDevCmd.bat — The general one
- vcvars32, vcvars64, cross-tools variants — Thin wrappers
- vcvarsall.bat — Parameterized: host and target architecture, optional Windows SDK version, optional toolset via -vcvars_ver, optional Spectre-mitigated libraries
> [!WARNING] Microsoft warns against mixing vcvars files from different Visual Studio versions in one shell, and against copying a vcvarsall.bat between machines.
- The C runtime
Most Windows-specific production incidents come from here. Since Visual Studio 2015 the CRT has three pieces.
- Universal CRT — A Windows component, shipped through the Windows SDK
- vcruntime — Compiler-version-specific: exception handling, RTTI, runtime checks
- Startup code — Always statically linked
/MT, /MTd, /MD and /MDd select the import libraries. Every EXE and DLL chooses independently, so one process can hold several CRTs, each with its own heap and static state.
- Export control is inverted
- Linux — Every non-static symbol exported — Visibility attributes or a version script to hide
- Windows — Nothing exported — __declspec(dllexport) or a .def file to export
Linking a DLL produces the DLL plus an import library (.lib) that contains no code. Confusing the two meanings of .lib is a reliable source of confusion in mixed teams.
- Debug information lives in PDBs
- /Z7 — Full debug info embedded in the object files
- /Zi — A separate PDB
- /ZI — Edit-and-Continue PDB; forces /Gy and /FC, disables most optimization pragmas
> [!IMPORTANT] A release build with no archived PDB produces minidumps nobody can symbolize. That is a process failure, not a technical one, and it is very common.
- Versioning changed recently
Through VS 2022 the compiler was versioned 14.xx, with binary compatibility across v140 to v143. From VS 2026, the MSVC package is identified by its own toolset version.
> [!TIP] In CI, locate toolchains with vswhere and pin explicitly, rather than trusting whatever the hosted agent image installed last week.
What interviewers probe
- Crash on free only in release — CRT mismatch
- DLL will not load — Dependencies, exports
- Stack trace with no function names — Missing or mismatched PDB
- Works in Visual Studio, fails in the pipeline — Environment and toolset pinning
Naming the mechanism separates someone who has shipped on Windows from someone who has only cross-compiled for it.
Key concepts
- Developer command prompt and vcvarsall
- MSVC requires PATH, INCLUDE, LIB, LIBPATH and around twenty other variables that the installer-generated batch files set. VsDevCmd.bat is the general entry point; vcvarsall.bat takes an architecture argument (x64, x86_amd64, amd64_arm64 and so on), an optional Windows SDK version, an optional -vcvars_ver toolset selector and an optional spectre flag. With no arguments it configures x86 native, not x64.
- The three-part CRT
- Universal CRT (ucrtbase.dll, a Windows component delivered through the Windows SDK), vcruntime (compiler-version-specific: exception handling, RTTI, runtime checks), and the startup code, which is always statically linked. /MD selects the import libraries msvcrt.lib, ucrt.lib and vcruntime.lib; /MT selects libcmt.lib, libucrt.lib and libvcruntime.lib. With no option at all, the linker uses the static set.
- Mismatched runtime
- Two images in one process using different CRTs, or the same CRT in debug and release form. Each has its own allocator, its own internal layouts and its own static state, so memory, FILE handles, locales and C++ objects cannot safely cross between them. MSVC emits detect-mismatch records so many cases fail at link with LNK2038 naming RuntimeLibrary or _ITERATOR_DEBUG_LEVEL.
- dllexport, dllimport and .def
- Nothing leaves a Windows DLL unless exported. declspec(dllexport) adds the export directive at compile time and handles C++ decorated names, which is why it is preferred; declspec(dllimport) on the consumer side lets the compiler generate a direct indirect call. A .def file is the alternative and the only route to ordinals, NONAME and PRIVATE. Using both together is allowed.
- Import library versus static library
- Linking a DLL emits a .lib containing stubs and a name table that bind to the DLL at load time. A static library is a .lib containing real object code copied into the consumer. Identical extension, entirely different artifact, and a routine source of confusion.
- PDB and debug information format
- /Z7 keeps full symbolic debug info inside each .obj and produces no compiler PDB. /Zi produces a separate PDB and implies /DEBUG. /ZI is /Zi plus Edit and Continue, forces /Gy and /FC, and disables optimize pragmas. The linker produces the final PDB. Binary and PDB are matched by name, GUID and age, so a rebuild invalidates old PDBs.
- /permissive- and conformance
- Turns off Microsoft language extensions and sets /Zc:referenceBinding, /Zc:strictStrings, /Zc:rvalueCast and /Zc:ternary to conforming behaviour; from VS 2022 17.6 it also sets /Zc:lambda and /Zc:externConstexpr. It is implied by /std:c++20 and /std:c++latest, and is required for C++20 modules. It enables two-phase name lookup, which is what breaks most legacy template code on first contact.
- Toolset version, Visual Studio version, Windows SDK
- Three independent axes. Through VS 2022 the platform toolset carried a v-number tied to the IDE (v140 for 2015 through v143 for 2022) with binary compatibility maintained across that range; from VS 2026 the MSVC version is decoupled and identified by the toolset version itself. The Windows SDK versions separately and supplies the UCRT headers and libraries. Pin all three in CI.
When to use it
- Adding a Windows target to a codebase that has only ever been built with GCC or Clang on Linux
- Debugging a crash that only reproduces in a release build on Windows and points at a free or a destructor
- Setting up a Windows build agent and needing the toolset and SDK to be pinned rather than whatever the image happens to ship
- Shipping a native DLL that third parties link against, where the export surface and the CRT choice become a public contract
- Symbolising production minidumps and discovering the PDBs were never archived
- Evaluating clang-cl to get Clang diagnostics and sanitizers while staying on the MSVC ABI and standard library
Interview questions
- A DLL crashes inside free() when the host EXE passes it an object to delete. Walk through the cause and the fixes.
- How do you produce, ship and use PDBs so that a production crash is actually debuggable?
- You are setting up Windows CI for a C++ project. How do you pin and locate the toolchain, and what are the failure modes?
- Explain __declspec(dllexport)/dllimport versus a .def file, and how you would structure exports for a library with external consumers.
- What does /permissive- change, why would you adopt it, and what breaks first?
- An application fails to start on a customer machine with a missing-DLL or 0xc000007b error. How do you diagnose it?
Sign up free to read the full topic
More DevOps topics
- Local Kubernetes Cluster Setup (minikube / kind / k3d)
- Log Aggregation
- Microservices Design
- Migration and Production Patterns
- Monitoring and Troubleshooting
- Monorepo Build Systems
- Multi-Cluster GitOps
- Native Artifact Signing and Release
- Native Debugging — gdb, Core Dumps and Symbols
- Node and Scaling Errors — NotReady, Scale-Up, Drain, Terminating and HPA
- Notifications, Alerts and Webhooks
- OpenFeature & Feature Flag Governance
Start free on Cariara · Coding problems · System design studio