diff --git a/.github/instructions/cppwinrt.instructions.md b/.github/instructions/cppwinrt.instructions.md new file mode 100644 index 000000000..ba5d66212 --- /dev/null +++ b/.github/instructions/cppwinrt.instructions.md @@ -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 `v145` 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/.0.h` — Forward declarations, ABIs, GUIDs, categories +- `impl/.1.h` — Interface definitions +- `impl/.2.h` — Delegates, structs, class implementations +- `.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 diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md new file mode 100644 index 000000000..3deee2955 --- /dev/null +++ b/.github/instructions/modules.instructions.md @@ -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.`). 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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d660400a0..6c2a04353 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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' + run: | + # Module test projects require v145 toolset + mv test\nuget\NugetTest.sln test\nuget\NugetTest.sln.orig + 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 diff --git a/cppwinrt.sln b/cppwinrt.sln index 3bcfb33bc..e48448154 100644 --- a/cppwinrt.sln +++ b/cppwinrt.sln @@ -124,6 +124,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_nocoro", "test\test_no {D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_cpp20_module", "test\test_cpp20_module\test_cpp20_module.vcxproj", "{B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}" + ProjectSection(ProjectDependencies) = postProject + {D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4} + EndProjectSection +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D15C8430-A7CD-4616-BD84-243B26A9F1C2}" ProjectSection(SolutionItems) = preProject build_nuget.cmd = build_nuget.cmd @@ -411,6 +416,14 @@ Global {9E392830-805A-4AAF-932D-C493143EFACA}.Release|x64.Build.0 = Release|x64 {9E392830-805A-4AAF-932D-C493143EFACA}.Release|x86.ActiveCfg = Release|Win32 {9E392830-805A-4AAF-932D-C493143EFACA}.Release|x86.Build.0 = Release|Win32 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|ARM64.ActiveCfg = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x64.ActiveCfg = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x64.Build.0 = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Debug|x86.ActiveCfg = Debug|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|ARM64.ActiveCfg = Release|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x64.ActiveCfg = Release|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x64.Build.0 = Release|x64 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -435,6 +448,7 @@ Global {5FF6CD6C-515A-4D55-97B6-62AD9BCB77EA} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} {D4C8F881-84D5-4A7B-8BDE-AB4E34A05374} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} {9E392830-805A-4AAF-932D-C493143EFACA} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2783B8FD-EA3B-4D6B-9F81-662D289E02AA} diff --git a/cppwinrt/code_writers.h b/cppwinrt/code_writers.h index fc5081bef..e0741031e 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -5,9 +5,9 @@ namespace cppwinrt struct finish_with { writer& w; - void (*finisher)(writer&); + std::function finisher; - finish_with(writer& w, void (*finisher)(writer&)) : w(w), finisher(finisher) {} + finish_with(writer& w, std::function finisher) : w(w), finisher(std::move(finisher)) {} finish_with(finish_with const&)= delete; void operator=(finish_with const&) = delete; @@ -35,6 +35,35 @@ namespace cppwinrt } } + static void write_endif(writer& w, std::string_view macro = {}) + { + if (macro.empty()) + { + w.write("#endif\n"); + } + else + { + w.write("#endif // %\n", macro); + } + } + + // When modules are enabled, wraps a block of #include directives in + // #ifndef WINRT_IMPL_BUILD_MODULE ... #endif so that in module builds (where + // WINRT_IMPL_BUILD_MODULE is defined in the global module fragment), textual + // includes are suppressed — dependencies come via import instead. + [[nodiscard]] static finish_with wrap_module_aware_includes_guard(writer& w, bool modules_enabled) + { + if (modules_enabled) + { + w.write("#ifndef WINRT_IMPL_BUILD_MODULE\n"); + return { w, [](writer& w) { write_endif(w, "WINRT_IMPL_BUILD_MODULE"); } }; + } + else + { + return { w, write_nothing }; + } + } + static void write_version_assert(writer& w) { w.write_root_include("base"); @@ -52,14 +81,6 @@ namespace cppwinrt w.write(format); } - static void write_endif(writer& w) - { - auto format = R"(#endif -)"; - - w.write(format); - } - static void write_close_file_guard(writer& w) { write_endif(w); @@ -105,7 +126,7 @@ namespace cppwinrt w.write(format); - return { w, write_endif }; + return { w, [](writer& w) { write_endif(w, "WINRT_LEAN_AND_MEAN"); } }; } else { @@ -120,7 +141,7 @@ namespace cppwinrt w.write(format, macro); - return { w, write_endif }; + return { w, [macro = std::string(macro)](writer& w) { write_endif(w, macro); } }; } static void write_parent_depends(writer& w, cache const& c, std::string_view const& type_namespace) @@ -166,7 +187,7 @@ namespace cppwinrt [[nodiscard]] static finish_with wrap_impl_namespace(writer& w) { - auto format = R"(namespace winrt::impl + auto format = R"(WINRT_EXPORT namespace winrt::impl { )"; diff --git a/cppwinrt/component_writers.h b/cppwinrt/component_writers.h index 3966cad6c..af5626d14 100644 --- a/cppwinrt/component_writers.h +++ b/cppwinrt/component_writers.h @@ -136,7 +136,10 @@ namespace cppwinrt static void write_module_g_cpp(writer& w, std::vector const& classes) { - w.write_root_include("base"); + if (!settings.modules) + { + w.write_root_include("base"); + } auto format = R"(% bool __stdcall %_can_unload_now() noexcept { diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index ed9386b4e..50774a20d 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -9,9 +9,14 @@ namespace cppwinrt w.write(strings::base_version_odr, CPPWINRT_VERSION_STRING); { auto wrap_file_guard = wrap_open_file_guard(w, "BASE"); + w.write("#ifndef WINRT_IMPORT_MODULE\n"); - w.write(strings::base_includes); - w.write(strings::base_macros); + { + auto wrap_includes = wrap_module_aware_includes_guard(w, true); + w.write(strings::base_includes); + } + w.write("#include \"winrt/base_macros.h\"\n"); + w.write(strings::base_source_location); w.write(strings::base_types); w.write(strings::base_extern); w.write(strings::base_meta); @@ -42,6 +47,8 @@ namespace cppwinrt w.write(strings::base_coroutine_threadpool); w.write(strings::base_natvis); w.write(strings::base_version); + + w.write("#endif // WINRT_IMPORT_MODULE\n"); } w.flush_to_file(settings.output_folder + "winrt/base.h"); } @@ -65,7 +72,15 @@ namespace cppwinrt w.flush_to_file(settings.output_folder + "winrt/fast_forward.h"); } - static void write_namespace_0_h(std::string_view const& ns, cache::namespace_members const& members) + static void collect_writer_deps(writer const& w, std::set& out) + { + for (auto&& [dep_ns, _] : w.depends) + { + out.insert(std::string(dep_ns)); + } + } + + static void write_namespace_0_h(std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -118,10 +133,11 @@ namespace cppwinrt w.write_each(depends.second); } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header('0'); } - static void write_namespace_1_h(std::string_view const& ns, cache::namespace_members const& members) + static void write_namespace_1_h(std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -137,16 +153,20 @@ namespace cppwinrt write_preamble(w); write_open_file_guard(w, ns, '1'); - for (auto&& depends : w.depends) { - w.write_depends(depends.first, '0'); - } + auto wrap_includes = wrap_module_aware_includes_guard(w, true); + for (auto&& depends : w.depends) + { + w.write_depends(depends.first, '0'); + } - w.write_depends(w.type_namespace, '0'); + w.write_depends(w.type_namespace, '0'); + } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header('1'); } - static void write_namespace_2_h(std::string_view const& ns, cache::namespace_members const& members) + static void write_namespace_2_h(std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -167,16 +187,20 @@ namespace cppwinrt char const impl = promote ? '2' : '1'; - for (auto&& depends : w.depends) { - w.write_depends(depends.first, impl); - } + auto wrap_includes = wrap_module_aware_includes_guard(w, true); + for (auto&& depends : w.depends) + { + w.write_depends(depends.first, impl); + } - w.write_depends(w.type_namespace, '1'); + w.write_depends(w.type_namespace, '1'); + } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header('2'); } - static void write_namespace_h(cache const& c, std::string_view const& ns, cache::namespace_members const& members) + static void write_namespace_h(cache const& c, std::string_view const& ns, cache::namespace_members const& members, std::set* out_deps = nullptr) { writer w; w.type_namespace = ns; @@ -216,18 +240,24 @@ namespace cppwinrt write_namespace_special(w, ns); write_close_file_guard(w); + w.write("#endif\n"); // WINRT_IMPORT_MODULE w.swap(); write_preamble(w); write_open_file_guard(w, ns); - write_version_assert(w); - write_parent_depends(w, c, ns); - - for (auto&& depends : w.depends) + w.write("#ifndef WINRT_IMPORT_MODULE\n\n"); { - w.write_depends(depends.first, '2'); - } + auto wrap_includes = wrap_module_aware_includes_guard(w, true); + write_version_assert(w); + write_parent_depends(w, c, ns); + + for (auto&& depends : w.depends) + { + w.write_depends(depends.first, '2'); + } - w.write_depends(w.type_namespace, '2'); + w.write_depends(w.type_namespace, '2'); + } + if (out_deps) collect_writer_deps(w, *out_deps); w.save_header(); } @@ -236,6 +266,28 @@ namespace cppwinrt writer w; write_preamble(w); write_pch(w); + + if (settings.modules) + { + // In module builds, import std and winrt_base instead of #include "winrt/base.h". + // std is needed for std::wstring_view, std::equal, std::int32_t used in + // the activation factory lookup code. + w.write("\nimport std;\n"); + w.write("import winrt_base;\n"); + + // Collect all unique namespaces from the component classes + std::set namespaces; + for (auto&& type : classes) + { + namespaces.insert(std::string(type.TypeNamespace())); + } + for (auto&& ns : namespaces) + { + w.write("import winrt.%;\n", ns); + } + w.write("\n"); + } + write_module_g_cpp(w, classes); w.flush_to_file(settings.output_folder + "module.g.cpp"); } @@ -250,11 +302,21 @@ namespace cppwinrt write_preamble(w); write_include_guard(w); + w.write("#ifdef WINRT_IMPORT_MODULE\n"); + w.write("#include \"winrt/base_macros.h\"\n"); + for (auto&& depends : w.depends) + { + w.write("import winrt.%;\n", depends.first); + } + w.write("#else\n"); + for (auto&& depends : w.depends) { w.write_depends(depends.first); } + w.write("#endif // WINRT_IMPORT_MODULE\n"); + auto filename = settings.output_folder + get_generated_component_filename(type) + ".g.h"; path folder = filename; folder.remove_filename(); @@ -316,7 +378,246 @@ namespace cppwinrt writer w; write_pch(w); + + if (settings.modules) + { + // The .g.h handles its own imports, but the implementation .h + // needs the types available in scope, so we import them here. + writer dep_scanner; + dep_scanner.add_depends(type); + write_component_g_h(dep_scanner, type); + + w.write("\n#define WINRT_IMPORT_MODULE\n"); + for (auto&& depends : dep_scanner.depends) + { + w.write("import winrt.%;\n", depends.first); + } + w.write("\n"); + } + write_component_cpp(w, type); w.flush_to_file(path); } + + // --- Per-namespace C++20 module interface unit (.ixx) writers --- + + // Emits the common global module fragment used by all generated .ixx files. + // Defines WINRT_IMPL_BUILD_MODULE so generated headers switch WINRT_EXPORT + // to 'export extern "C++"' and suppress textual #includes of dependencies + // (dependencies arrive via module imports instead). + // Includes minimal headers needed for macros, intrinsics, and debug assertions. + static void write_module_preamble(writer& w) + { + write_preamble(w); + w.write(strings::base_module_ixx_preamble); + } + + // Emits $(out)/winrt/base_macros.h + // This header provides the core macros shared between header and module builds. + // In header builds, base.h includes base_macros.h inline (via the prebuild-embedded string). + // In module builds, each .ixx file includes this in its global module fragment. + static void write_macros_h() + { + writer w; + write_preamble(w); + w.write(strings::base_macros); + w.flush_to_file(settings.output_folder + "winrt/base_macros.h"); + } + + static void write_base_ixx() + { + writer w; + write_module_preamble(w); + w.write(strings::base_module_base_ixx); + w.flush_to_file(settings.output_folder + "winrt/winrt_base.ixx"); + } + + static void write_numerics_ixx() + { + writer w; + write_module_preamble(w); + w.write(strings::base_module_numerics_ixx); + w.flush_to_file(settings.output_folder + "winrt/winrt_numerics.ixx"); + } + + // Emits a per-namespace module interface unit for namespaces that are NOT + // part of a dependency cycle (standalone module). + // Output: $(out)/winrt/winrt..ixx (export module winrt.;) + // + // The generated .ixx: + // 1. Starts with the global module fragment (WINRT_IMPL_BUILD_MODULE, minimal includes) + // 2. Declares 'export module winrt.;' + // 3. Imports std and re-exports winrt_base + // 4. Imports each dependent namespace module (computed from type references in headers) + // 5. Includes the impl headers (*.0.h, *.1.h, *.2.h) and public header (.h) + // in the module purview, where WINRT_EXPORT causes declarations to be exported + static void write_namespace_ixx( + std::string_view const& ns, + std::set const& deps, + std::set const& module_namespaces) + { + writer w; + write_module_preamble(w); + + // Module declaration + w.write("export module winrt.%;\n\n", ns); + + // Document dependencies + w.write("// Module dependencies:\n"); + w.write("// - std\n"); + w.write("// - winrt_base (re-exported)\n"); + if (deps.empty()) + { + w.write("// - (no additional namespace imports)\n"); + } + else + { + for (auto& dep : deps) + { + if (module_namespaces.count(dep) || module_namespaces.empty()) + { + w.write("// - winrt.%\n", dep); + } + } + } + w.write("\n"); + + // Import std and base + w.write("import std;\n"); + w.write("export import winrt_base;\n"); + + // Import dependency namespace modules + for (auto& dep : deps) + { + if (module_namespaces.count(dep) || module_namespaces.empty()) + { + w.write("import winrt.%;\n", dep); + } + } + + w.write("\n"); + + // Include namespace headers in module purview + w.write("#include \"winrt/impl/%.0.h\"\n", ns); + w.write("#include \"winrt/impl/%.1.h\"\n", ns); + w.write("#include \"winrt/impl/%.2.h\"\n", ns); + w.write("#include \"winrt/%.h\"\n", ns); + + w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(ns) + ".ixx"); + } + + // Emits the SCC (Strongly Connected Component) owner module interface unit. + // When multiple namespaces form a dependency cycle, they cannot each have their + // own independent module (circular imports are illegal in C++20 modules). + // Instead, one namespace is chosen as the "owner" (alphabetically first in the SCC), + // and ALL cyclic namespaces' declarations are consolidated into this single module. + // The other namespaces in the SCC get thin re-export stubs (see write_namespace_reexport_ixx). + // + // Output: $(out)/winrt/winrt..ixx (export module winrt.;) + // + // The owner module: + // 1. Imports external dependencies (deps outside the SCC) + // 2. Forward-declares all projected types for ALL SCC namespaces before any + // impl headers — this breaks the type reference cycles + // 3. Includes impl headers in stable phase order: all *.0.h, then all *.1.h, + // then all *.2.h, then all public headers — preserving the original header + // layering while keeping SCC compilation deterministic + static void write_namespace_scc_owner_ixx( + cache const& c, + std::string_view const& owner, + std::vector const& scc_members, + std::set const& external_deps, + std::set const& module_namespaces) + { + writer w; + write_module_preamble(w); + + // Module declaration (owner namespace) + w.write("// This module is an SCC owner (cycle breaker). The following namespaces\n"); + w.write("// form a dependency cycle and are consolidated into this single module:\n"); + for (auto& ns : scc_members) + { + w.write("// - %\n", ns); + } + w.write("// Other SCC namespaces are emitted as re-export stubs.\n\n"); + w.write("export module winrt.%;\n\n", owner); + + // Import std and base + w.write("import std;\n"); + w.write("export import winrt_base;\n"); + + // Import external dependency modules (outside the SCC) + for (auto& dep : external_deps) + { + if (module_namespaces.count(dep) || module_namespaces.empty()) + { + w.write("import winrt.%;\n", dep); + } + } + + w.write("\n"); + + // Forward declarations for all projected types in this SCC. + // This is required because SCC members have cyclic type references, + // and generated headers suppress dependent #includes when WINRT_IMPL_BUILD_MODULE + // is defined. Forward declarations provide the names needed before definitions. + for (auto& ns : scc_members) + { + auto found = c.namespaces().find(ns); + if (found == c.namespaces().end()) + { + continue; + } + auto& members = found->second; + + auto wrap_type = wrap_type_namespace(w, ns); + w.write_each(members.enums); + w.write_each(members.interfaces); + w.write_each(members.classes); + w.write_each(members.structs); + w.write_each(members.delegates); + w.write_each(members.contracts); + } + + // Include all SCC members' headers in stable phase order. + // All *.0.h (forward decls + ABIs), then all *.1.h (interfaces), + // then all *.2.h (delegates/structs/classes), then all public headers. + // This preserves the original header layering while keeping compilation deterministic. + for (auto& ns : scc_members) + { + w.write("#include \"winrt/impl/%.0.h\"\n", ns); + } + for (auto& ns : scc_members) + { + w.write("#include \"winrt/impl/%.1.h\"\n", ns); + } + for (auto& ns : scc_members) + { + w.write("#include \"winrt/impl/%.2.h\"\n", ns); + } + for (auto& ns : scc_members) + { + w.write("#include \"winrt/%.h\"\n", ns); + } + + w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(owner) + ".ixx"); + } + + // Emits a thin re-export stub module for SCC non-owner namespaces. + // This allows 'import winrt.;' to work even though the actual declarations + // live in the SCC owner module. The stub simply re-exports the owner. + // Output: $(out)/winrt/winrt..ixx (export module winrt.; export import winrt.;) + static void write_namespace_reexport_ixx( + std::string_view const& ns, + std::string_view const& owner) + { + writer w; + write_preamble(w); + w.write("\n// NOTE: This module does not define declarations of its own.\n"); + w.write("// It re-exports all declarations from the 'winrt.%' module. This is used to break cycles in the\n", owner); + w.write("// WinRT namespace module dependency graph (SCC owner consolidation).\n\n"); + w.write("export module winrt.%;\n", ns); + w.write("export import winrt.%;\n", owner); + w.flush_to_file(settings.output_folder + "winrt/winrt." + std::string(ns) + ".ixx"); + } } diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 70a55b076..1ee84c418 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include +#include #include "strings.h" #include "settings.h" #include "type_writers.h" @@ -39,6 +40,9 @@ namespace cppwinrt { "fastabi", 0, 0 }, // Enable support for the Fast ABI { "ignore_velocity", 0, 0 }, // Ignore feature staging metadata and always include implementations { "synchronous", 0, 0 }, // Instructs cppwinrt to run on a single thread to avoid file system issues in batch builds + { "modules", 0, 0, {}, "Generate per-namespace C++20 module interface units (.ixx)" }, + { "module_include", 0, option::no_max, "", "Filter which namespaces are included in module .ixx generation" }, + { "module_exclude", 0, option::no_max, "", "Filter which namespaces are excluded from module .ixx generation" }, }; static void print_usage(writer& w) @@ -85,6 +89,7 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder { settings.verbose = args.exists("verbose"); settings.fastabi = args.exists("fastabi"); + settings.modules = args.exists("modules"); settings.input = args.files("input", database::is_database); settings.reference = args.files("reference", database::is_database); @@ -92,6 +97,15 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder settings.component = args.exists("component"); settings.base = args.exists("base"); + for (auto&& ns : args.values("module_include")) + { + settings.module_include.insert(ns); + } + for (auto&& ns : args.values("module_exclude")) + { + settings.module_exclude.insert(ns); + } + settings.license = args.exists("license"); settings.brackets = args.exists("brackets"); @@ -199,6 +213,13 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder static void build_filters(cache const& c) { + // Build module_filter from -module_include / -module_exclude args. + // This controls which namespaces get .ixx files without affecting header generation. + if (!settings.module_include.empty() || !settings.module_exclude.empty()) + { + settings.module_filter = { settings.module_include, settings.module_exclude }; + } + if (settings.reference.empty()) { return; @@ -281,6 +302,79 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder c.remove_type("Windows.Foundation.Numerics", "Vector4"); } + // Tarjan's algorithm for finding strongly connected components in the + // namespace dependency graph. Namespaces in an SCC have cyclic deps and + // must be combined into a single module. + static std::vector> find_sccs( + std::map, std::less<>> const& graph) + { + struct context + { + std::map index_of; + std::map lowlink; + std::map on_stack; + std::vector stack; + int next_index = 0; + std::vector> result; + + void strongconnect(std::string const& v, + std::map, std::less<>> const& g) + { + index_of[v] = next_index; + lowlink[v] = next_index; + next_index++; + stack.push_back(v); + on_stack[v] = true; + + auto it = g.find(v); + if (it != g.end()) + { + for (auto& w : it->second) + { + if (g.find(w) == g.end()) + { + continue; // dep not in graph (not a projected namespace) + } + + if (index_of.find(w) == index_of.end()) + { + strongconnect(w, g); + lowlink[v] = (std::min)(lowlink[v], lowlink[w]); + } + else if (on_stack[w]) + { + lowlink[v] = (std::min)(lowlink[v], index_of[w]); + } + } + } + + if (lowlink[v] == index_of[v]) + { + std::vector scc; + std::string w; + do + { + w = stack.back(); + stack.pop_back(); + on_stack[w] = false; + scc.push_back(std::move(w)); + } while (scc.back() != v); + result.push_back(std::move(scc)); + } + } + }; + + context ctx; + for (auto& [node, _] : graph) + { + if (ctx.index_of.find(node) == ctx.index_of.end()) + { + ctx.strongconnect(node, graph); + } + } + return std::move(ctx.result); + } + static int run(int const argc, char** argv) { int result{}; @@ -342,11 +436,30 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder w.flush_to_console(); task_group group; group.synchronous(args.exists("synchronous")); - writer ixx; - write_preamble(ixx); - ixx.write("module;\n"); - ixx.write(strings::base_includes); - ixx.write("\nexport module winrt;\n#define WINRT_EXPORT export\n\n"); + + // Dependency collection for per-namespace modules (v2) + std::map, std::less<>> ns_deps_map; + std::set projected_namespaces; + std::mutex ns_deps_mutex; + + // First pass: determine which namespaces will be in the module. + // This includes namespaces from this invocation AND those from other invocations + // (e.g., platform namespaces when building a component). The module_filter + // tells us which namespaces have modules across all invocations. + if (settings.modules) + { + for (auto&& [ns, members] : c.namespaces()) + { + if (!has_projected_types(members)) + { + continue; + } + if (settings.module_filter.empty() || settings.module_filter.includes(members)) + { + projected_namespaces.insert(std::string(ns)); + } + } + } for (auto&&[ns, members] : c.namespaces()) { @@ -355,21 +468,29 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder continue; } - ixx.write("#include \"winrt/%.h\"\n", ns); - group.add([&, &ns = ns, &members = members] { - write_namespace_0_h(ns, members); - write_namespace_1_h(ns, members); - write_namespace_2_h(ns, members); - write_namespace_h(c, ns, members); + bool in_module = projected_namespaces.count(std::string(ns)) > 0; + std::set ns_deps; + auto* deps_ptr = (settings.modules && in_module) ? &ns_deps : nullptr; + + write_namespace_0_h(ns, members, deps_ptr); + write_namespace_1_h(ns, members, deps_ptr); + write_namespace_2_h(ns, members, deps_ptr); + write_namespace_h(c, ns, members, deps_ptr); + + if (settings.modules && in_module) + { + std::lock_guard lock(ns_deps_mutex); + ns_deps_map[std::string(ns)] = std::move(ns_deps); + } }); } if (settings.base) { write_base_h(); - ixx.flush_to_file(settings.output_folder + "winrt/winrt.ixx"); + write_macros_h(); } if (settings.component) @@ -404,6 +525,64 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder group.get(); + // Generate per-namespace module interface files (.ixx) + // + // Each projected namespace gets its own C++20 named module (winrt.). + // Namespaces that form dependency cycles are detected using Tarjan's SCC algorithm + // and consolidated: one namespace "owns" the SCC module (containing all declarations), + // while others get thin re-export stubs so 'import winrt.;' always works. + // + // Base infrastructure modules (winrt_base, winrt_numerics) are only generated + // for platform projection builds (-base flag). + if (settings.modules) + { + if (settings.base) + { + write_numerics_ixx(); + write_base_ixx(); + } + + // Tarjan's SCC algorithm for cyclic namespace dependencies + auto sccs = find_sccs(ns_deps_map); + + for (auto& scc : sccs) + { + if (scc.size() == 1) + { + // Standalone namespace module + auto& ns = scc[0]; + write_namespace_ixx(ns, ns_deps_map[ns], projected_namespaces); + } + else + { + // SCC: choose owner (alphabetically first), others re-export + std::sort(scc.begin(), scc.end()); + auto& owner = scc[0]; + + // External deps = union of all SCC members' deps, minus SCC members themselves + std::set external_deps; + std::set scc_set(scc.begin(), scc.end()); + for (auto& ns : scc) + { + for (auto& dep : ns_deps_map[ns]) + { + if (!scc_set.count(dep)) + { + external_deps.insert(dep); + } + } + } + + write_namespace_scc_owner_ixx(c, owner, scc, external_deps, projected_namespaces); + + for (size_t i = 1; i < scc.size(); ++i) + { + write_namespace_reexport_ixx(scc[i], owner); + } + } + } + } + if (settings.verbose) { w.write(" time: %ms\n", get_elapsed_time(start)); diff --git a/cppwinrt/settings.h b/cppwinrt/settings.h index e07df4ea2..110e64917 100644 --- a/cppwinrt/settings.h +++ b/cppwinrt/settings.h @@ -31,6 +31,12 @@ namespace cppwinrt bool fastabi{}; std::map fastabi_cache; + + bool modules{}; // Generate per-namespace C++20 module interface units (.ixx) + + std::set module_include; + std::set module_exclude; + winmd::reader::filter module_filter; }; extern settings_type settings; diff --git a/docs/modules-design.md b/docs/modules-design.md new file mode 100644 index 000000000..8327231a7 --- /dev/null +++ b/docs/modules-design.md @@ -0,0 +1,190 @@ +# C++/WinRT Per-Namespace Modules: Design & Internals + +This document describes the design and implementation of per-namespace C++20 module support in C++/WinRT. It is intended for cppwinrt maintainers and contributors. + +## Architecture Overview + +The module system generates one C++20 named module per WinRT namespace. Each module encapsulates the same content as the traditional header files but exports declarations via `WINRT_EXPORT` in module purview. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ winrt_numerics.ixx ── export module winrt_numerics; │ +│ winrt_base.ixx ── export module winrt_base; │ +│ export import winrt_numerics; │ +│ winrt.Windows.Foundation.ixx │ +│ ── export module winrt.Windows.Foundation; │ +│ import std; export import winrt_base; │ +│ import winrt.Windows.Foundation.Collections; │ +│ #include "winrt/impl/Windows.Foundation.0.h" │ +│ ... │ +│ #include "winrt/Windows.Foundation.h" │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Key Design Decisions + +### Unconditional Guards + +Module guards (`WINRT_IMPL_BUILD_MODULE`, `WINRT_IMPORT_MODULE`) are emitted unconditionally in generated projection headers — they are always present regardless of whether `-modules` was passed to cppwinrt.exe. The `-modules` flag controls `.ixx` generation and whether generated component files (`module.g.cpp`, stub `.cpp`) use module imports. This means: + +- Projection headers generated without `-modules` still work correctly when later compiled inside a module interface unit +- No regeneration of projection headers needed when switching between header and module consumption + +### WINRT_EXPORT Macro + +`WINRT_EXPORT` is defined in `base_macros.h` (generated as `winrt/base_macros.h`): +- When `WINRT_IMPL_BUILD_MODULE` is defined (inside `.ixx` compilation): `export extern "C++"` +- Otherwise (header mode): empty + +All `namespace winrt::impl` and `namespace std` blocks use `WINRT_EXPORT` so they export correctly from modules. The `extern "C++"` wrapping enables include-before-import compatibility (same technique as MSVC STL). + +### Per-Namespace vs Monolithic + +Unlike the v1 approach (single `import winrt;`), v2 generates one module per namespace. This provides: + +- **Finer granularity**: Import only what you need +- **Better parallelism**: Independent modules compile in parallel +- **Component module support**: Component namespaces get their own modules + +The trade-off is handling dependency cycles between namespaces (see SCC below). + +This is enforced via the MSBuild `CppWinRTConsumeModule` metadata on ProjectReference — it only points at the platform module builder, not at component projects. + +## Code Generator Pipeline + +### Entry Point: `main.cpp` + +1. **Namespace enumeration**: First pass determines which namespaces are module-eligible using `module_filter` (from `-module_include`/`-module_exclude`) + +2. **Header generation**: Standard header generation with optional dependency collection. When `-modules` is active and a namespace is in the module, `write_namespace_*_h()` functions populate `ns_deps` sets via the `deps_ptr` parameter + +3. **Dependency graph construction**: After all headers are generated, `ns_deps_map` contains the full namespace dependency graph + +4. **SCC detection**: Tarjan's algorithm (`find_sccs()`) identifies strongly-connected components + +5. **Module generation**: For each SCC: + - Size 1: `write_namespace_ixx()` — standalone module + - Size > 1: `write_namespace_scc_owner_ixx()` for the owner (alphabetically first) + `write_namespace_reexport_ixx()` for others + +### CLI Options + +| Flag | Description | +|-|-| +| `-modules` | Enable `.ixx` generation | +| `-module_include ...` | Only generate modules for these namespace prefixes | +| `-module_exclude ...` | Skip these namespace prefixes | + +The `module_filter` is populated from these flags and checked against ALL cache namespaces (not just the projection filter). This is important for component builds where the platform namespaces are not being projected but their modules exist from a prior builder invocation. + +### Generated Files + +| File | When Generated | Purpose | +|-|-|-| +| `winrt/base_macros.h` | Always with `-base` | Macros for module builds (WINRT_EXPORT, etc.) | +| `winrt/winrt_base.ixx` | `-modules -base` | Core types module | +| `winrt/winrt_numerics.ixx` | `-modules -base` | Numerics module | +| `winrt/winrt..ixx` | `-modules` | Per-namespace module | + +## SCC (Strongly Connected Components) + +### The Problem + +WinRT namespaces have cyclic dependencies. For example: +- `Windows.Foundation` depends on `Windows.Foundation.Collections` (via `IVector`, `IMap`, etc.) +- `Windows.Foundation.Collections` depends on `Windows.Foundation` (via `IAsyncOperation`, `Uri`, etc.) + +C++20 modules cannot have circular imports. If module A imports module B, then module B cannot import module A. + +### The Solution: SCC Consolidation + +Tarjan's algorithm identifies groups of namespaces that form dependency cycles. These groups (SCCs) are consolidated: + +1. **Owner selection**: The alphabetically first namespace in the SCC becomes the "owner" +2. **Owner module**: Contains ALL declarations from ALL SCC namespaces. Forward-declares all types first, then includes headers in phase order (all `*.0.h`, then `*.1.h`, then `*.2.h`, then public headers) +3. **Re-export stubs**: Other SCC members get thin `.ixx` files that just re-export the owner module + +This means `import winrt.Windows.Foundation;` and `import winrt.Windows.Foundation.Collections;` both work — they resolve to the same underlying module. + +### Example Generated Files + +**Owner** (`winrt.Windows.Foundation.ixx`): +```cpp +module; +#define WINRT_IMPL_BUILD_MODULE +#include "winrt/base_macros.h" +// ... + +// This module is an SCC owner (cycle breaker). The following namespaces +// form a dependency cycle and are consolidated into this single module: +// - Windows.Foundation +// - Windows.Foundation.Collections +// Other SCC namespaces are emitted as re-export stubs. + +export module winrt.Windows.Foundation; + +import std; +export import winrt_base; + +// Forward declarations for all SCC namespaces... +// #include all impl headers in phase order... +``` + +**Re-export stub** (`winrt.Windows.Foundation.Collections.ixx`): +```cpp +// NOTE: This module does not define declarations of its own. +// It re-exports all declarations from the 'winrt.Windows.Foundation' module. +export module winrt.Windows.Foundation.Collections; +export import winrt.Windows.Foundation; +``` + +## MSBuild Integration + +### Targets Flow + +``` +CppWinRTResolveModuleReferences (resolves IFC paths from ProjectReference metadata) + ↓ +CppWinRTMakePlatformProjection (generates headers + .ixx for platform types) +CppWinRTMakeReferenceProjection (generates headers + .ixx for referenced WinMDs) +CppWinRTMakeComponentProjection (generates headers + .ixx for component types) + ↓ +CppWinRTAddModuleInterfaces (discovers .ixx files, adds to ClCompile items) + ↓ +FixupCLCompileOptions (MSVC module dependency scanner processes .ixx) + ↓ +ClCompile (compiles .ixx → .ifc + .obj) +``` + +### Key Properties + +- `CppWinRTBuildModule`: Enables `-modules` for all three projections (platform, reference, component), causing `.ixx` generation and compilation. +- `CppWinRTConsumeModule` (ProjectReference metadata): Per-reference opt-in for IFC consumption. When set, suppresses `-modules` on the platform projection so the consumer uses pre-built IFCs from the referenced project instead of generating its own. +- `_CppWinRTConsumesPlatformModules`: Internal property set by `CppWinRTResolveModuleReferences` when any ProjectReference has `CppWinRTConsumeModule=true`. Controls whether the platform projection receives `-modules`. + +### Cross-Project IFC Resolution + +MSVC's module dependency scanner uses `/ifcSearchDir` for within-project module resolution. For cross-project modules, the scanner generates explicit `/reference "module.name=path.ifc"` entries based on the dependency scan results. The `/ifcSearchDir` pointing to the builder's `$(IntDir)` allows the scanner to find the pre-built IFCs. + +## Dependency Collection + +During header generation, when `-modules` is active, each `write_namespace_*_h()` function receives a `deps_ptr` parameter. The writer's `w.depends` map is inspected to find referenced namespaces. Only namespaces that: +1. Exist in the cache +2. Have projected types +3. Are in the module namespace set (or set is empty) + +are added to the dependency set. Self-references are excluded. The union of dependencies from all four header files (`*.0.h`, `*.1.h`, `*.2.h`, `.h`) gives the complete dependency set for a namespace module. + +## Testing + +### test/test_cpp20_module/ (in-repo) + +Standalone test built by the main solution. Uses a PreBuildEvent to run cppwinrt.exe with `-modules -base -module_include "Windows.Foundation"`. Tests URI, events, collections, and coroutines. + +### test/nuget/ (NuGet integration) + +Multi-project solution: +- **TestModuleBuilder**: Static library that pre-builds platform modules +- **TestModuleComponent1**: Component DLL (Greeter class), consumes builder's modules +- **TestModuleComponent2**: Component DLL (GreeterGroup), depends on Component1 +- **TestModuleConsumerApp**: Console app, consumes builder + both components +- **TestModuleApp**: Single-project that builds and consumes its own modules diff --git a/natvis/pch.h b/natvis/pch.h index 95e561971..3de6807b3 100644 --- a/natvis/pch.h +++ b/natvis/pch.h @@ -12,6 +12,7 @@ #include #include "base_includes.h" #include "base_macros.h" +#include "base_source_location.h" #include "base_types.h" #include "base_extern.h" #include "base_meta.h" diff --git a/nuget/Microsoft.Windows.CppWinRT.targets b/nuget/Microsoft.Windows.CppWinRT.targets index 188e56835..a297258f3 100644 --- a/nuget/Microsoft.Windows.CppWinRT.targets +++ b/nuget/Microsoft.Windows.CppWinRT.targets @@ -26,6 +26,10 @@ Copyright (C) Microsoft Corporation. All rights reserved. $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)))..\..\ $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory))) $(CppWinRTParameters) -fastabi + + -modules + -module_include $(CppWinRTModuleInclude.Replace(';', ' ')) + $(CppWinRTCommandModuleFilter) -module_exclude $(CppWinRTModuleExclude.Replace(';', ' ')) "$(CppWinRTPackageDir)bin\" "$(CppWinRTPackageDir)" @@ -651,6 +655,9 @@ $(XamlMetaDataProviderPch) <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) + + <_CppwinrtParameters Condition="'$(_CppWinRTConsumesPlatformModules)'!='true'">$(_CppwinrtParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) -out "$(GeneratedFilesDir)." @@ -729,7 +736,7 @@ $(XamlMetaDataProviderPch) <_CppwinrtRefRefs Include="@(CppWinRTPlatformWinMDReferences)"/> - <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) + <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtRefInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtRefRefs->'-ref "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) -out "$(GeneratedFilesDir)." @@ -835,7 +842,7 @@ $(XamlMetaDataProviderPch) <_CppwinrtCompRefs Include="@(CppWinRTPlatformWinMDReferences)"/> - <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) -overwrite -name $(RootNamespace) $(CppWinRTCommandPrecompiledHeader) $(CppWinRTCommandUsePrefixes) -comp "$(GeneratedFilesDir)sources" + <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) $(CppWinRTCommandModules) $(CppWinRTCommandModuleFilter) -overwrite -name $(RootNamespace) $(CppWinRTCommandPrecompiledHeader) $(CppWinRTCommandUsePrefixes) -comp "$(GeneratedFilesDir)sources" <_CppwinrtParameters Condition="'$(CppWinRTOptimized)'=='true'">$(_CppwinrtParameters) -opt <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtCompInputs->'-in "%(WinMDPath)"', ' ') <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtCompRefs->'-ref "%(WinMDPath)"', ' ') @@ -895,4 +902,88 @@ $(XamlMetaDataProviderPch) + + + + + CompileAsCppModule + true + NotUsing + + + + + + + + + $(GeneratedFilesDir) + $(IntDir) + $(OutDir) + + + + + + + + + <_CppWinRTModuleProviders Remove="@(_CppWinRTModuleProviders)" /> + <_CppWinRTModuleProviders Include="@(ProjectReference)" + Condition="'%(ProjectReference.CppWinRTConsumeModule)' == 'true'" /> + + + + + <_CppWinRTConsumesPlatformModules>true + + + + + + + + + + + + + <_CppWinRTModuleIfcSearchDirs>@(_CppWinRTResolvedModuleRefs->'%(CppWinRTModuleIfcDir)') + + + + $(_CppWinRTModuleIfcSearchDirs);%(ClCompile.AdditionalBMIDirectories) + + + + diff --git a/nuget/modules.md b/nuget/modules.md new file mode 100644 index 000000000..f1a796361 --- /dev/null +++ b/nuget/modules.md @@ -0,0 +1,159 @@ +# C++/WinRT C++20 Modules Guide + +## Overview + +C++/WinRT can generate per-namespace C++20 named modules (`.ixx` files) alongside the traditional projection headers. This allows you to write: + +```cpp +import winrt.Windows.Foundation; +``` + +instead of: + +```cpp +#include +``` + +Modules provide faster builds through pre-compiled module interfaces (IFCs) and better isolation of macro and declaration scopes. + +## Quick Start — Single Project + +For a project that builds and consumes its own modules: + +1. Set `CppWinRTBuildModule` to `true` in your project: + ```xml + + true + + ``` + +2. Optionally limit which namespaces get modules: + ```xml + + Windows.Foundation;Windows.Storage + + ``` + +3. Enable `BuildStlModules` for `import std;` support: + ```xml + + true + + ``` + +4. In your `.cpp` files: + ```cpp + import winrt.Windows.Foundation; + + int main() { + winrt::init_apartment(); + winrt::Windows::Foundation::Uri uri(L"https://example.com"); + } + ``` + +## Quick Start — Multi-Project (Recommended) + +For larger solutions, compile platform modules once in a dedicated "builder" static library, and share the pre-built IFCs with other projects. + +### Module Builder (static library) + +```xml + + StaticLibrary + true + Windows.Foundation + +``` + +### Consumer (exe or dll) + +```xml + + true + + + + true + + +``` + +The `CppWinRTConsumeModule` metadata on the ProjectReference tells the build system to: +- Use the builder's pre-built platform IFCs instead of compiling platform `.ixx` files again +- Skip generating platform `.ixx` files in the consumer's own projection + +### Component DLLs + +WinRT component projects can also use modules. Set `CppWinRTBuildModule=true` and all three projections (platform, reference, component) will generate `.ixx` files. + +```xml + + true + MyComponent + + + + true + + +``` + +### Consuming Components from Other Projects + +If project A references a component DLL from project B, project A builds its own reference projection modules from B's `.winmd`: + +```cpp +// These modules are built locally from the component's .winmd +import winrt.MyComponent; + +auto obj = winrt::MyComponent::MyClass(); +``` + +## MSBuild Properties + +| Property | Default | Description | +|-|-|-| +| `CppWinRTBuildModule` | false | Generate `.ixx` module interface units from projections | +| `CppWinRTModuleInclude` | (all) | Semicolon-delimited namespace prefixes to include in module generation | +| `CppWinRTModuleExclude` | (none) | Semicolon-delimited namespace prefixes to exclude from module generation | + +| ProjectReference Metadata | Default | Description | +|-|-|-| +| `CppWinRTConsumeModule` | false | Consume pre-built platform module IFCs from this project reference | + +## Module Names + +| Module | Contents | +|-|-| +| `winrt_base` | Core C++/WinRT types (`hstring`, `com_ptr`, `IUnknown`, etc.) — re-exported by all namespace modules | +| `winrt_numerics` | `Windows::Foundation::Numerics` types — re-exported by `winrt_base` | +| `winrt.` | Per-namespace projection (e.g., `winrt.Windows.Foundation`) | + +## Requirements + +- MSVC v145 toolset (Visual Studio 2026) or later recommended +- C++20 or later (`/std:c++20` or newer) +- `BuildStlModules=true` for `import std;` support + +## Limitations + +- Module IFCs are not compatible across toolset versions. All projects must use the same toolset. +- Cyclic namespace dependencies (e.g., `Windows.Foundation` ↔ `Windows.Foundation.Collections`) are handled automatically via SCC consolidation, but the resulting module name is chosen alphabetically. Adding new APIs could change SCC groupings. + +## Caution: Module Reuse Across Projects + +Pre-built IFCs (via `CppWinRTConsumeModule`) should only be shared when the builder and consumer use the same compilation context. In particular: + +- **Component modules are project-private.** A component projection built with `CppWinRTOptimized=true` generates modules that bypass activation factories for in-component type instantiation (`-opt`). If a consuming project accidentally imports these modules instead of building its own reference projection, the consumer will attempt direct instantiation across DLL boundaries, resulting in linker errors or incorrect behavior. Each project should build its own modules from the component's `.winmd` — do not tag component ProjectReferences with `CppWinRTConsumeModule`. + +- **`CppWinRTConsumeModule` is intended for platform module builders only.** The builder project is a dedicated static library whose sole purpose is compiling platform SDK modules. Its compilation flags (no `-opt`, no `-comp`) produce modules safe for any consumer. Only tag this builder's ProjectReference with `CppWinRTConsumeModule=true`. + +- **Module filter scope matters.** `CppWinRTModuleInclude` / `CppWinRTModuleExclude` applies to all three projections (platform, reference, component). If you set `CppWinRTModuleInclude=MyComponent`, only `MyComponent` namespaces will get `.ixx` files — platform and reference namespace modules will not be generated. Make sure your filter includes all namespaces you intend to import as modules, or use `CppWinRTConsumeModule` to get platform modules from a builder that was configured with the appropriate filter. + +## Troubleshooting + +**"could not find module 'winrt.X'"** — Ensure the `.ixx` was generated (check `$(GeneratedFilesDir)winrt\`) and that `CppWinRTBuildModule=true` is set. For cross-project references, verify the consuming project's `ProjectReference` to the builder has `CppWinRTConsumeModule=true`, and that the builder's `IntDir` is accessible via `/ifcSearchDir`. + +**Linker errors for component constructors** — You may be importing a component's internal module instead of building your own reference projection. Remove explicit `/reference` flags for component IFCs and ensure your project has `CppWinRTBuildModule=true` so it builds reference projection modules from the component's `.winmd`. + +**Redefinition errors** — Don't mix `#include` and `import` for the same namespace in the same translation unit. Use `import` consistently. diff --git a/nuget/readme.md b/nuget/readme.md index 9379aa716..c8c0b9537 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -70,6 +70,9 @@ C++/WinRT behavior can be customized with these project properties: | CppWinRTOptimized | true \| *false | Enables component projection [optimization features](https://kennykerr.ca/2019/06/07/cppwinrt-optimizing-components/) | | CppWinRTGenerateWindowsMetadata | true \| *false | Indicates whether this project produces Windows Metadata | | CppWinRTEnableDefaultPrivateFalse | true \| *false | Indicates whether this project uses C++/WinRT optimized default for copying binaries to the output directory | +| CppWinRTBuildModule | true \| *false | Generates per-namespace C++20 module interface units (.ixx) alongside projection headers | +| CppWinRTModuleInclude | namespace list | Semicolon-delimited namespaces to include in module generation (default: all) | +| CppWinRTModuleExclude | namespace list | Semicolon-delimited namespaces to exclude from module generation | \*Default value To customize common C++/WinRT project properties: @@ -132,6 +135,15 @@ void DerivedPage::InitializeComponent() } ``` +## C++20 Modules + +C++/WinRT supports C++20 named modules as an alternative to `#include`-based consumption. Instead of `#include `, you can write `import winrt.Windows.Foundation;`. See [modules.md](modules.md) for the full guide. + +| ProjectReference metadata | Description | +|-|-| +| CppWinRTConsumeModule | true \| *false | When set on a ProjectReference, consumes pre-built platform module IFCs from the referenced project | +\*Default value + ## Troubleshooting The msbuild verbosity level maps to msbuild message importance as follows: diff --git a/strings/base_abi.h b/strings/base_abi.h index 72946a3fa..4b7b8f77f 100644 --- a/strings/base_abi.h +++ b/strings/base_abi.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template <> struct abi { diff --git a/strings/base_activation.h b/strings/base_activation.h index 1a195d865..c657c692d 100644 --- a/strings/base_activation.h +++ b/strings/base_activation.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct library_traits { @@ -125,7 +125,7 @@ WINRT_EXPORT namespace winrt #define WINRT_IMPL_INTERLOCKED_READ_MEMORY_BARRIER (__dmb(_ARM64_BARRIER_ISH)); #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline std::int32_t interlocked_read_32(std::int32_t const volatile* target) noexcept { @@ -548,7 +548,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template T fast_activate(Windows::Foundation::IActivationFactory const& factory) diff --git a/strings/base_agile_ref.h b/strings/base_agile_ref.h index 88fbea065..14447706a 100644 --- a/strings/base_agile_ref.h +++ b/strings/base_agile_ref.h @@ -47,7 +47,7 @@ WINRT_EXPORT namespace winrt #endif } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct module_lock_updater; diff --git a/strings/base_array.h b/strings/base_array.h index 48d10c6ce..d29bee883 100644 --- a/strings/base_array.h +++ b/strings/base_array.h @@ -483,7 +483,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct array_size_proxy diff --git a/strings/base_collections.h b/strings/base_collections.h index 4e1af51eb..7d8dc2e77 100644 --- a/strings/base_collections.h +++ b/strings/base_collections.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { namespace wfc = Windows::Foundation::Collections; diff --git a/strings/base_collections_base.h b/strings/base_collections_base.h index 6fe10fa64..d299cc0c8 100644 --- a/strings/base_collections_base.h +++ b/strings/base_collections_base.h @@ -1,4 +1,4 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct nop_lock_guard {}; diff --git a/strings/base_collections_input_iterable.h b/strings/base_collections_input_iterable.h index e75211c30..e9d3af251 100644 --- a/strings/base_collections_input_iterable.h +++ b/strings/base_collections_input_iterable.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct input_iterable : diff --git a/strings/base_collections_input_map.h b/strings/base_collections_input_map.h index b33975fe6..3fe146bf6 100644 --- a/strings/base_collections_input_map.h +++ b/strings/base_collections_input_map.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct map_impl : diff --git a/strings/base_collections_input_map_view.h b/strings/base_collections_input_map_view.h index bfd8d82a9..d79eed61d 100644 --- a/strings/base_collections_input_map_view.h +++ b/strings/base_collections_input_map_view.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct input_map_view : diff --git a/strings/base_collections_input_vector.h b/strings/base_collections_input_vector.h index b5b76de38..a06e73b33 100644 --- a/strings/base_collections_input_vector.h +++ b/strings/base_collections_input_vector.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct vector_impl : diff --git a/strings/base_collections_input_vector_view.h b/strings/base_collections_input_vector_view.h index 3793e239a..30768c18e 100644 --- a/strings/base_collections_input_vector_view.h +++ b/strings/base_collections_input_vector_view.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct input_vector_view : diff --git a/strings/base_collections_map.h b/strings/base_collections_map.h index fa769fb88..5d0f9e746 100644 --- a/strings/base_collections_map.h +++ b/strings/base_collections_map.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using multi_threaded_map = map_impl; @@ -116,7 +116,7 @@ WINRT_EXPORT namespace winrt } } -namespace std +WINRT_IMPL_STD_EXPORT namespace std { template struct tuple_size> diff --git a/strings/base_collections_vector.h b/strings/base_collections_vector.h index 3e9c1b254..3388806af 100644 --- a/strings/base_collections_vector.h +++ b/strings/base_collections_vector.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using multi_threaded_vector = vector_impl; diff --git a/strings/base_com_ptr.h b/strings/base_com_ptr.h index 27496789a..0f02fabeb 100644 --- a/strings/base_com_ptr.h +++ b/strings/base_com_ptr.h @@ -5,7 +5,7 @@ WINRT_EXPORT namespace winrt struct com_ptr; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct capture_decay { @@ -349,7 +349,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template std::int32_t capture_to(void** result, com_ptr const& object, M method, Args&& ...args) diff --git a/strings/base_composable.h b/strings/base_composable.h index e606d1292..5a7712ef7 100644 --- a/strings/base_composable.h +++ b/strings/base_composable.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct composable_factory diff --git a/strings/base_coroutine_foundation.h b/strings/base_coroutine_foundation.h index 4f1c8d0b6..5cefad836 100644 --- a/strings/base_coroutine_foundation.h +++ b/strings/base_coroutine_foundation.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct async_completed_handler; @@ -312,7 +312,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct cancellation_token @@ -704,7 +704,7 @@ namespace winrt::impl }; } -namespace std +WINRT_IMPL_STD_EXPORT namespace std { template struct coroutine_traits diff --git a/strings/base_coroutine_threadpool.h b/strings/base_coroutine_threadpool.h index 6748906ca..057d5b548 100644 --- a/strings/base_coroutine_threadpool.h +++ b/strings/base_coroutine_threadpool.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #ifdef WINRT_IMPL_COROUTINES inline auto submit_threadpool_callback(void(__stdcall* callback)(void*, void* context), void* context) @@ -321,7 +321,7 @@ WINRT_EXPORT namespace winrt }; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct apartment_awaiter { @@ -671,7 +671,7 @@ WINRT_EXPORT namespace winrt struct fire_and_forget {}; } -namespace std +WINRT_IMPL_STD_EXPORT namespace std { template struct coroutine_traits diff --git a/strings/base_delegate.h b/strings/base_delegate.h index 1cfe58710..1fe00ecd1 100644 --- a/strings/base_delegate.h +++ b/strings/base_delegate.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #if defined(_MSC_VER) #pragma warning(push) diff --git a/strings/base_error.h b/strings/base_error.h index c58635e5d..d42ca516b 100644 --- a/strings/base_error.h +++ b/strings/base_error.h @@ -7,7 +7,7 @@ #define WINRT_IMPL_RETURNADDRESS() nullptr #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct heap_traits { @@ -536,7 +536,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline hresult check_hresult_allow_bounds(hresult const result, winrt::impl::slim_source_location const& sourceInformation = winrt::impl::slim_source_location::current()) { diff --git a/strings/base_events.h b/strings/base_events.h index f7e2e6976..c21124e9c 100644 --- a/strings/base_events.h +++ b/strings/base_events.h @@ -130,7 +130,7 @@ WINRT_EXPORT namespace winrt }; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct event_revoker diff --git a/strings/base_extern.h b/strings/base_extern.h index 84e2943c4..a17908ac4 100644 --- a/strings/base_extern.h +++ b/strings/base_extern.h @@ -1,8 +1,11 @@ -__declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(void* address) noexcept {}; -__declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(void* address) {}; -__declspec(selectany) void(__stdcall* winrt_throw_hresult_handler)(std::uint32_t lineNumber, char const* fileName, char const* functionName, void* returnAddress, winrt::hresult const result) noexcept {}; -__declspec(selectany) std::int32_t(__stdcall* winrt_activation_handler)(void* classId, winrt::guid const& iid, void** factory) noexcept {}; +// These global function pointers must use WINRT_EXPORT (which expands to +// 'export extern "C++"' in module builds) so that module and non-module TUs +// in the same binary share the same instances. +WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(void* address) noexcept {}; +WINRT_EXPORT __declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(void* address) {}; +WINRT_EXPORT __declspec(selectany) void(__stdcall* winrt_throw_hresult_handler)(std::uint32_t lineNumber, char const* fileName, char const* functionName, void* returnAddress, winrt::hresult const result) noexcept {}; +WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_activation_handler)(void* classId, winrt::guid const& iid, void** factory) noexcept {}; #if defined(_MSC_VER) #ifdef _M_HYBRID diff --git a/strings/base_fast_forward.h b/strings/base_fast_forward.h index dc89fe6ce..a73e70a04 100644 --- a/strings/base_fast_forward.h +++ b/strings/base_fast_forward.h @@ -30,7 +30,11 @@ static_assert(WINRT_FAST_ABI_SIZE >= %); #pragma detect_mismatch("WINRT_FAST_ABI_SIZE", WINRT_IMPL_STRING(WINRT_FAST_ABI_SIZE)) -namespace winrt::impl +#ifndef WINRT_EXPORT +#define WINRT_EXPORT +#endif // WINRT_EXPORT + +WINRT_EXPORT namespace winrt::impl { // Thunk definitions are in arch-specific assembly sources % diff --git a/strings/base_foundation.h b/strings/base_foundation.h index ea8881edc..083252753 100644 --- a/strings/base_foundation.h +++ b/strings/base_foundation.h @@ -100,7 +100,7 @@ WINRT_EXPORT namespace winrt::Windows::Foundation } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template <> inline constexpr auto& name_v = L"Windows.Foundation.Point"; template <> inline constexpr auto& name_v = L"Windows.Foundation.Size"; diff --git a/strings/base_identity.h b/strings/base_identity.h index 30830bc5a..7c61c83e2 100644 --- a/strings/base_identity.h +++ b/strings/base_identity.h @@ -17,7 +17,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template constexpr std::array to_array(T const* value, std::index_sequence const) noexcept diff --git a/strings/base_implements.h b/strings/base_implements.h index 7edf32149..0eb8db0bd 100644 --- a/strings/base_implements.h +++ b/strings/base_implements.h @@ -6,7 +6,7 @@ #endif #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct marker { @@ -30,7 +30,7 @@ WINRT_EXPORT namespace winrt struct implements; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using tuple_cat_t = decltype(std::tuple_cat(std::declval()...)); @@ -267,7 +267,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct interface_list; diff --git a/strings/base_iterator.h b/strings/base_iterator.h index acb0ebd42..46c0c6b63 100644 --- a/strings/base_iterator.h +++ b/strings/base_iterator.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct fast_iterator diff --git a/strings/base_macros.h b/strings/base_macros.h index 3dc01fa2d..8e74a9b8e 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -1,3 +1,6 @@ +#pragma once +#ifndef WINRT_BASE_MACROS_H +#define WINRT_BASE_MACROS_H #ifdef _DEBUG @@ -11,7 +14,11 @@ #define WINRT_VERIFY(expression) (void)(expression) #define WINRT_VERIFY_(result, expression) (void)(expression) -#endif +#endif // _DEBUG + +#if defined(__cpp_lib_coroutine) +#define WINRT_IMPL_COROUTINES +#endif // __cpp_lib_coroutine #define WINRT_IMPL_SHIM(...) (*(abi_t<__VA_ARGS__>**)&static_cast<__VA_ARGS__ const&>(static_cast(*this))) @@ -21,15 +28,34 @@ // Note: this is a workaround for a false-positive warning produced by the Visual C++ 16.3 compiler. #pragma warning(disable : 4268) -#endif -#if defined(__cpp_lib_coroutine) -#define WINRT_IMPL_COROUTINES -#endif +// C++ module warnings by /W4 +#pragma warning(disable : 4499) +#pragma warning(disable : 4630) +#endif // _MSC_VER #ifndef WINRT_EXPORT +#ifdef WINRT_IMPL_BUILD_MODULE +#define WINRT_EXPORT export extern "C++" +#else #define WINRT_EXPORT -#endif +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_EXPORT + +// Template specializations in namespace std (hash, coroutine_traits) need extern "C++" +// linkage in module builds for proper merging with the std module, but must NOT be +// exported — exporting namespace std would make all of std transitively visible. +#ifndef WINRT_IMPL_STD_EXPORT +#ifdef WINRT_IMPL_BUILD_MODULE +#define WINRT_IMPL_STD_EXPORT extern "C++" +#else +#define WINRT_IMPL_STD_EXPORT +#endif // WINRT_IMPL_BUILD_MODULE +#endif // WINRT_IMPL_STD_EXPORT + +// pulls in large, hard-to-control legacy headers. In header builds we keep the +// existing behavior, but in module builds it's provided by the winrt_numerics module. +#if !(defined(WINRT_IMPL_BUILD_MODULE) || defined(WINRT_IMPORT_MODULE)) #ifdef WINRT_IMPL_NUMERICS #define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics @@ -39,7 +65,9 @@ #undef _WINDOWS_NUMERICS_NAMESPACE_ #undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ #undef _WINDOWS_NUMERICS_END_NAMESPACE_ -#endif +#endif // WINRT_IMPL_NUMERICS + +#endif // !(WINRT_IMPL_BUILD_MODULE || WINRT_IMPORT_MODULE) #if defined(_MSC_VER) #define WINRT_IMPL_NOINLINE __declspec(noinline) @@ -81,7 +109,7 @@ #define WINRT_IMPL_HAS_DECLSPEC_UUID 0 #endif -#ifdef __IUnknown_INTERFACE_DEFINED__ +#if defined(__IUnknown_INTERFACE_DEFINED__) || defined(WINRT_ENABLE_LEGACY_COM) #define WINRT_IMPL_IUNKNOWN_DEFINED #else // Forward declare so we can talk about it. @@ -95,99 +123,4 @@ typedef struct _GUID GUID; #define WINRT_IMPL_CONSTEVAL constexpr #endif -// The intrinsics (such as __builtin_FILE()) that power std::source_location are also used to power winrt:impl::slim_source_location. -// The source location needs to be for the calling code, not cppwinrt itself, so that it is useful to developers building on top of -// this library. As a result any public-facing method that can result in an error needs a default-constructed slim_source_location -// argument so that it will collect source information from the application code that is calling into cppwinrt. -// -// We do not directly use std::source_location for two reasons: -// 1) std::source_location::function_name() is unavoidable. These strings end up in the final binary, bloating their size. This -// is particularly impactful for code bases that use templates heavily. Cases of 50% binary size growth have been observed. -// 2) std::source_location is a cpp20 feature, which is above the cpp17 feature floor for cppwinrt. By defining our own version -// we can avoid ODR violations in mixed cpp17/cpp20 builds. cpp17 callers will have an ABI that matches cpp20 callers (they -// will just not have useful file/line/function information). -// -// Some projects may decide that the source information binary size impact is not worth the benefit. Defining WINRT_NO_SOURCE_LOCATION -// will prevent this feature from activating. The slim_source_location type will be forwarded around but it will not include any -// nonzero data. That eliminates the biggest source of binary size overhead. -// -// To help with debugging the __builtin_FUNCTION() intrinsic will be used in _DEBUG builds. This will provide a bit more diagnostic -// value at the cost of binary size. The assumption is that binary size is considered less important in debug builds so this tradeoff -// is acceptable. -// -// The different behavior of the default parameters to winrt::impl::slim_source_location::current() is technically an ODR violation, -// albeit a minor one. There should be no serious consequence to this violation. In practice it means that mixing cpp17/cpp20, -// or mixing WINRT_NO_SOURCE_LOCATION with undefining it, will lead to inconsistent source location information. It may be missing -// when it is expected to be included, or it may be present when it is not expected. The behavior will depend on the linker's choice -// when there are multiple translation units with different options. This violation is tracked by https://github.com/microsoft/cppwinrt/issues/1445. - -#if !defined(__cpp_lib_source_location) || defined(WINRT_NO_SOURCE_LOCATION) -// Case1: cpp17 mode. The source_location intrinsics are not available. -// Case2: The caller has disabled source_location support. Ensure that there is no binary size overhead for line/file/function. -#define WINRT_IMPL_BUILTIN_LINE 0 -#define WINRT_IMPL_BUILTIN_FILE nullptr -#define WINRT_IMPL_BUILTIN_FUNCTION nullptr -#elif _DEBUG -// cpp20 _DEBUG builds include function information, which has a heavy binary size impact, in addition to file/line. -#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() -#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() -#define WINRT_IMPL_BUILTIN_FUNCTION __builtin_FUNCTION() -#else -// Release builds in cpp20 mode get file and line information but NOT function information. Function strings -// quickly add up to a substantial binary size impact, especially when templates are heavily used. -#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() -#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() -#define WINRT_IMPL_BUILTIN_FUNCTION nullptr -#endif - -namespace winrt::impl -{ - // This struct is intended to be highly similar to std::source_location. The key difference is - // that function_name is NOT included. Function names do not fold to identical strings and can - // have heavy binary size overhead when templates cause many permutations to exist. - struct slim_source_location - { - [[nodiscard]] static WINRT_IMPL_CONSTEVAL slim_source_location current( - const std::uint_least32_t line = WINRT_IMPL_BUILTIN_LINE, - const char* const file = WINRT_IMPL_BUILTIN_FILE, - const char* const function = WINRT_IMPL_BUILTIN_FUNCTION) noexcept - { - return slim_source_location{ line, file, function }; - } - - [[nodiscard]] constexpr slim_source_location() noexcept = default; - - [[nodiscard]] constexpr slim_source_location( - const std::uint_least32_t line, - const char* const file, - const char* const function) noexcept : - m_line(line), - m_file(file), - m_function(function) - {} - - [[nodiscard]] constexpr std::uint_least32_t line() const noexcept - { - return m_line; - } - - [[nodiscard]] constexpr const char* file_name() const noexcept - { - return m_file; - } - - [[nodiscard]] constexpr const char* function_name() const noexcept - { - return m_function; - } - - private: - const std::uint_least32_t m_line{}; - const char* const m_file{}; - const char* const m_function{}; - }; -} - -#ifdef _MSC_VER -#pragma detect_mismatch("WINRT_SOURCE_LOCATION", "slim") -#endif // _MSC_VER +#endif // WINRT_BASE_MACROS_H diff --git a/strings/base_marshaler.h b/strings/base_marshaler.h index 526f4d4c3..9be6959c9 100644 --- a/strings/base_marshaler.h +++ b/strings/base_marshaler.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline std::int32_t make_marshaler(unknown_abi* outer, void** result) noexcept { diff --git a/strings/base_meta.h b/strings/base_meta.h index 7dbb4c386..6b28640ef 100644 --- a/strings/base_meta.h +++ b/strings/base_meta.h @@ -48,7 +48,7 @@ WINRT_EXPORT namespace winrt } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { using namespace std::literals; diff --git a/strings/base_module_base_ixx.h b/strings/base_module_base_ixx.h new file mode 100644 index 000000000..83336d75d --- /dev/null +++ b/strings/base_module_base_ixx.h @@ -0,0 +1,17 @@ + +#ifdef WINRT_ENABLE_LEGACY_COM +#include +#include +#undef GetCurrentTime +#endif + +export module winrt_base; + +import std; +export import winrt_numerics; + +#if __has_include() +#define WINRT_IMPL_NUMERICS +#endif + +#include "winrt/base.h" diff --git a/strings/base_module_ixx_preamble.h b/strings/base_module_ixx_preamble.h new file mode 100644 index 000000000..6ae29d059 --- /dev/null +++ b/strings/base_module_ixx_preamble.h @@ -0,0 +1,9 @@ +module; +#define WINRT_IMPL_BUILD_MODULE +#include +#include +#include +#ifdef _DEBUG +#include +#endif // _DEBUG +#include "winrt/base_macros.h" diff --git a/strings/base_module_numerics_ixx.h b/strings/base_module_numerics_ixx.h new file mode 100644 index 000000000..d25c09b7b --- /dev/null +++ b/strings/base_module_numerics_ixx.h @@ -0,0 +1,22 @@ + +export module winrt_numerics; + +#if __has_include() +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 5244) +#endif +#include + +#define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ export extern "C++" namespace winrt::Windows::Foundation::Numerics +#define _WINDOWS_NUMERICS_END_NAMESPACE_ +#include +#undef _WINDOWS_NUMERICS_NAMESPACE_ +#undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ +#undef _WINDOWS_NUMERICS_END_NAMESPACE_ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#endif diff --git a/strings/base_natvis.h b/strings/base_natvis.h index 60c5f5548..9e78563cb 100644 --- a/strings/base_natvis.h +++ b/strings/base_natvis.h @@ -5,7 +5,7 @@ #ifdef WINRT_NATVIS -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct natvis { diff --git a/strings/base_reference_produce.h b/strings/base_reference_produce.h index 2820aff50..abffb3384 100644 --- a/strings/base_reference_produce.h +++ b/strings/base_reference_produce.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct reference : implements, Windows::Foundation::IReference, Windows::Foundation::IPropertyValue> @@ -420,7 +420,7 @@ WINRT_EXPORT namespace winrt::Windows::Foundation } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template T unbox_value_type(From&& value) diff --git a/strings/base_source_location.h b/strings/base_source_location.h new file mode 100644 index 000000000..c0fd81f9f --- /dev/null +++ b/strings/base_source_location.h @@ -0,0 +1,97 @@ + +// The intrinsics (such as __builtin_FILE()) that power std::source_location are also used to power winrt:impl::slim_source_location. +// The source location needs to be for the calling code, not cppwinrt itself, so that it is useful to developers building on top of +// this library. As a result any public-facing method that can result in an error needs a default-constructed slim_source_location +// argument so that it will collect source information from the application code that is calling into cppwinrt. +// +// We do not directly use std::source_location for two reasons: +// 1) std::source_location::function_name() is unavoidable. These strings end up in the final binary, bloating their size. This +// is particularly impactful for code bases that use templates heavily. Cases of 50% binary size growth have been observed. +// 2) std::source_location is a cpp20 feature, which is above the cpp17 feature floor for cppwinrt. By defining our own version +// we can avoid ODR violations in mixed cpp17/cpp20 builds. cpp17 callers will have an ABI that matches cpp20 callers (they +// will just not have useful file/line/function information). +// +// Some projects may decide that the source information binary size impact is not worth the benefit. Defining WINRT_NO_SOURCE_LOCATION +// will prevent this feature from activating. The slim_source_location type will be forwarded around but it will not include any +// nonzero data. That eliminates the biggest source of binary size overhead. +// +// To help with debugging the __builtin_FUNCTION() intrinsic will be used in _DEBUG builds. This will provide a bit more diagnostic +// value at the cost of binary size. The assumption is that binary size is considered less important in debug builds so this tradeoff +// is acceptable. +// +// The different behavior of the default parameters to winrt::impl::slim_source_location::current() is technically an ODR violation, +// albeit a minor one. There should be no serious consequence to this violation. In practice it means that mixing cpp17/cpp20, +// or mixing WINRT_NO_SOURCE_LOCATION with undefining it, will lead to inconsistent source location information. It may be missing +// when it is expected to be included, or it may be present when it is not expected. The behavior will depend on the linker's choice +// when there are multiple translation units with different options. This violation is tracked by https://github.com/microsoft/cppwinrt/issues/1445. + +#if !defined(__cpp_lib_source_location) || defined(WINRT_NO_SOURCE_LOCATION) +// Case1: cpp17 mode. The source_location intrinsics are not available. +// Case2: The caller has disabled source_location support. Ensure that there is no binary size overhead for line/file/function. +#define WINRT_IMPL_BUILTIN_LINE 0 +#define WINRT_IMPL_BUILTIN_FILE nullptr +#define WINRT_IMPL_BUILTIN_FUNCTION nullptr +#elif _DEBUG +// cpp20 _DEBUG builds include function information, which has a heavy binary size impact, in addition to file/line. +#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() +#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() +#define WINRT_IMPL_BUILTIN_FUNCTION __builtin_FUNCTION() +#else +// Release builds in cpp20 mode get file and line information but NOT function information. Function strings +// quickly add up to a substantial binary size impact, especially when templates are heavily used. +#define WINRT_IMPL_BUILTIN_LINE __builtin_LINE() +#define WINRT_IMPL_BUILTIN_FILE __builtin_FILE() +#define WINRT_IMPL_BUILTIN_FUNCTION nullptr +#endif + +WINRT_EXPORT namespace winrt::impl +{ + // This struct is intended to be highly similar to std::source_location. The key difference is + // that function_name is NOT included. Function names do not fold to identical strings and can + // have heavy binary size overhead when templates cause many permutations to exist. + struct slim_source_location + { + [[nodiscard]] static WINRT_IMPL_CONSTEVAL slim_source_location current( + const std::uint_least32_t line = WINRT_IMPL_BUILTIN_LINE, + const char* const file = WINRT_IMPL_BUILTIN_FILE, + const char* const function = WINRT_IMPL_BUILTIN_FUNCTION) noexcept + { + return slim_source_location{ line, file, function }; + } + + [[nodiscard]] constexpr slim_source_location() noexcept = default; + + [[nodiscard]] constexpr slim_source_location( + const std::uint_least32_t line, + const char* const file, + const char* const function) noexcept : + m_line(line), + m_file(file), + m_function(function) + {} + + [[nodiscard]] constexpr std::uint_least32_t line() const noexcept + { + return m_line; + } + + [[nodiscard]] constexpr const char* file_name() const noexcept + { + return m_file; + } + + [[nodiscard]] constexpr const char* function_name() const noexcept + { + return m_function; + } + + private: + const std::uint_least32_t m_line{}; + const char* const m_file{}; + const char* const m_function{}; + }; +} + +#ifdef _MSC_VER +#pragma detect_mismatch("WINRT_SOURCE_LOCATION", "slim") +#endif // _MSC_VER diff --git a/strings/base_std_hash.h b/strings/base_std_hash.h index 864c31b13..4777f8082 100644 --- a/strings/base_std_hash.h +++ b/strings/base_std_hash.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline std::size_t hash_data(void const* ptr, std::size_t const bytes) noexcept { @@ -32,7 +32,7 @@ namespace winrt::impl }; } -namespace std +WINRT_IMPL_STD_EXPORT namespace std { template<> struct hash { diff --git a/strings/base_string.h b/strings/base_string.h index 92295e81c..6b1fb37b5 100644 --- a/strings/base_string.h +++ b/strings/base_string.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { struct atomic_ref_count { @@ -442,7 +442,7 @@ template<> struct std::formatter : std::formatter {}; #endif -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template <> struct abi { diff --git a/strings/base_string_input.h b/strings/base_string_input.h index 5ac0221f6..71cd5f3c0 100644 --- a/strings/base_string_input.h +++ b/strings/base_string_input.h @@ -65,7 +65,7 @@ WINRT_EXPORT namespace winrt::param } } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template using param_type = std::conditional_t, param::hstring, T>; diff --git a/strings/base_string_operators.h b/strings/base_string_operators.h index 25e2eccce..223769d01 100644 --- a/strings/base_string_operators.h +++ b/strings/base_string_operators.h @@ -94,7 +94,7 @@ WINRT_EXPORT namespace winrt bool operator>=(std::nullptr_t left, hstring const& right) = delete; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { inline hstring concat_hstring(std::wstring_view const& left, std::wstring_view const& right) { diff --git a/strings/base_types.h b/strings/base_types.h index 18529e116..dc2b13632 100644 --- a/strings/base_types.h +++ b/strings/base_types.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { using ptp_io = struct tp_io*; using ptp_timer = struct tp_timer*; @@ -208,7 +208,7 @@ WINRT_EXPORT namespace winrt::Windows::Foundation using DateTime = std::chrono::time_point; } -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #ifdef WINRT_IMPL_IUNKNOWN_DEFINED using hresult_type = long; diff --git a/strings/base_windows.h b/strings/base_windows.h index 21c4163e5..dcd164574 100644 --- a/strings/base_windows.h +++ b/strings/base_windows.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { #ifdef WINRT_DIAGNOSTICS diff --git a/strings/base_xaml_typename.h b/strings/base_xaml_typename.h index b7b3a954a..2cc45b0bb 100644 --- a/strings/base_xaml_typename.h +++ b/strings/base_xaml_typename.h @@ -1,5 +1,5 @@ -namespace winrt::impl +WINRT_EXPORT namespace winrt::impl { template struct xaml_typename_name diff --git a/test/nuget/NuGetTest.sln b/test/nuget/NuGetTest.sln index 310b0f252..c836b2551 100644 --- a/test/nuget/NuGetTest.sln +++ b/test/nuget/NuGetTest.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.33516.290 @@ -47,6 +47,21 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleApplication1", "Cons EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestProxyStub", "TestProxyStub\TestProxyStub.vcxproj", "{98E28FC8-2EB7-4544-9B6A-941462C6D3E2}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleApp", "TestModuleApp\TestModuleApp.vcxproj", "{8679913F-D38D-468F-A8B7-75B187A7A8BC}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleBuilder", "TestModuleBuilder\TestModuleBuilder.vcxproj", "{AEE91B86-AA17-4C22-B0C2-08B2C287E375}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent1", "TestModuleComponent1\TestModuleComponent1.vcxproj", "{F54D9A50-84D7-4953-8350-BEFE73CC36F6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent2", "TestModuleComponent2\TestModuleComponent2.vcxproj", "{126E9412-E861-47C6-8684-C8F9BF32C0BD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleConsumerApp", "TestModuleConsumerApp\TestModuleConsumerApp.vcxproj", "{FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}" + ProjectSection(ProjectDependencies) = postProject + {AEE91B86-AA17-4C22-B0C2-08B2C287E375} = {AEE91B86-AA17-4C22-B0C2-08B2C287E375} + {F54D9A50-84D7-4953-8350-BEFE73CC36F6} = {F54D9A50-84D7-4953-8350-BEFE73CC36F6} + {126E9412-E861-47C6-8684-C8F9BF32C0BD} = {126E9412-E861-47C6-8684-C8F9BF32C0BD} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -279,6 +294,58 @@ Global {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x64.Build.0 = Release|x64 {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x86.ActiveCfg = Release|Win32 {98E28FC8-2EB7-4544-9B6A-941462C6D3E2}.Release|x86.Build.0 = Release|Win32 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|ARM64.Build.0 = Debug|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x64.ActiveCfg = Debug|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x64.Build.0 = Debug|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x86.ActiveCfg = Debug|Win32 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Debug|x86.Build.0 = Debug|Win32 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|ARM64.ActiveCfg = Release|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|ARM64.Build.0 = Release|ARM64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x64.ActiveCfg = Release|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x64.Build.0 = Release|x64 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x86.ActiveCfg = Release|Win32 + {8679913F-D38D-468F-A8B7-75B187A7A8BC}.Release|x86.Build.0 = Release|Win32 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|ARM64.ActiveCfg = Release|ARM64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|x64.ActiveCfg = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|x64.Build.0 = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Debug|x86.ActiveCfg = Release|Win32 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|ARM64.ActiveCfg = Release|ARM64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|ARM64.Build.0 = Release|ARM64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x64.ActiveCfg = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x64.Build.0 = Release|x64 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x86.ActiveCfg = Release|Win32 + {AEE91B86-AA17-4C22-B0C2-08B2C287E375}.Release|x86.Build.0 = Release|Win32 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|ARM64.ActiveCfg = Release|ARM64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|x64.ActiveCfg = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|x64.Build.0 = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Debug|x86.ActiveCfg = Release|Win32 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|ARM64.ActiveCfg = Release|ARM64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|ARM64.Build.0 = Release|ARM64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x64.ActiveCfg = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x64.Build.0 = Release|x64 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x86.ActiveCfg = Release|Win32 + {F54D9A50-84D7-4953-8350-BEFE73CC36F6}.Release|x86.Build.0 = Release|Win32 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|ARM64.ActiveCfg = Release|ARM64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|x64.ActiveCfg = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|x64.Build.0 = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Debug|x86.ActiveCfg = Release|Win32 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|ARM64.ActiveCfg = Release|ARM64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|ARM64.Build.0 = Release|ARM64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x64.ActiveCfg = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x64.Build.0 = Release|x64 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x86.ActiveCfg = Release|Win32 + {126E9412-E861-47C6-8684-C8F9BF32C0BD}.Release|x86.Build.0 = Release|Win32 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|ARM64.ActiveCfg = Release|ARM64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|x64.ActiveCfg = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|x64.Build.0 = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Debug|x86.ActiveCfg = Release|Win32 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|ARM64.ActiveCfg = Release|ARM64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|ARM64.Build.0 = Release|ARM64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x64.ActiveCfg = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x64.Build.0 = Release|x64 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x86.ActiveCfg = Release|Win32 + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/nuget/TestModuleApp/CustomDependencyObject.cpp b/test/nuget/TestModuleApp/CustomDependencyObject.cpp new file mode 100644 index 000000000..81631e387 --- /dev/null +++ b/test/nuget/TestModuleApp/CustomDependencyObject.cpp @@ -0,0 +1,8 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt.Windows.Foundation; +import winrt.Windows.UI.Xaml; + +#include "CustomDependencyObject.h" +#include "CustomDependencyObject.g.cpp" diff --git a/test/nuget/TestModuleApp/CustomDependencyObject.h b/test/nuget/TestModuleApp/CustomDependencyObject.h new file mode 100644 index 000000000..79fd6b1ff --- /dev/null +++ b/test/nuget/TestModuleApp/CustomDependencyObject.h @@ -0,0 +1,23 @@ +#pragma once +#include "CustomDependencyObject.g.h" + +namespace winrt::TestModuleApp::implementation +{ + struct CustomDependencyObject : CustomDependencyObjectT + { + CustomDependencyObject() = default; + + hstring Name() { return m_name; } + void Name(hstring const& value) { m_name = value; } + + private: + hstring m_name; + }; +} + +namespace winrt::TestModuleApp::factory_implementation +{ + struct CustomDependencyObject : CustomDependencyObjectT + { + }; +} diff --git a/test/nuget/TestModuleApp/ModuleTestHelper.cpp b/test/nuget/TestModuleApp/ModuleTestHelper.cpp new file mode 100644 index 000000000..1e3d7bc1a --- /dev/null +++ b/test/nuget/TestModuleApp/ModuleTestHelper.cpp @@ -0,0 +1,7 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt.Windows.Foundation; + +#include "ModuleTestHelper.h" +#include "ModuleTestHelper.g.cpp" diff --git a/test/nuget/TestModuleApp/ModuleTestHelper.h b/test/nuget/TestModuleApp/ModuleTestHelper.h new file mode 100644 index 000000000..c5a699c6b --- /dev/null +++ b/test/nuget/TestModuleApp/ModuleTestHelper.h @@ -0,0 +1,27 @@ +#pragma once +#include "ModuleTestHelper.g.h" + +namespace winrt::TestModuleApp::implementation +{ + struct ModuleTestHelper : ModuleTestHelperT + { + ModuleTestHelper() = default; + + Windows::Foundation::Uri CreateUri(hstring const& url) + { + return Windows::Foundation::Uri(url); + } + + Windows::Foundation::IAsyncOperation GetStringAsync() + { + co_return L"hello from module"; + } + }; +} + +namespace winrt::TestModuleApp::factory_implementation +{ + struct ModuleTestHelper : ModuleTestHelperT + { + }; +} diff --git a/test/nuget/TestModuleApp/PropertySheet.props b/test/nuget/TestModuleApp/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleApp/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleApp/TestModuleApp.def b/test/nuget/TestModuleApp/TestModuleApp.def new file mode 100644 index 000000000..53d2e7cbf --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/test/nuget/TestModuleApp/TestModuleApp.idl b/test/nuget/TestModuleApp/TestModuleApp.idl new file mode 100644 index 000000000..ab3c56707 --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.idl @@ -0,0 +1,20 @@ +namespace TestModuleApp +{ + // A type that inherits from Windows.UI.Xaml.DependencyObject to exercise + // cross-namespace inheritance in module builds. + [default_interface] + unsealed runtimeclass CustomDependencyObject : Windows.UI.Xaml.DependencyObject + { + CustomDependencyObject(); + String Name{ get; set; }; + } + + // A simple runtime class using platform SDK types. + [default_interface] + runtimeclass ModuleTestHelper + { + ModuleTestHelper(); + Windows.Foundation.Uri CreateUri(String url); + Windows.Foundation.IAsyncOperation GetStringAsync(); + } +} diff --git a/test/nuget/TestModuleApp/TestModuleApp.vcxproj b/test/nuget/TestModuleApp/TestModuleApp.vcxproj new file mode 100644 index 000000000..522e0d911 --- /dev/null +++ b/test/nuget/TestModuleApp/TestModuleApp.vcxproj @@ -0,0 +1,127 @@ + + + + + true + true + true + Windows;TestModuleApp + true + {8679913F-D38D-468F-A8B7-75B187A7A8BC} + TestModuleApp + TestModuleApp + en-US + 14.0 + + + + + Debug + Win32 + + + Debug + x64 + + + Debug + ARM64 + + + Release + Win32 + + + Release + x64 + + + Release + ARM64 + + + + Application + v145 + Unicode + + + true + + + false + true + + + + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj + 5311;28204 + NOMINMAX;%(PreprocessorDefinitions) + true + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + + TestModuleApp.idl + + + TestModuleApp.idl + + + + + Create + + + + TestModuleApp.idl + + + TestModuleApp.idl + + + + + + + + + + + + + diff --git a/test/nuget/TestModuleApp/main.cpp b/test/nuget/TestModuleApp/main.cpp new file mode 100644 index 000000000..907e4afed --- /dev/null +++ b/test/nuget/TestModuleApp/main.cpp @@ -0,0 +1,42 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import std; +import winrt.Windows.Foundation; +import winrt.Windows.UI.Xaml; + +#include "ModuleTestHelper.h" +#include "CustomDependencyObject.h" + +using namespace winrt; +using namespace Windows::Foundation; + +int main() +{ + init_apartment(); + + // Test ModuleTestHelper + auto helper = TestModuleApp::ModuleTestHelper(); + auto uri = helper.CreateUri(L"https://example.com"); + std::printf("URI: %ls\n", uri.AbsoluteUri().c_str()); + + auto str = helper.GetStringAsync().get(); + std::printf("Async: %ls\n", str.c_str()); + + // Test CustomDependencyObject (inherits from DependencyObject) + // Note: DependencyObject requires XAML runtime, which isn't available in a console app. + // We verify the type compiles and links correctly; runtime creation would need a XAML host. + try + { + auto obj = winrt::make(); + obj.Name(L"test"); + std::printf("Name: %ls\n", obj.Name().c_str()); + } + catch (winrt::hresult_error const& e) + { + std::printf("CustomDependencyObject: expected runtime error (no XAML host): %ls\n", e.message().c_str()); + } + + std::printf("All module tests passed.\n"); + return 0; +} diff --git a/test/nuget/TestModuleApp/pch.cpp b/test/nuget/TestModuleApp/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleApp/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleApp/pch.h b/test/nuget/TestModuleApp/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleApp/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/test/nuget/TestModuleBuilder/PropertySheet.props b/test/nuget/TestModuleBuilder/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleBuilder/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj new file mode 100644 index 000000000..86a3d7c89 --- /dev/null +++ b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj @@ -0,0 +1,66 @@ + + + + + true + Windows.Foundation + true + {AEE91B86-AA17-4C22-B0C2-08B2C287E375} + TestModuleBuilder + TestModuleBuilder + en-US + 14.0 + + + + + Release + Win32 + + + Release + x64 + + + Release + ARM64 + + + + StaticLibrary + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj + true + NOMINMAX;%(PreprocessorDefinitions) + + + + + + + + Create + + + + + diff --git a/test/nuget/TestModuleBuilder/pch.cpp b/test/nuget/TestModuleBuilder/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleBuilder/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleBuilder/pch.h b/test/nuget/TestModuleBuilder/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleBuilder/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/test/nuget/TestModuleComponent1/Greeter.cpp b/test/nuget/TestModuleComponent1/Greeter.cpp new file mode 100644 index 000000000..04c668e10 --- /dev/null +++ b/test/nuget/TestModuleComponent1/Greeter.cpp @@ -0,0 +1,8 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import winrt_base; +import winrt.Windows.Foundation; + +#include "Greeter.h" +#include "Greeter.g.cpp" diff --git a/test/nuget/TestModuleComponent1/Greeter.h b/test/nuget/TestModuleComponent1/Greeter.h new file mode 100644 index 000000000..726fb7d22 --- /dev/null +++ b/test/nuget/TestModuleComponent1/Greeter.h @@ -0,0 +1,25 @@ +#pragma once +#include "Greeter.g.h" + +namespace winrt::TestModuleComponent1::implementation +{ + struct Greeter : GreeterT + { + Greeter() : m_name(L"World") {} + Greeter(hstring const& name) : m_name(name) {} + + hstring Name() { return m_name; } + hstring Greet() { return L"Hello, " + m_name + L"!"; } + Windows::Foundation::Uri Homepage() { return Windows::Foundation::Uri(L"https://example.com/" + m_name); } + + private: + hstring m_name; + }; +} + +namespace winrt::TestModuleComponent1::factory_implementation +{ + struct Greeter : GreeterT + { + }; +} diff --git a/test/nuget/TestModuleComponent1/PropertySheet.props b/test/nuget/TestModuleComponent1/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleComponent1/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.def b/test/nuget/TestModuleComponent1/TestModuleComponent1.def new file mode 100644 index 000000000..53d2e7cbf --- /dev/null +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.idl b/test/nuget/TestModuleComponent1/TestModuleComponent1.idl new file mode 100644 index 000000000..73a983ee2 --- /dev/null +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.idl @@ -0,0 +1,12 @@ +namespace TestModuleComponent1 +{ + [default_interface] + runtimeclass Greeter + { + Greeter(); + Greeter(String name); + String Name{ get; }; + String Greet(); + Windows.Foundation.Uri Homepage{ get; }; + } +} diff --git a/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj new file mode 100644 index 000000000..669b25ff0 --- /dev/null +++ b/test/nuget/TestModuleComponent1/TestModuleComponent1.vcxproj @@ -0,0 +1,92 @@ + + + + + true + true + true + true + {F54D9A50-84D7-4953-8350-BEFE73CC36F6} + TestModuleComponent1 + TestModuleComponent1 + en-US + 14.0 + + + + + Release + Win32 + + + Release + x64 + + + Release + ARM64 + + + + DynamicLibrary + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj + true + _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + false + TestModuleComponent1.def + + + + + + TestModuleComponent1.idl + + + + + Create + + + TestModuleComponent1.idl + + + + + + + + + + + + + true + + + + + diff --git a/test/nuget/TestModuleComponent1/pch.cpp b/test/nuget/TestModuleComponent1/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleComponent1/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleComponent1/pch.h b/test/nuget/TestModuleComponent1/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleComponent1/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/test/nuget/TestModuleComponent2/GreeterGroup.cpp b/test/nuget/TestModuleComponent2/GreeterGroup.cpp new file mode 100644 index 000000000..7c6410b44 --- /dev/null +++ b/test/nuget/TestModuleComponent2/GreeterGroup.cpp @@ -0,0 +1,9 @@ +#include "pch.h" + +#define WINRT_IMPORT_MODULE +import std; +import winrt.Windows.Foundation; +import winrt.TestModuleComponent1; + +#include "GreeterGroup.h" +#include "GreeterGroup.g.cpp" diff --git a/test/nuget/TestModuleComponent2/GreeterGroup.h b/test/nuget/TestModuleComponent2/GreeterGroup.h new file mode 100644 index 000000000..461570425 --- /dev/null +++ b/test/nuget/TestModuleComponent2/GreeterGroup.h @@ -0,0 +1,36 @@ +#pragma once +#include "GreeterGroup.g.h" + +namespace winrt::TestModuleComponent2::implementation +{ + struct GreeterGroup : GreeterGroupT + { + GreeterGroup() = default; + + void Add(winrt::TestModuleComponent1::Greeter const& greeter) + { + m_greeters.push_back(greeter); + } + + hstring GreetAll() + { + hstring result; + for (auto const& g : m_greeters) + { + if (!result.empty()) result = result + L", "; + result = result + g.Greet(); + } + return result; + } + + private: + std::vector m_greeters; + }; +} + +namespace winrt::TestModuleComponent2::factory_implementation +{ + struct GreeterGroup : GreeterGroupT + { + }; +} diff --git a/test/nuget/TestModuleComponent2/PropertySheet.props b/test/nuget/TestModuleComponent2/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleComponent2/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.def b/test/nuget/TestModuleComponent2/TestModuleComponent2.def new file mode 100644 index 000000000..53d2e7cbf --- /dev/null +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.idl b/test/nuget/TestModuleComponent2/TestModuleComponent2.idl new file mode 100644 index 000000000..e45eed438 --- /dev/null +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.idl @@ -0,0 +1,10 @@ +namespace TestModuleComponent2 +{ + [default_interface] + runtimeclass GreeterGroup + { + GreeterGroup(); + void Add(TestModuleComponent1.Greeter greeter); + String GreetAll(); + } +} diff --git a/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj new file mode 100644 index 000000000..ff4982965 --- /dev/null +++ b/test/nuget/TestModuleComponent2/TestModuleComponent2.vcxproj @@ -0,0 +1,93 @@ + + + + + true + true + true + true + {126E9412-E861-47C6-8684-C8F9BF32C0BD} + TestModuleComponent2 + TestModuleComponent2 + en-US + 14.0 + + + + + Release + Win32 + + + Release + x64 + + + Release + ARM64 + + + + DynamicLibrary + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj + true + _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + false + TestModuleComponent2.def + + + + + + TestModuleComponent2.idl + + + + + Create + + + TestModuleComponent2.idl + + + + + + + + + + + + + true + + + + + + diff --git a/test/nuget/TestModuleComponent2/pch.cpp b/test/nuget/TestModuleComponent2/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleComponent2/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleComponent2/pch.h b/test/nuget/TestModuleComponent2/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleComponent2/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/test/nuget/TestModuleConsumerApp/PropertySheet.props b/test/nuget/TestModuleConsumerApp/PropertySheet.props new file mode 100644 index 000000000..379c2c3a2 --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/PropertySheet.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj new file mode 100644 index 000000000..910586a0c --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj @@ -0,0 +1,76 @@ + + + + + true + true + {FB7FEAA7-09DE-465C-BA0E-60374D3EFFD9} + TestModuleConsumerApp + TestModuleConsumerApp + en-US + 14.0 + + + + + Release + Win32 + + + Release + x64 + + + Release + ARM64 + + + + Application + v145 + Unicode + + + false + true + + + + + + + + + + Use + pch.h + stdcpplatest + Level4 + true + %(AdditionalOptions) /bigobj + true + NOMINMAX;%(PreprocessorDefinitions) + + + Console + + + + + + + + Create + + + + + + true + + + + + + + diff --git a/test/nuget/TestModuleConsumerApp/main.cpp b/test/nuget/TestModuleConsumerApp/main.cpp new file mode 100644 index 000000000..da930703b --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/main.cpp @@ -0,0 +1,32 @@ +#include "pch.h" + +import std; +import winrt.Windows.Foundation; +import winrt.TestModuleComponent1; +import winrt.TestModuleComponent2; + +using namespace winrt; +using namespace Windows::Foundation; + +int main() +{ + init_apartment(); + + // Platform types from pre-built modules + Uri uri(L"https://example.com/consumer"); + std::printf("URI: %ls\n", uri.AbsoluteUri().c_str()); + + // Component1 + auto greeter = TestModuleComponent1::Greeter(L"Modules"); + std::printf("Greet: %ls\n", greeter.Greet().c_str()); + std::printf("Homepage: %ls\n", greeter.Homepage().AbsoluteUri().c_str()); + + // Component2 (depends on Component1) + auto group = TestModuleComponent2::GreeterGroup(); + group.Add(TestModuleComponent1::Greeter(L"Alice")); + group.Add(TestModuleComponent1::Greeter(L"Bob")); + std::printf("GreetAll: %ls\n", group.GreetAll().c_str()); + + std::printf("All consumer tests passed.\n"); + return 0; +} diff --git a/test/nuget/TestModuleConsumerApp/pch.cpp b/test/nuget/TestModuleConsumerApp/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleConsumerApp/pch.h b/test/nuget/TestModuleConsumerApp/pch.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/pch.h @@ -0,0 +1 @@ +#pragma once diff --git a/test/test_cpp20_module/collections.cpp b/test/test_cpp20_module/collections.cpp new file mode 100644 index 000000000..b80e123d2 --- /dev/null +++ b/test/test_cpp20_module/collections.cpp @@ -0,0 +1,57 @@ +#include "pch.h" + +import winrt.Windows.Foundation; + +using namespace winrt; +using namespace Windows::Foundation::Collections; + +TEST_CASE("module_vector") +{ + auto vec = single_threaded_vector(); + vec.Append(10); + vec.Append(20); + vec.Append(30); + REQUIRE(vec.Size() == 3); + REQUIRE(vec.GetAt(0) == 10); + REQUIRE(vec.GetAt(2) == 30); + + vec.RemoveAtEnd(); + REQUIRE(vec.Size() == 2); +} + +TEST_CASE("module_map") +{ + auto map = single_threaded_map(); + map.Insert(L"key1", L"value1"); + map.Insert(L"key2", L"value2"); + REQUIRE(map.Size() == 2); + REQUIRE(map.Lookup(L"key1") == L"value1"); + REQUIRE(map.HasKey(L"key2")); + REQUIRE(!map.HasKey(L"key3")); +} + +TEST_CASE("module_observable_vector") +{ + auto vec = single_threaded_observable_vector(); + int change_count = 0; + auto token = vec.VectorChanged([&](auto&&, auto&&) { ++change_count; }); + vec.Append(1); + vec.Append(2); + REQUIRE(change_count == 2); + vec.VectorChanged(token); +} + +TEST_CASE("module_iterable") +{ + auto vec = single_threaded_vector(); + vec.Append(1); + vec.Append(2); + vec.Append(3); + + int sum = 0; + for (auto v : vec) + { + sum += v; + } + REQUIRE(sum == 6); +} diff --git a/test/test_cpp20_module/coroutines.cpp b/test/test_cpp20_module/coroutines.cpp new file mode 100644 index 000000000..1553b1c6d --- /dev/null +++ b/test/test_cpp20_module/coroutines.cpp @@ -0,0 +1,58 @@ +#include "pch.h" + +import std; +import winrt.Windows.Foundation; + +using namespace winrt; +using namespace Windows::Foundation; + +IAsyncAction do_nothing_async() +{ + co_return; +} + +IAsyncOperation return_42_async() +{ + co_return 42; +} + +IAsyncOperation return_string_async() +{ + co_return L"module coroutine"; +} + +IAsyncAction chain_async() +{ + auto result = co_await return_string_async(); + REQUIRE(!result.empty()); +} + +IAsyncOperation slow_operation() +{ + co_await resume_after(std::chrono::hours(1)); + co_return 0; +} + +TEST_CASE("module_async_action") +{ + auto action = do_nothing_async(); + action.get(); + REQUIRE(action.Status() == AsyncStatus::Completed); +} + +TEST_CASE("module_async_operation") +{ + REQUIRE(return_42_async().get() == 42); +} + +TEST_CASE("module_async_chain") +{ + chain_async().get(); +} + +TEST_CASE("module_async_cancel") +{ + auto op = slow_operation(); + op.Cancel(); + REQUIRE(op.Status() == AsyncStatus::Canceled); +} diff --git a/test/test_cpp20_module/foundation.cpp b/test/test_cpp20_module/foundation.cpp new file mode 100644 index 000000000..b5adbbe48 --- /dev/null +++ b/test/test_cpp20_module/foundation.cpp @@ -0,0 +1,45 @@ +#include "pch.h" + +import std; +import winrt.Windows.Foundation; + +using namespace winrt; +using namespace Windows::Foundation; + +TEST_CASE("module_uri") +{ + Uri uri(L"https://example.com/path?query=1"); + REQUIRE(!uri.AbsoluteUri().empty()); + REQUIRE(uri.Host() == L"example.com"); + REQUIRE(uri.Path() == L"/path"); +} + +TEST_CASE("module_property_value") +{ + auto pv = PropertyValue::CreateInt32(42); + REQUIRE(pv.as().GetInt32() == 42); + + auto pvs = PropertyValue::CreateString(L"hello"); + REQUIRE(pvs.as().GetString() == L"hello"); +} + +TEST_CASE("module_hstring") +{ + hstring text = L"C++/WinRT modules"; + REQUIRE(!text.empty()); + REQUIRE(text.size() == 17); + + hstring empty; + REQUIRE(empty.empty()); + REQUIRE(empty.size() == 0); +} + +TEST_CASE("module_events") +{ + winrt::event> my_event; + int received = 0; + auto token = my_event.add([&](auto&&, int value) { received = value; }); + my_event(nullptr, 42); + REQUIRE(received == 42); + my_event.remove(token); +} diff --git a/test/test_cpp20_module/main.cpp b/test/test_cpp20_module/main.cpp new file mode 100644 index 000000000..415e26f0f --- /dev/null +++ b/test/test_cpp20_module/main.cpp @@ -0,0 +1,24 @@ +#include +#define CATCH_CONFIG_RUNNER +#define CATCH_CONFIG_WINDOWS_SEH +#include "catch.hpp" + +import winrt_base; + +using namespace winrt; + +int main(int const argc, char** argv) +{ + init_apartment(); + std::set_terminate([] { reportFatal("Abnormal termination"); ExitProcess(1); }); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + (void)_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + (void)_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + return Catch::Session().run(argc, argv); +} + +CATCH_TRANSLATE_EXCEPTION(hresult_error const& e) +{ + return to_string(e.message()); +} diff --git a/test/test_cpp20_module/pch.cpp b/test/test_cpp20_module/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/test_cpp20_module/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/test_cpp20_module/pch.h b/test/test_cpp20_module/pch.h new file mode 100644 index 000000000..d0eb301ac --- /dev/null +++ b/test/test_cpp20_module/pch.h @@ -0,0 +1,3 @@ +#pragma once + +#include "catch.hpp" diff --git a/test/test_cpp20_module/source_location.cpp b/test/test_cpp20_module/source_location.cpp new file mode 100644 index 000000000..ebf5139c3 --- /dev/null +++ b/test/test_cpp20_module/source_location.cpp @@ -0,0 +1,13 @@ +#include "pch.h" + +import std; +import winrt.Windows.Foundation; + +TEST_CASE("module_source_location") +{ + // Verify that slim_source_location works across the module boundary + auto loc = winrt::impl::slim_source_location::current(); + REQUIRE(loc.line() > 0); + std::string_view file(loc.file_name()); + REQUIRE(file.find("source_location.cpp") != std::string_view::npos); +} diff --git a/test/test_cpp20_module/test_cpp20_module.vcxproj b/test/test_cpp20_module/test_cpp20_module.vcxproj new file mode 100644 index 000000000..ad20d2e8a --- /dev/null +++ b/test/test_cpp20_module/test_cpp20_module.vcxproj @@ -0,0 +1,105 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + 16.0 + {B8E3A5CE-4E91-4F27-9B02-E0CAF7E10D72} + test_cpp20_module + test_cpp20_module + 10.0 + v145 + + + + Application + v145 + + + true + + + false + true + + + + + + + + + $(IntDir)Generated Files\ + + + + Use + pch.h + stdcpplatest + $(CppWinRTGenDir);..\;%(AdditionalIncludeDirectories) + NOMINMAX;%(PreprocessorDefinitions) + Level4 + true + 5311 + /bigobj + true + true + + + Console + ole32.lib;windowsapp.lib;%(AdditionalDependencies) + + + "$(CppWinRTDir)cppwinrt.exe" -in local -out "$(CppWinRTGenDir)." -modules -base -verbose -module_include "Windows.Foundation" + + + + + + + CompileAsCppModule + true + NotUsing + + + + + + + + + Create + + + NotUsing + + + + + + + \ No newline at end of file