Resolving MSVC 2019 SQLite 3.38.0 Build Error C2220 Due to C5105 Warning
Compiler Warning C5105 Treated as Error During SQLite 3.38.0 Compilation
Root Cause Analysis of winbase.h Macro Expansion Failure
The core issue arises when compiling SQLite 3.38.0’s amalgamation source (sqlite3.c) using Microsoft Visual C++ (MSVC) 2019 with Windows SDK 10.0.19041.0. The compiler emits warning C5105 ("macro expansion producing ‘defined’ has undefined behavior") at line 9531 of winbase.h, which is escalated to error C2220 due to project-level compiler settings treating warnings as errors. This occurs during preprocessing of Windows SDK headers included indirectly through SQLite’s platform-agnostic code. The problematic code section involves a conditional compilation directive #if MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS
that evaluates a macro defined earlier in the header. The Microsoft-specific Interlocked*
function overloads trigger this warning when the experimental MSVC preprocessor is active, exposing a conflict between legacy Windows API definitions and modern C/C++ standards compliance checks.
Key technical relationships:
- SQLite’s amalgamation (sqlite3.c) includes windows.h implicitly through its OS abstraction layer
- Windows Kits 10.0.19041.0’s winbase.h defines
MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS
using a compound logical expression involving_WIN32_WINNT
version checks - MSVC’s /experimental:preprocessor flag (enabled in the build) activates a standards-conformant preprocessor that strictly evaluates macro expansion sequences
- The project’s /WX flag (Treat Warnings As Errors) converts benign but non-compliant preprocessor usage into a fatal build-stopping condition
This error manifests specifically in environments where:
- SQLite 3.38.0 is built using non-default MSVC compiler flags
- Recent Windows SDK headers (post-Windows 8) are installed
- The build system overrides SQLite’s default Makefile.msc configuration
Trigger Conditions for Preprocessor Compatibility Warnings
Four primary factors combine to produce this build failure:
1. Activation of MSVC’s Conformant Preprocessor
The /experimental:preprocessor
compiler option enables MSVC’s updated C/C++ preprocessor implementation that adheres strictly to ISO C/C++ standards. This mode rejects macro expansions that generate defined
operators during token pasting, which older MSVC versions tolerated. SQLite’s default build configuration (Makefile.msc) does NOT enable this flag. Its presence in the build implies either:
- Manual addition to compiler command-line arguments
- Inheritance from higher-level project properties (e.g., CMakeLists.txt, .vcxproj files)
- Environment variables (CL, CL) modifying the compiler’s default behavior
2. Windows SDK Header Version Sensitivity
Windows Kits 10.0.19041.0 (Windows 10 2004 SDK) contains updated definitions for InterlockedCompareExchange
, InterlockedIncrement
, and other synchronization primitives. The winbase.h header uses conditional compilation to maintain backward compatibility with pre-Windows Vista systems. The macro MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS
evaluates to (_WIN32_WINNT >= 0x0502 || !defined(_WINBASE_))
, creating a scenario where the conformant preprocessor identifies a standards violation when expanding this logic.
3. SQLite’s Compile-Time Environment Configuration
Although SQLite itself doesn’t directly include winbase.h, its use of Windows API functions for file I/O, memory mapping, and mutex operations pulls in system headers. The amalgamation’s monolithic structure (sqlite3.c) exacerbates header inclusion side effects because all SQLite code is processed in a single translation unit. Any Windows.h inclusion policy (e.g., WIN32_LEAN_AND_MEAN
) or _WIN32_WINNT
version defines in the build environment directly affect how winbase.h processes the contentious macros.
4. Aggressive Warning Policies in Build Configuration
Projects enabling /W4
(Level 4 Warnings) and /WX
(Warnings as Errors) will fatalize C5105. The SQLite source is typically compiled with /W3
in its default Makefile.msc, but custom builds often increase warning levels for code quality enforcement. This transforms what would be a non-fatal diagnostic into a build abortion.
Comprehensive Build Configuration Remediation Strategies
Step 1: Isolate the Triggering Preprocessor Flag
Execute the build process with verbose logging to capture the exact compiler invocation:
msbuild /v:detailed YourProject.sln > build.log
Search the log for cl.exe
commands containing /experimental:preprocessor
. If found, trace its origin:
- Visual Studio IDE Check: Navigate to Project Properties > C/C++ > Preprocessor > "Enable Experimental Preprocessor". Ensure this is set to No (/experimental:preprocessor-).
- Command-Line Audit: For Makefile-based builds, inspect the Makefile.msc (or equivalent) for
CFLAGS
entries containing the flag. SQLite’s default Makefile.msc uses:CFLAGS = /nologo /W3 /Gy /GF /Zi
Remove any
/experimental:preprocessor
additions.
Step 2: Override Windows SDK Macro Definitions
If removing the experimental preprocessor isn’t feasible (e.g., for cross-platform code using __VA_OPT__
), redefine the problematic winbase.h macros before including SQLite:
#define MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS 0
#include "sqlite3.c"
Alternatively, force _WIN32_WINNT
to a version ≥ 0x0502 (Windows Server 2003) in your project’s preprocessor definitions:
CL /D_WIN32_WINNT=0x0502 ...
This satisfies the MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS
condition without relying on macro logic that trips the new preprocessor.
Step 3: Selective Warning Suppression
Disable C5105 specifically for Windows SDK headers using pragmas:
#pragma warning(push)
#pragma warning(disable:5105)
#include <windows.h>
#pragma warning(pop)
For SQLite amalgamation builds where modifying source isn’t practical, pass /wd5105
to CL.exe:
CFLAGS = /nologo /W3 /wd5105 ...
Step 4: Downgrade Windows SDK Version
If using Windows SDK 10.0.19041.0 isn’t mandatory, revert to an earlier version (e.g., 10.0.18362.0) via Visual Studio Installer. Update the project’s include paths:
INCLUDE = C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um;...
Step 5: Patch SQLite’s Header Inclusion
Modify SQLite’s os_win.c to include Windows.h with strict controls:
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
This reduces header bloat and potentially bypasses the winbase.h code path causing C5105.
Step 6: Update to SQLite 3.38.1+
Later SQLite versions (3.38.1 onward) include build system adjustments for modern MSVC toolchains. The Makefile.msc
now explicitly sets /W3
and avoids injecting experimental flags.
Step 7: Hybrid Build Approach
Compile SQLite as a static library using its unmodified Makefile.msc, then link against it from your project. This isolates SQLite’s build environment from your application’s stricter compiler settings:
nmake -f Makefile.msc sqlite3.lib
Final Validation Checklist
- Confirm
/experimental:preprocessor
is absent from compiler commands - Verify
_WIN32_WINNT ≥ 0x0502
in preprocessor definitions - Ensure C5105 is either suppressed or downgraded to non-fatal
- Test with Windows SDK 10.0.19041.0 and latest MSVC 2019 updates
- Rebuild entire solution after clearing intermediate files
Permanent resolution requires aligning the project’s compiler flags with SQLite’s expected build environment while addressing Windows SDK header compatibility through targeted definitions and warning suppression. For teams requiring /experimental:preprocessor
, maintaining a patch that adjusts winbase.h inclusion or works around the macro logic is essential until Microsoft updates the Windows SDK headers.