Skip to content

Commit 55f1b45

Browse files
DefaultRyanCopilotYexuanXiao
authored
C++20 module support v2 (#1575)
* Initial implementation of vs modules based on cppwinrtplus fork * Rename WINRT_MODULE -> WINRT_IMPL_BUILD_MODULE; WINRT_CONSUME_MODULE -> WINRT_IMPORT_MODULE * Namespace modules are winrt.Namespace. Non-namespace modules are winrt_base/winrt_numerics Co-authored-by: Copilot <copilot@github.com> * Bootstrap basic unit test Co-authored-by: Copilot <copilot@github.com> * Fix coroutine export. A few more unit tests. Co-authored-by: Copilot <copilot@github.com> * module_include, module_exclude Co-authored-by: Copilot <copilot@github.com> * Rename test_module to test_cpp20_module * Nuget test project with module include/exclude Co-authored-by: Copilot <copilot@github.com> * Refine generated files in component. Co-authored-by: Copilot <copilot@github.com> * Added a type deriving from DependencyObject Co-authored-by: Copilot <copilot@github.com> * Basic module build/consume support Co-authored-by: Copilot <copilot@github.com> * Prefix namespace ixx files with "winrt." for consistency with module names and built ifc files. Co-authored-by: Copilot <copilot@github.com> * IFC needs better per-project scoping. Now it fully works end to end. * Minor fix to TestModuleComponent2 * Documentation and polish. Co-authored-by: YexuanXiao <bizwen@nykz.org> Co-authored-by: Copilot <copilot@github.com> * Fix CI failures Co-authored-by: Copilot <copilot@github.com> * Try for better std hygiene * Address some PR feedback * Better automation of AdditionalBMIDirectories Co-authored-by: Copilot <copilot@github.com> * Missed fallback definition of WINRT_IMPL_STD_EXPORT * Wrap base.h and extern the handler pointers * Uniform namespace filtering * Write trailing comments for #endif * Clarify language version requirement * Add source_location test Co-authored-by: Copilot <copilot@github.com> * Refactor/cleanup some string writers Co-authored-by: Copilot <copilot@github.com> * Add arm64 configs and replace bogus project guids with real guids. Co-authored-by: Copilot <copilot@github.com> * More cleanup of strings. Collected common macros into base_macros.h, generates into winrt/macros.h Co-authored-by: Copilot <copilot@github.com> * More strings cleanup. Emit a canonical winrt/base_macros.h Co-authored-by: Copilot <copilot@github.com> * More clarifications about guidance when sharing pre-built modules Co-authored-by: Copilot <copilot@github.com> * Ensure structured bindings are exported for IKeyValuePair Co-authored-by: Copilot <copilot@github.com> * Fix up module namespace exclude logic so it still generates import statements. * intrin.h and <cstddef> only needed by base, not namespace modules * Tidy up a few codegen bits * Fix sln config on test_cpp20_module project * Namespace modules now check version against imported winrt_base Co-authored-by: Copilot <copilot@github.com> * PR feedback: Test format, hash, natvis hook visibility, message on old compiler Co-authored-by: Copilot <copilot@github.com> * Clean up some handling of numerics-related code and add some numerics smoke tests * De-duplicate windowsnumerics.impl.h logic * A little more WINRT_IMPORT_MODULE cleanup * Use write_depends in SCC writers --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: YexuanXiao <bizwen@nykz.org>
1 parent 58811e1 commit 55f1b45

111 files changed

Lines changed: 3146 additions & 224 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# C++/WinRT Codebase — Agent Instructions
2+
3+
## Repository Structure
4+
5+
- `cppwinrt/` — The cppwinrt.exe code generator (C++ source)
6+
- `main.cpp` — CLI parsing, namespace iteration, SCC detection, .ixx orchestration
7+
- `file_writers.h` — All file generation functions (headers, .ixx modules, component stubs)
8+
- `code_writers.h` — Code-level writing utilities (guards, namespace wrappers, type writers)
9+
- `type_writers.h` — Type formatting (ABI signatures, names, GUIDs)
10+
- `component_writers.h` — Component authoring code generation
11+
- `helpers.h` — Metadata reading helpers
12+
- `settings.h` — Global settings populated from CLI args
13+
- `text_writer.h` — Core text writer infrastructure
14+
- `strings/` — String literal `.h` files embedded by the prebuild step. Changes require: delete prebuild.exe → rebuild solution
15+
- `nuget/` — MSBuild targets, props, and NuGet packaging
16+
- `Microsoft.Windows.CppWinRT.targets` — Main MSBuild integration (projections, module support)
17+
- `test/` — Test projects
18+
- `test/test_cpp20_module/` — Standalone module test (in main solution)
19+
- `test/nuget/` — NuGet integration tests (multi-project module chain)
20+
- `docs/` — Documentation
21+
- `natvis/` — Visual Studio debug visualizer (includes strings/*.h in its pch.h — add new files there too)
22+
23+
## Build Process
24+
25+
- Use VS Developer Shell for correct toolset environment
26+
- `cmake --build build --config Release --target cppwinrt` for cppwinrt.exe (or MSBuild: `msbuild cppwinrt\cppwinrt.vcxproj /p:Configuration=Release /p:Platform=x64`)
27+
- NuGet tests: `msbuild test\nuget\NuGetTest.sln /p:Configuration=Release /p:Platform=x64`
28+
- Module test projects require v145 toolset (VS 2026). Directory.Build.Props sets v143 by default — override with `<PlatformToolset>v145</PlatformToolset>` in Configuration PropertyGroup
29+
30+
## Key Patterns
31+
32+
### Prebuild Embedding
33+
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.
34+
35+
### Module Guard Macros
36+
- `WINRT_IMPL_BUILD_MODULE` — Defined in .ixx global fragment. Makes `WINRT_EXPORT` expand to `export extern "C++"` and suppresses `#include` of dependencies
37+
- `WINRT_IMPORT_MODULE` — Defined by consumers who import modules. Makes namespace headers and base.h no-op (types come from module import)
38+
- `WINRT_EXPORT` — Empty in header mode, `export extern "C++"` in module mode. Defined in `winrt/base_macros.h`
39+
- `WINRT_IMPL_STD_EXPORT` — Empty in header mode, `extern "C++"` (without export) in module mode. Used for `namespace std` specializations
40+
41+
### Generated Header Structure
42+
Each namespace produces four header files:
43+
- `impl/<ns>.0.h` — Forward declarations, ABIs, GUIDs, categories
44+
- `impl/<ns>.1.h` — Interface definitions
45+
- `impl/<ns>.2.h` — Delegates, structs, class implementations
46+
- `<ns>.h` — Public API surface (consume definitions, class wrappers, operators)
47+
48+
### Dependency Collection
49+
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.
50+
51+
## Common Gotchas
52+
53+
- Module IFCs are NOT compatible across toolset versions — always clean rebuild when switching
54+
- PCH and modules can coexist but PCH should NOT include winrt headers when using modules
55+
- `/ifcSearchDir` works for the module dependency scanner to find IFCs, but cross-component modules may need explicit `/reference "name=path.ifc"` flags
56+
- `import std;` requires `BuildStlModules=true`
57+
- `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
58+
- 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
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# C++/WinRT Modules — Agent Instructions
2+
3+
## Module Architecture (v2 — Per-Namespace)
4+
5+
Each WinRT namespace gets its own C++20 named module (`winrt.<Namespace>`). Base infrastructure is in `winrt_base` and `winrt_numerics`.
6+
7+
### Code Generator Flow
8+
9+
1. `-modules` flag enables .ixx generation in cppwinrt.exe
10+
2. `-module_include`/`-module_exclude` filter which namespaces get modules
11+
3. Headers are generated with dependency tracking (deps_ptr parameter)
12+
4. Tarjan's SCC algorithm detects cyclic namespace groups
13+
5. Standalone namespaces get individual .ixx; cyclic groups get consolidated SCC owner + re-export stubs
14+
15+
### MSBuild Flow
16+
17+
1. `CppWinRTBuildModule=true` adds `-modules` to cppwinrt.exe invocations
18+
2. `CppWinRTAddModuleInterfaces` discovers `$(GeneratedFilesDir)winrt\*.ixx` and adds to ClCompile
19+
3. `CppWinRTConsumeModule` metadata on ProjectReference controls per-reference IFC sharing
20+
4. `CppWinRTResolveModuleReferences` calls `CppWinRTGetModuleOutputs` on tagged references
21+
5. Platform projection suppresses `-modules` when consuming pre-built IFCs
22+
23+
### Critical Invariants
24+
25+
- Module guards are unconditional in codegen — `-modules` controls .ixx generation and component codegen (module.g.cpp, stub .cpp)
26+
- SCC owner is alphabetically first namespace in the cycle
27+
- All .ixx filenames use `winrt` prefix: `winrt.Windows.Foundation.ixx`, `winrt_base.ixx`
28+
- Shared macros live in `strings/base_module.h` → generates `winrt/macros.h`. `base_macros.h` includes it via `#include "winrt/macros.h"`
29+
30+
### Testing Changes
31+
32+
After modifying cppwinrt.exe code:
33+
1. Rebuild cppwinrt.exe: `msbuild cppwinrt\cppwinrt.vcxproj /p:Configuration=Release /p:Platform=x64`
34+
2. Run standalone test: build `test_cpp20_module` in main solution
35+
3. Run NuGet tests: `msbuild test\nuget\NuGetTest.sln /p:Configuration=Release /p:Platform=x64`
36+
37+
After modifying targets:
38+
1. Clean NuGet test obj dirs
39+
2. Build with `/v:normal` and check "Module providers:" diagnostic messages
40+
3. Inspect `.rsp` files in `obj/` to verify correct `-modules` flag placement

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,15 @@ jobs:
379379
$target_platform = "${{ matrix.arch }}"
380380
& "_build\$target_platform\$target_configuration\cppwinrt.exe" -in local -out _build\$target_platform\$target_configuration -verbose
381381
382+
- name: Remove module test projects on v143
383+
if: matrix.toolchain.platform_toolset == 'v143'
384+
run: |
385+
# Module test projects require v145 toolset
386+
mv test\nuget\NugetTest.sln test\nuget\NugetTest.sln.orig
387+
Get-Content test\nuget\NugetTest.sln.orig |
388+
Where-Object { -not ($_ -match 'TestModule') } |
389+
Set-Content test\nuget\NugetTest.sln
390+
382391
- name: Run nuget test
383392
run: |
384393
cmd /c "$env:VSDevCmd" "&" msbuild /m /clp:ForceConsoleColor "$env:msbuild_config_props" test\nuget\NugetTest.sln

cppwinrt.sln

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_nocoro", "test\test_no
124124
{D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4}
125125
EndProjectSection
126126
EndProject
127+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_cpp20_module", "test\test_cpp20_module\test_cpp20_module.vcxproj", "{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}"
128+
ProjectSection(ProjectDependencies) = postProject
129+
{D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4}
130+
EndProjectSection
131+
EndProject
127132
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D15C8430-A7CD-4616-BD84-243B26A9F1C2}"
128133
ProjectSection(SolutionItems) = preProject
129134
build_nuget.cmd = build_nuget.cmd
@@ -411,6 +416,18 @@ Global
411416
{9E392830-805A-4AAF-932D-C493143EFACA}.Release|x64.Build.0 = Release|x64
412417
{9E392830-805A-4AAF-932D-C493143EFACA}.Release|x86.ActiveCfg = Release|Win32
413418
{9E392830-805A-4AAF-932D-C493143EFACA}.Release|x86.Build.0 = Release|Win32
419+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|ARM64.ActiveCfg = Debug|ARM64
420+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|ARM64.Build.0 = Debug|ARM64
421+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x64.ActiveCfg = Debug|x64
422+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x64.Build.0 = Debug|x64
423+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x86.ActiveCfg = Debug|Win32
424+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x86.Build.0 = Debug|Win32
425+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|ARM64.ActiveCfg = Release|ARM64
426+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|ARM64.Build.0 = Release|ARM64
427+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x64.ActiveCfg = Release|x64
428+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x64.Build.0 = Release|x64
429+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x86.ActiveCfg = Release|Win32
430+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x86.Build.0 = Release|Win32
414431
EndGlobalSection
415432
GlobalSection(SolutionProperties) = preSolution
416433
HideSolutionNode = FALSE
@@ -435,6 +452,7 @@ Global
435452
{5FF6CD6C-515A-4D55-97B6-62AD9BCB77EA} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0}
436453
{D4C8F881-84D5-4A7B-8BDE-AB4E34A05374} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0}
437454
{9E392830-805A-4AAF-932D-C493143EFACA} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0}
455+
{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0}
438456
EndGlobalSection
439457
GlobalSection(ExtensibilityGlobals) = postSolution
440458
SolutionGuid = {2783B8FD-EA3B-4D6B-9F81-662D289E02AA}

cppwinrt/code_writers.h

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ namespace cppwinrt
55
struct finish_with
66
{
77
writer& w;
8-
void (*finisher)(writer&);
8+
std::function<void(writer&)> finisher;
99

10-
finish_with(writer& w, void (*finisher)(writer&)) : w(w), finisher(finisher) {}
10+
finish_with(writer& w, std::function<void(writer&)> finisher) : w(w), finisher(std::move(finisher)) {}
1111
finish_with(finish_with const&)= delete;
1212
void operator=(finish_with const&) = delete;
1313

@@ -35,6 +35,35 @@ namespace cppwinrt
3535
}
3636
}
3737

38+
static void write_endif(writer& w, std::string_view macro = {})
39+
{
40+
if (macro.empty())
41+
{
42+
w.write("#endif\n");
43+
}
44+
else
45+
{
46+
w.write("#endif // %\n", macro);
47+
}
48+
}
49+
50+
// When modules are enabled, wraps a block of #include directives in
51+
// #ifndef WINRT_IMPL_BUILD_MODULE ... #endif so that in module builds (where
52+
// WINRT_IMPL_BUILD_MODULE is defined in the global module fragment), textual
53+
// includes are suppressed — dependencies come via import instead.
54+
[[nodiscard]] static finish_with wrap_module_aware_includes_guard(writer& w, bool modules_enabled)
55+
{
56+
if (modules_enabled)
57+
{
58+
w.write("#ifndef WINRT_IMPL_BUILD_MODULE\n");
59+
return { w, [](writer& w) { write_endif(w, "WINRT_IMPL_BUILD_MODULE"); } };
60+
}
61+
else
62+
{
63+
return { w, write_nothing };
64+
}
65+
}
66+
3867
static void write_version_assert(writer& w)
3968
{
4069
w.write_root_include("base");
@@ -52,14 +81,6 @@ namespace cppwinrt
5281
w.write(format);
5382
}
5483

55-
static void write_endif(writer& w)
56-
{
57-
auto format = R"(#endif
58-
)";
59-
60-
w.write(format);
61-
}
62-
6384
static void write_close_file_guard(writer& w)
6485
{
6586
write_endif(w);
@@ -105,7 +126,7 @@ namespace cppwinrt
105126

106127
w.write(format);
107128

108-
return { w, write_endif };
129+
return { w, [](writer& w) { write_endif(w, "WINRT_LEAN_AND_MEAN"); } };
109130
}
110131
else
111132
{
@@ -120,7 +141,17 @@ namespace cppwinrt
120141

121142
w.write(format, macro);
122143

123-
return { w, write_endif };
144+
return { w, [macro = std::string(macro)](writer& w) { write_endif(w, macro); } };
145+
}
146+
147+
[[nodiscard]] static finish_with wrap_ifndef(writer& w, std::string_view macro)
148+
{
149+
auto format = R"(#ifndef %
150+
)";
151+
152+
w.write(format, macro);
153+
154+
return { w, [macro = std::string(macro)](writer& w) { write_endif(w, macro); } };
124155
}
125156

126157
static void write_parent_depends(writer& w, cache const& c, std::string_view const& type_namespace)
@@ -166,7 +197,7 @@ namespace cppwinrt
166197

167198
[[nodiscard]] static finish_with wrap_impl_namespace(writer& w)
168199
{
169-
auto format = R"(namespace winrt::impl
200+
auto format = R"(WINRT_EXPORT namespace winrt::impl
170201
{
171202
)";
172203

cppwinrt/component_writers.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,10 @@ namespace cppwinrt
136136

137137
static void write_module_g_cpp(writer& w, std::vector<TypeDef> const& classes)
138138
{
139-
w.write_root_include("base");
139+
if (!settings.modules)
140+
{
141+
w.write_root_include("base");
142+
}
140143
auto format = R"(%
141144
bool __stdcall %_can_unload_now() noexcept
142145
{

0 commit comments

Comments
 (0)