A modern C++23 projection of the Win32 API, generated from Microsoft's
Windows.Win32.winmd metadata (the same metadata that powers windows-rs and CsWin32).
Design goals:
- Absolutely modern C++23 — concepts, templates,
std::span,std::expected,std::optional, RAII,constexpr,enum class,[[nodiscard]], three-way comparison. - Zero
<windows.h>— every shipped header is self-contained. No Win32 macros (min,max,GetMessage→GetMessageW, …) ever leak into your translation units. - Clean, metadata-mirroring surface —
Windows.Win32.System.Threading→Windows::Win32::System::Threading, included as"Windows/Win32/System/Threading.hpp". Thewin32ppname is internal-only (the hand-written runtime); consumers never type it. - MSDN-faithful names — types/functions/constants keep their metadata spelling
(
CreateFileW,FILE_SHARE_READ,MSG), so docs are one search away. - Full Win32 coverage — the generator can emit the entire surface; the full pre-generated header pack is committed to the repo.
include/
Windows/Win32/ generated public projection (namespace Windows::Win32::...)
win32pp/core/ hand-written runtime (win32pp::core), header-only, no <windows.h>
Guid HResult Win32Error error_policy ComPtr com_concepts
UniqueHandle HandleTraits flags string Bstr span_utils apartment interop
ct callback coroutine enumerate modern bridge (pure templates; joined into core.hpp)
detail/win_abi.hpp self-declared GUID/HRESULT/IUnknown/IDispatch ABI primitives
detail/win_imports.hpp self-declared extern "C" imports the core calls
win32pp/ext/ opt-in bridge headers that #include the generated projection (still no
factory executor Variant safearray <windows.h>); exposed in the win32pp::core namespace
tools/gen/ the C# generator (Reader -> IR -> Passes -> Planner -> Emitter)
tests/ core unit tests, ABI-verify TU, no-windows.h gate, single-TU gate
examples/ hello_core (core only), hello_win32 (generated projection)
cmake/ Winmd.cmake (fetch+pin winmd), Generate.cmake (regen wiring)
Requires MSVC (C++23 / /std:c++latest), CMake ≥ 3.28, Ninja.
cmake --preset msvc-x64
cmake --build --preset msvc-x64
ctest --preset msvc-x64
No .NET is needed to consume the library — the generated headers are committed.
Fallible calls return std::expected<T, HResult> / std::expected<T, Win32Error>
(non-throwing, composes with and_then/transform, works under -fno-exceptions).
Prefer the exception style? Wrap any call in win32pp::core::Check(...):
namespace fs = Windows::Win32::Storage::FileSystem;
// CreateFileW -> RAII UniqueHANDLE; WriteFile's buffer is a BytesView — pass native C++ types
// (std::string_view / std::string / std::vector / std::span) with no reinterpret_cast.
auto file = fs::CreateFileW(L"out.bin", 0x40000000u, {}, nullptr, fs::FILE_CREATION_DISPOSITION{2}, {});
if (file) { // std::expected<UniqueHANDLE, Win32Error>
auto n = fs::WriteFile(file->get(), std::string_view{"hello"}); // -> Win32Result<uint32_t>
} // file auto-closed here (RAII)
auto forced = Check(fs::CreateFileW(L"in.bin", /* ... */)); // throwing style, zero API duplicationBeyond the raw projection, a hand-written layer adds C++ ergonomics — pure-template pieces live in
win32pp::core (in core.hpp); pieces that need Win32 functions are opt-in win32pp/ext/ headers
that reuse the generated, ABI-verified imports (no regeneration, no re-declared ABI):
// Lambda -> Win32 callback (stateful; context recovered from the callback's last arg / LPARAM):
auto cb = core::MakeContextCallback<WNDENUMPROC>([&](HWND h){ ++count; return BOOL{1}; });
EnumWindows(cb.thunk(), cb.context());
// Coroutines with Win32 thread-pool awaiters (ext/executor.hpp):
core::Task<int> work() { co_await core::ResumeBackground(); /* on a pool thread */ co_return 1; }
// COM activation (ext/factory.hpp) — HrResult<ComPtr<T>>:
auto dlg = core::CreateInstance<IFileOpenDialog>(CLSID_FileOpenDialog);
// VARIANT / SAFEARRAY RAII (ext/Variant.hpp, ext/safearray.hpp), enumeration as ranges:
for (auto item : core::ComEnumRange<IEnumString, PWSTR>{e}) { /* … */ }Also: ct.hpp (compile-time MakeHResult / typed status constants), ToStdcall (captureless
lambda → function pointer), and GenerateRange(open, next, close) for FindFirstFile-style loops.
Every namespace also ships a named-module interface under include/module/ — Windows.Win32.<Ns>.ixx
plus the hand-written win32pp.core.ixx / win32pp.ext.ixx. Each .ixx #includes its header in the
global module fragment (the header stays the correctness contract) and re-exports the public surface,
so consumers import instead of #include:
import Windows.Win32.Storage.FileSystem; // re-exports win32pp.core — no separate import needed
namespace fs = Windows::Win32::Storage::FileSystem;
auto file = fs::CreateFileW(L"out.bin", /* … */); // Win32Result<UniqueHANDLE>
auto n = fs::WriteFile(file->get(), std::string_view{"hi"});Each Windows.Win32.<Ns> module does export import win32pp.core;, so importing it alone brings the
runtime types (HResult, Win32Result, UniqueHandle, …). The opt-in bridge (import win32pp.ext; —
CreateInstance, coroutine awaiters, Variant) stays a separate import.
Aggregate ("upper") modules mirror the namespace tree — each prefix re-exports its children:
import Windows.Win32.System; // all of System.* (Com, Threading, Diagnostics.*, …)
import Windows.Win32; // the entire projectionThe top-level Windows.Win32 is composed of module partitions — one per area (Windows.Win32:System,
Windows.Win32:Graphics, …) — so its (huge) re-export list is decomposed into isolated interface units.
Granular per-namespace modules remain importable; the aggregates are additive convenience.
Enable the build with -DWIN32PP_MODULES=ON (builds a representative slice + test_modules).
Because each module wraps its full header transitively, BMIs are large — building all 322 is heavy, so
the modules are an opt-in alternative, not the default. (Importers should #include <compare> for the
defaulted operator<=> on HResult.)
The generator (build-time only) pulls Microsoft.Windows.SDK.Win32Metadata from NuGet,
verifies its SHA-256, and emits the pack:
cmake --preset msvc-x64-regen # -DWIN32PP_REGENERATE=ON (needs the .NET 10 SDK)
Selective generation is driven by a request list (see tools/gen/tests/slice.gen.json).