-
Notifications
You must be signed in to change notification settings - Fork 274
C++20 module support v2 #1575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
C++20 module support v2 #1575
Changes from all commits
5f7d2f5
469c963
84da05a
ab72882
464a75c
b6d6186
717754a
d9ec817
ca0090a
1aceeed
05a845e
6fffa1d
b8b9715
25ce335
ccfee65
f4073d3
d5d1821
4d91f75
79eb574
b0d4656
392a1c3
9207cf9
9a98166
c4e1b06
5889c9e
2c83d42
cb62bc7
054fdf5
9322551
805c89c
6c852cc
aed4a2d
a7b1413
14eb2dc
b8b0cdd
d784e0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # C++/WinRT Codebase — Agent Instructions | ||
|
|
||
| ## Repository Structure | ||
|
|
||
| - `cppwinrt/` — The cppwinrt.exe code generator (C++ source) | ||
| - `main.cpp` — CLI parsing, namespace iteration, SCC detection, .ixx orchestration | ||
| - `file_writers.h` — All file generation functions (headers, .ixx modules, component stubs) | ||
| - `code_writers.h` — Code-level writing utilities (guards, namespace wrappers, type writers) | ||
| - `type_writers.h` — Type formatting (ABI signatures, names, GUIDs) | ||
| - `component_writers.h` — Component authoring code generation | ||
| - `helpers.h` — Metadata reading helpers | ||
| - `settings.h` — Global settings populated from CLI args | ||
| - `text_writer.h` — Core text writer infrastructure | ||
| - `strings/` — String literal `.h` files embedded by the prebuild step. Changes require: delete prebuild.exe → rebuild solution | ||
| - `nuget/` — MSBuild targets, props, and NuGet packaging | ||
| - `Microsoft.Windows.CppWinRT.targets` — Main MSBuild integration (projections, module support) | ||
| - `test/` — Test projects | ||
| - `test/test_cpp20_module/` — Standalone module test (in main solution) | ||
| - `test/nuget/` — NuGet integration tests (multi-project module chain) | ||
| - `docs/` — Documentation | ||
| - `natvis/` — Visual Studio debug visualizer (includes strings/*.h in its pch.h — add new files there too) | ||
|
|
||
| ## Build Process | ||
|
|
||
| - Use VS Developer Shell for correct toolset environment | ||
| - `cmake --build build --config Release --target cppwinrt` for cppwinrt.exe (or MSBuild: `msbuild cppwinrt\cppwinrt.vcxproj /p:Configuration=Release /p:Platform=x64`) | ||
| - NuGet tests: `msbuild test\nuget\NuGetTest.sln /p:Configuration=Release /p:Platform=x64` | ||
| - Module test projects require v145 toolset (VS 2026). Directory.Build.Props sets v143 by default — override with `<PlatformToolset>v145</PlatformToolset>` in Configuration PropertyGroup | ||
|
|
||
| ## Key Patterns | ||
|
|
||
| ### Prebuild Embedding | ||
| The `strings/*.h` files are embedded as string literals by the prebuild step. If you modify any `strings/*.h` file, you must delete `prebuild.exe` and rebuild the entire solution for changes to take effect. | ||
|
|
||
| ### Module Guard Macros | ||
| - `WINRT_IMPL_BUILD_MODULE` — Defined in .ixx global fragment. Makes `WINRT_EXPORT` expand to `export extern "C++"` and suppresses `#include` of dependencies | ||
| - `WINRT_IMPORT_MODULE` — Defined by consumers who import modules. Makes namespace headers and base.h no-op (types come from module import) | ||
| - `WINRT_EXPORT` — Empty in header mode, `export extern "C++"` in module mode. Defined in `winrt/base_macros.h` | ||
| - `WINRT_IMPL_STD_EXPORT` — Empty in header mode, `extern "C++"` (without export) in module mode. Used for `namespace std` specializations | ||
|
|
||
| ### Generated Header Structure | ||
| Each namespace produces four header files: | ||
| - `impl/<ns>.0.h` — Forward declarations, ABIs, GUIDs, categories | ||
| - `impl/<ns>.1.h` — Interface definitions | ||
| - `impl/<ns>.2.h` — Delegates, structs, class implementations | ||
| - `<ns>.h` — Public API surface (consume definitions, class wrappers, operators) | ||
|
|
||
| ### Dependency Collection | ||
| When generating headers with `-modules`, writer.depends is inspected after each header to build a namespace dependency graph. This graph drives SCC detection and module import lists. | ||
|
|
||
| ## Common Gotchas | ||
|
|
||
| - Module IFCs are NOT compatible across toolset versions — always clean rebuild when switching | ||
| - PCH and modules can coexist but PCH should NOT include winrt headers when using modules | ||
| - `/ifcSearchDir` works for the module dependency scanner to find IFCs, but cross-component modules may need explicit `/reference "name=path.ifc"` flags | ||
| - `import std;` requires `BuildStlModules=true` | ||
| - `strings/base_macros.h` is the single source of truth for shared macros (generated as `winrt/base_macros.h`). New macros go in `base_macros.h` only | ||
| - When adding, removing, or heavily refactoring `strings/*.h` files, always rebuild the natvis project (`natvis/cppwinrtvisualizer.sln`) to verify — it includes strings/*.h directly in its pch.h | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| # C++/WinRT Modules — Agent Instructions | ||
|
|
||
| ## Module Architecture (v2 — Per-Namespace) | ||
|
|
||
| Each WinRT namespace gets its own C++20 named module (`winrt.<Namespace>`). Base infrastructure is in `winrt_base` and `winrt_numerics`. | ||
|
|
||
| ### Code Generator Flow | ||
|
|
||
| 1. `-modules` flag enables .ixx generation in cppwinrt.exe | ||
| 2. `-module_include`/`-module_exclude` filter which namespaces get modules | ||
| 3. Headers are generated with dependency tracking (deps_ptr parameter) | ||
| 4. Tarjan's SCC algorithm detects cyclic namespace groups | ||
| 5. Standalone namespaces get individual .ixx; cyclic groups get consolidated SCC owner + re-export stubs | ||
|
|
||
| ### MSBuild Flow | ||
|
|
||
| 1. `CppWinRTBuildModule=true` adds `-modules` to cppwinrt.exe invocations | ||
| 2. `CppWinRTAddModuleInterfaces` discovers `$(GeneratedFilesDir)winrt\*.ixx` and adds to ClCompile | ||
| 3. `CppWinRTConsumeModule` metadata on ProjectReference controls per-reference IFC sharing | ||
| 4. `CppWinRTResolveModuleReferences` calls `CppWinRTGetModuleOutputs` on tagged references | ||
| 5. Platform projection suppresses `-modules` when consuming pre-built IFCs | ||
|
|
||
| ### Critical Invariants | ||
|
|
||
| - Module guards are unconditional in codegen — `-modules` controls .ixx generation and component codegen (module.g.cpp, stub .cpp) | ||
| - SCC owner is alphabetically first namespace in the cycle | ||
| - All .ixx filenames use `winrt` prefix: `winrt.Windows.Foundation.ixx`, `winrt_base.ixx` | ||
| - Shared macros live in `strings/base_module.h` → generates `winrt/macros.h`. `base_macros.h` includes it via `#include "winrt/macros.h"` | ||
|
|
||
| ### Testing Changes | ||
|
|
||
| After modifying cppwinrt.exe code: | ||
| 1. Rebuild cppwinrt.exe: `msbuild cppwinrt\cppwinrt.vcxproj /p:Configuration=Release /p:Platform=x64` | ||
| 2. Run standalone test: build `test_cpp20_module` in main solution | ||
| 3. Run NuGet tests: `msbuild test\nuget\NuGetTest.sln /p:Configuration=Release /p:Platform=x64` | ||
|
|
||
| After modifying targets: | ||
| 1. Clean NuGet test obj dirs | ||
| 2. Build with `/v:normal` and check "Module providers:" diagnostic messages | ||
| 3. Inspect `.rsp` files in `obj/` to verify correct `-modules` flag placement |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -379,6 +379,15 @@ jobs: | |
| $target_platform = "${{ matrix.arch }}" | ||
| & "_build\$target_platform\$target_configuration\cppwinrt.exe" -in local -out _build\$target_platform\$target_configuration -verbose | ||
|
|
||
| - name: Remove module test projects on v143 | ||
| if: matrix.toolchain.platform_toolset == 'v143' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we detect/enforce v145 requirement? is there a graceful degradation path to v143, et al? is that not worthwhile?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are fundemental limitations in the v143 toolset. One particular pain point is that v143 modules don't like the Here's a CI run when it aattempted to use v143: https://github.com/microsoft/cppwinrt/actions/runs/24921696485/job/72984385782 Even after working around that by wrapping the return type like
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. However, rather than relying on a cryptic build error to leave a user wondering if they held it wrong, we could issue a I'm leaning towards a warning like "This probably won't work on this version", rather than forbidding it. I bet |
||
| run: | | ||
| # Module test projects require v145 toolset | ||
| mv test\nuget\NugetTest.sln test\nuget\NugetTest.sln.orig | ||
|
dmachaj marked this conversation as resolved.
|
||
| Get-Content test\nuget\NugetTest.sln.orig | | ||
| Where-Object { -not ($_ -match 'TestModule') } | | ||
| Set-Content test\nuget\NugetTest.sln | ||
|
|
||
| - name: Run nuget test | ||
| run: | | ||
| cmd /c "$env:VSDevCmd" "&" msbuild /m /clp:ForceConsoleColor "$env:msbuild_config_props" test\nuget\NugetTest.sln | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this reminds me - do we still have the #pragma detect mismatch protection to prevent mixing cppwinrt versions across a build? that was a snag I ran into in early experiments (due do it being macro-based). do we have a test case to validate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TLDR; Good point. Fix incoming.
Full version:
I had to think about this one for a sec, and I think there might be a small gap, which I'll try to test for.
But at the same time, modules offer stronger built-in protection for some cases.
To refresh my memory and review what we have, here is what I believe works right now:
#pragma detect_mismatchandcheck_versionare still present inbase.h, and therefore thewinrt_basemodule.winrt_basehaving different versions should still hit the#pragma detect_mismatchwinrt_baseinto a binary should still hit the#pragma detect_mismatchwinrt_baseinto a translation unit, even if they were the sameCPPWINRT_VERSION, but if you manage to pull it off, I'd still expect#pragma detect_mismatchto catch it.The gap is:
winrt_baseof a different version. (We still have a single version ofwinrt_basein the binary, but a namespace module of a different version is importing it)We're probably covered on many of the "scary" cases, like silent ODR violations of base types causing undefined behavior, but not all of it. The interesting machinery types are all owned by
winrt_base, and the various namespace modules mostly build on it to create the WinRT projections. ABI-breaking changes to types in base would probably cause a build error, e.g. "wrong number of parameters inwinrt::some_function" or " typebardoesn't exist in namespacewinrt". But an update could violate assumptions on how those building blocks are supposed to be used, and do so in a way that doesn't break the build.When compiling the namespace modules, we're importing
winrt_baseinstead of #includingbase.h. Bothbase.hand all the modules #includebase_macros.h, butCPPWINRT_VERSION-related stuff is directly inbase.h, so the namespace modules don't see it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got a fix up now.
winrt_basenow exports the version string, and namespace modules check their locally-generated version against the version they import fromwinrt_base.