Skip to content

SingerBallad/win32pp

Repository files navigation

win32pp

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 surfaceWindows.Win32.System.ThreadingWindows::Win32::System::Threading, included as "Windows/Win32/System/Threading.hpp". The win32pp name 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.

Layout

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)

Build

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.

Error model

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 duplication

Modern bridge layer

Beyond 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.

C++23 modules (opt-in)

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 projection

The 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.)

Regenerating

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).

About

A modern C++23 projection of the Win32 API.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Contributors

Languages