diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..ecfa8f944 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,102 @@ +# C++/WinRT Repository - Copilot Instructions + +## Project Overview + +C++/WinRT is a C++ language projection for Windows Runtime APIs. The `cppwinrt.exe` +code generator reads WinRT metadata (.winmd) and produces C++ headers and a C++20 +module interface unit (winrt.ixx). + +## Repository Structure + +- `cppwinrt/` — The code generator source (cppwinrt.exe) +- `strings/` — Handwritten C++ fragments embedded into generated output by the prebuild step +- `prebuild/` — Embeds strings/*.h content into strings.h/strings.cpp as string literals +- `nuget/` — NuGet package files (props, targets, rules) for customer integration +- `natvis/` — Debugger visualizer (directly includes strings/*.h, not generated output) +- `test/` — Test projects +- `_build/` — Build output +- `docs/` — Documentation including modules.md and modules-internals.md + +## Build System + +- Uses MSBuild with Visual Studio. Build from a VS Developer PowerShell. +- `Directory.Build.Props` sets shared properties (toolset, output paths, language standard). +- `$(OutDir)` = `$(SolutionDir)_build\$(Platform)\$(Configuration)\` +- `$(IntDir)` = `$(SolutionDir)_build\$(Platform)\$(Configuration)\temp\$(ProjectName)\` +- The prebuild step must run before cppwinrt can compile (it generates strings.h/strings.cpp). + +### Key Build Commands + +```powershell +# Build just the code generator +msbuild cppwinrt.sln /t:cppwinrt /p:Configuration=Debug /p:Platform=x64 /v:minimal + +# Build a test project (pass SolutionDir for path resolution) +msbuild test\test_cpp20\test_cpp20.vcxproj /p:Configuration=Debug /p:Platform=x64 /p:SolutionDir=d:\Repos\GitHub\cppwinrt\ + +# Generate SDK projection +.\_build\x64\Debug\cppwinrt.exe -in sdk -out .\_build\x64\Debug\ +``` + +## Code Generator Architecture + +### String Fragment System + +The `strings/base_*.h` files are NOT headers that get #included at build time. +They are embedded as string literals by the prebuild step into `strings.cpp`. +The code generator writes them into generated output files (base.h, winrt.ixx, etc.). + +When modifying strings/*.h files: +1. Rebuild `prebuild` and then `cppwinrt` to pick up changes +2. Regenerate the projection (`cppwinrt.exe -in sdk -out ...`) +3. Then rebuild test projects + +### Text Writer System + +- `text_writer.h` — Base writer with `%` placeholder substitution +- `type_writers.h` — The `writer` class with `write_root_include`, `write_depends`, etc. +- `code_writers.h` — High-level writers: `write_version_assert`, `write_open_file_guard`, etc. +- `file_writers.h` — File-level orchestration: `write_base_h`, `write_namespace_h`, etc. +- `component_writers.h` — Component stub generation: `write_module_g_cpp`, `write_component_g_h`, etc. + +### Generated File Layers (per namespace) + +- `.0.h` — Forward declarations, enums, ABI, consume structs, template specializations +- `.1.h` — Interface type definitions +- `.2.h` — Class/struct/delegate type definitions +- Main `.h` — Consume definitions, produce implementations, std::hash specializations + +## C++20 Modules + +See `docs/modules.md` for user-facing documentation and `docs/modules-internals.md` +for maintainer details. + +### Key Design Decisions + +- `winrt::impl` is exported from the module so component headers can specialize + templates (`category`, `abi`, `guid_v`, etc.) after `import winrt;` +- `base_includes.h` has platform headers only; `base_std_includes.h` has std library + headers. The ixx global module fragment uses both as raw #includes. base.h uses + the std includes conditionally (import std vs #include). +- `base_module_macros.h` provides macros that don't cross module boundaries + (WINRT_EXPORT, WINRT_IMPL_EMPTY_BASES, etc.). It's the single source of truth; + `base_macros.h` has `#ifndef` fallbacks for direct consumers like natvis. +- Generated component files (`.g.h`, `module.g.cpp`) use `import winrt;` when + `-module` flag is passed. `import std;` is conditional on `WINRT_IMPORT_STD`. +- The natvis project includes strings/*.h directly — any new strings/ files must + be added to natvis/pch.h too. + +### Common Pitfalls + +- `` transitively includes STL headers on newer MSVC. Can't use + `import std;` in the ixx purview because it conflicts with these transitive includes. +- `#pragma once` prevents re-inclusion but macros from included headers DO persist + from the global module fragment into the purview. +- PCH + conditional compilation: `#include "pch.h"` must be the FIRST non-comment + line in a file. It cannot be inside `#ifdef` blocks. +- `PlatformToolset` in `Directory.Build.Props` overrides per-project settings in + the Globals PropertyGroup. To override, set it in Configuration PropertyGroups. +- `WINRT_EXPORT` must be defined before any code that uses it. When strings/ files + are included directly (natvis), `base_module_macros.h` must come before `base_macros.h`. +- `extern "C++"` is needed on exported `__declspec(selectany)` variables + (`winrt_to_hresult_handler` etc.) to prevent module linkage. diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md new file mode 100644 index 000000000..4041a2618 --- /dev/null +++ b/.github/instructions/modules.instructions.md @@ -0,0 +1,135 @@ +--- +applyTo: "strings/**,cppwinrt/**,nuget/**,test/test_cpp20_module_winrt/**,test/test_cpp23_module_winrt/**,test/test_cpp20_module/**,test/test_component_module/**,test/nuget/TestModule*/**" +--- + +# C++20 Module Code Generation + +## When Modifying strings/*.h Files + +These files are embedded as string literals by the prebuild step. They are NOT +normal headers: + +1. Changes require rebuilding prebuild → cppwinrt → regenerating projection +2. The natvis project includes these files directly via its pch.h — add any new + files there too +3. `WINRT_EXPORT` prefixes `namespace winrt` (public) AND `namespace winrt::impl` + (for component template specialization). Both expansions are needed. +4. `base_module_macros.h` is the single source of truth for macros shared between + module and header modes. Don't duplicate definitions — use `#ifndef` guards. + +## Module-Aware Component Code Generation (WINRT_MODULE macro) + +Generated component files use `#ifdef WINRT_MODULE` guards to support both +module and header modes. No special cppwinrt.exe flag is needed — the +`WINRT_MODULE` preprocessor macro is set at the project level by the NuGet +targets (or manually for non-NuGet projects). + +### module.g.cpp +- Keeps `#include "pch.h"` (PCH is compatible with modules) +- `#ifdef WINRT_MODULE`: uses `import winrt;` (and conditional `import std;`) +- `#else`: uses `#include "winrt/base.h"` + +### .g.h (component base template) +- `#ifdef WINRT_MODULE`: emits `#include "winrt/base_macros.h"` for macros, + conditional `import std;`, `import winrt;` +- Always includes component's own headers. Platform deps are skipped by + per-namespace `WINRT_MODULE_NS_*` guards; component cross-namespace deps + are included normally. + +### .g.cpp (factory + optimized constructors) +- Always emits the `winrt_make_*` factory function +- Constructor/static overrides are guarded by `#ifndef WINRT_MODULE` + (they come from the projection header via the module in module mode) + +## Macro Scoping + +Two project-level macros and one set of per-namespace macros control behavior: + +- `WINRT_BUILD_MODULE` — Defined by cppwinrt inside winrt.ixx's global module + fragment. Controls base.h skip in version assert. Does NOT include + `winrt_module_namespaces.h` (inside the ixx, all deps must resolve). + Never set by users or NuGet targets. +- `WINRT_MODULE` — Defined project-wide by NuGet targets. Controls .g.h/.g.cpp + behavior, version assert base.h skip, AND triggers inclusion of + `winrt_module_namespaces.h` for per-namespace guards. +- `WINRT_MODULE_NS_*` — Per-namespace macros (e.g. `WINRT_MODULE_NS_Windows_Foundation`) + defined in the generated `winrt_module_namespaces.h`. Each cross-namespace + `#include` dep is guarded by `#ifndef WINRT_MODULE_NS_`. Only + namespaces in the module are skipped; component and other deps always resolve. + +## Per-Namespace Include Guards + +Generated namespace headers use per-namespace guards for cross-namespace deps: +```cpp +#ifndef WINRT_MODULE_NS_Windows_Foundation +#include "winrt/impl/Windows.Foundation.0.h" +#endif +``` + +The version assert at the top of each namespace header: +```cpp +#if defined(WINRT_BUILD_MODULE) || defined(WINRT_MODULE) +#include "winrt/base_macros.h" +#endif +#if defined(WINRT_MODULE) && !defined(WINRT_BUILD_MODULE) +#include "winrt/winrt_module_namespaces.h" +#endif +#if !defined(WINRT_BUILD_MODULE) && !defined(WINRT_MODULE) +#include "winrt/base.h" +#endif +``` + +- Cross-namespace dependencies: GUARDED by `WINRT_MODULE_NS_*` + (`write_depends_guarded` / `write_root_include_guarded`) +- Self-namespace dependencies: NOT guarded + (`write_depends` / `write_root_include`) +- base.h include: GUARDED by `WINRT_BUILD_MODULE || WINRT_MODULE` + +## Test Project Architecture + +- `test_module_winrt` — Static lib that builds winrt.ixx. Generates projection + to its own "Generated Files" directory. Other module projects reference its + IFC and OBJ. +- `test_cpp20_module` — Consumer test app. References test_module_winrt's IFC/OBJ. + Uses PCH for non-winrt headers (catch.hpp, Windows.h). +- `test_component_module` — Component DLL. References test_module_winrt's IFC/OBJ. + Has its own MIDL/cppwinrt steps for component projection. + +All module test projects use v143 toolset. `import std;` requires `BuildStlModules=true` +and either v145 toolset or `/std:c++latest` on v143. + +## Module Namespace Filtering (-module_filter) + +The `-module_filter` cppwinrt.exe arg controls which namespaces go into +`winrt.ixx` and `winrt_module_namespaces.h`. All namespace headers are still +generated for textual inclusion — only the module contents are filtered. + +- NuGet property: `CppWinRTModuleFilter` (space-separated prefixes) +- cppwinrt.exe arg: `-module_filter ` (can be repeated) +- Implementation: `settings.ixx_filter` in `main.cpp`/`settings.h` +- The repo test builders use `-module_filter Windows.Foundation` to speed up + the inner loop (5 namespaces instead of 339). + +## NuGet Module Integration (test/nuget/) + +The NuGet targets split module support into two properties: + +- `CppWinRTModuleBuild=true` — Project generates the SDK projection and compiles + winrt.ixx. Exports IFC/OBJ/include paths via `CppWinRTGetModuleOutputs` target. +- `CppWinRTModuleConsume=true` — Project consumes a pre-built module from a + `ProjectReference` to a builder. `CppWinRTResolveModuleReferences` target + resolves paths via `MSBuild` task calling `CppWinRTGetModuleOutputs` on refs. + +Both properties define `WINRT_MODULE` so generated component files use `import winrt;`. +`CppWinRTModuleConsume` auto-sets `CppWinRTEnablePlatformProjection=false`. + +### NuGet Module Test Projects + +- `test/nuget/TestModuleBuilder` — StaticLib, `CppWinRTModuleBuild=true`. Builds + the shared winrt module. No source files — NuGet targets add winrt.ixx. +- `test/nuget/TestModuleConsumerApp` — Console app, `CppWinRTModuleConsume=true`. + References TestModuleBuilder. Uses `import std; import winrt;`. +- `test/nuget/TestModuleComponent` — DLL with IDL, `CppWinRTModuleConsume=true`. + References TestModuleBuilder. Component .g.h files use `import winrt;`. +- `test/nuget/TestModuleSingleProject` — Console app, `CppWinRTModuleBuild=true`. + Builds and consumes module in one project (no separate builder). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb8cafd2b..771595d3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,7 @@ jobs: compiler: [MSVC, clang-cl] arch: [x86, x64, arm64] config: [Debug, Release] - test_exe: [test, test_nocoro, test_cpp20, test_cpp20_no_sourcelocation, test_fast, test_slow, test_old, test_module_lock_custom, test_module_lock_none] + test_exe: [test, test_nocoro, test_cpp20, test_cpp20_no_sourcelocation, test_fast, test_slow, test_old, test_module_lock_custom, test_module_lock_none, test_cpp20_module] exclude: - arch: arm64 config: Debug @@ -100,6 +100,9 @@ jobs: arch: arm64 - compiler: clang-cl config: Release + # Module tests require MSVC (clang-cl doesn't support C++20 modules) + - test_exe: test_cpp20_module + compiler: clang-cl runs-on: windows-latest steps: - uses: actions/checkout@v6 @@ -364,6 +367,30 @@ jobs: run: | cmd /c "$env:VSDevCmd" "&" msbuild /m /clp:ForceConsoleColor "$env:msbuild_config_props" test\nuget\NugetTest.sln + - name: Run module consumer app + if: matrix.arch == 'x64' + run: | + $target_configuration = "${{ matrix.config }}" + $platform_dir = "x64" + $app = "test\nuget\bin\$target_configuration\$platform_dir\TestModuleConsumerApp\TestModuleConsumerApp.exe" + if (Test-Path $app) { + & $app + } else { + echo "::warning::TestModuleConsumerApp not found at $app" + } + + - name: Run module single-project app + if: matrix.arch == 'x64' + run: | + $target_configuration = "${{ matrix.config }}" + $platform_dir = "x64" + $app = "test\nuget\bin\$target_configuration\$platform_dir\TestModuleSingleProject\TestModuleSingleProject.exe" + if (Test-Path $app) { + & $app + } else { + echo "::warning::TestModuleSingleProject not found at $app" + } + build-nuget: name: Build nuget package with MSVC runs-on: windows-latest diff --git a/cppwinrt.sln b/cppwinrt.sln index 3bcfb33bc..34baed7c9 100644 --- a/cppwinrt.sln +++ b/cppwinrt.sln @@ -124,6 +124,31 @@ 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", "{B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}" + ProjectSection(ProjectDependencies) = postProject + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C} = {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_cpp20_module_winrt", "test\test_cpp20_module_winrt\test_cpp20_module_winrt.vcxproj", "{F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}" + ProjectSection(ProjectDependencies) = postProject + {D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_component_module", "test\test_component_module\test_component_module.vcxproj", "{E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}" + ProjectSection(ProjectDependencies) = postProject + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C} = {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_cpp23_module_winrt", "test\test_cpp23_module_winrt\test_cpp23_module_winrt.vcxproj", "{A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12}" + ProjectSection(ProjectDependencies) = postProject + {D613FB39-5035-4043-91E2-BAB323908AF4} = {D613FB39-5035-4043-91E2-BAB323908AF4} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_cpp23_module", "test\\test_cpp23_module\\test_cpp23_module.vcxproj", "{C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F}" + ProjectSection(ProjectDependencies) = postProject + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12} = {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12} + 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 +436,54 @@ 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 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Debug|ARM64.Build.0 = Debug|ARM64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Debug|x64.ActiveCfg = Debug|x64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Debug|x64.Build.0 = Debug|x64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Debug|x86.ActiveCfg = Debug|Win32 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Debug|x86.Build.0 = Debug|Win32 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Release|ARM64.ActiveCfg = Release|ARM64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Release|ARM64.Build.0 = Release|ARM64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Release|x64.ActiveCfg = Release|x64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Release|x64.Build.0 = Release|x64 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Release|x86.ActiveCfg = Release|Win32 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE}.Release|x86.Build.0 = Release|Win32 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Debug|ARM64.Build.0 = Debug|ARM64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Debug|x64.ActiveCfg = Debug|x64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Debug|x64.Build.0 = Debug|x64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Debug|x86.ActiveCfg = Debug|Win32 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Debug|x86.Build.0 = Debug|Win32 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Release|ARM64.ActiveCfg = Release|ARM64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Release|ARM64.Build.0 = Release|ARM64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Release|x64.ActiveCfg = Release|x64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Release|x64.Build.0 = Release|x64 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Release|x86.ActiveCfg = Release|Win32 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C}.Release|x86.Build.0 = Release|Win32 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Debug|ARM64.Build.0 = Debug|ARM64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Debug|x64.ActiveCfg = Debug|x64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Debug|x64.Build.0 = Debug|x64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Debug|x86.ActiveCfg = Debug|Win32 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Debug|x86.Build.0 = Debug|Win32 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Release|ARM64.ActiveCfg = Release|ARM64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Release|ARM64.Build.0 = Release|ARM64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Release|x64.ActiveCfg = Release|x64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Release|x64.Build.0 = Release|x64 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Release|x86.ActiveCfg = Release|Win32 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1}.Release|x86.Build.0 = Release|Win32 + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12}.Debug|x64.ActiveCfg = Debug|x64 + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12}.Debug|x86.ActiveCfg = Debug|Win32 + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12}.Release|ARM64.ActiveCfg = Release|ARM64 + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12}.Release|x64.ActiveCfg = Release|x64 + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12}.Release|x86.ActiveCfg = Release|Win32 + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F}.Debug|x64.ActiveCfg = Debug|x64 + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F}.Debug|x86.ActiveCfg = Debug|Win32 + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F}.Release|ARM64.ActiveCfg = Release|ARM64 + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F}.Release|x64.ActiveCfg = Release|x64 + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F}.Release|x86.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -435,6 +508,11 @@ 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} + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12} = {3C7EA5F8-6E8C-4376-B499-2CAF596384B0} + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F} = {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..1fa8edd5b 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -37,7 +37,28 @@ namespace cppwinrt static void write_version_assert(writer& w) { + // When building the module (WINRT_BUILD_MODULE, inside winrt.ixx) or + // consuming the module (WINRT_MODULE, project-wide), base.h types are + // already available. Macros don't cross module boundaries, so include + // base_macros.h for the version check and other macros. + // When WINRT_MODULE is defined, also include winrt_module_namespaces.h + // which declares per-namespace macros (WINRT_MODULE_NS_*) so that + // cross-namespace #include guards can precisely skip only the namespaces + // that are in the module. + auto format_guard = R"(#if defined(WINRT_BUILD_MODULE) || defined(WINRT_MODULE) +#include "winrt/base_macros.h" +#endif +#if defined(WINRT_MODULE) && !defined(WINRT_BUILD_MODULE) +#include "winrt/winrt_module_namespaces.h" +#endif +#if !defined(WINRT_BUILD_MODULE) && !defined(WINRT_MODULE) +)"; + auto format_end = R"(#endif +)"; + w.write(format_guard); w.write_root_include("base"); + w.write(format_end); + auto format = R"(static_assert(winrt::check_version(CPPWINRT_VERSION, "%"), "Mismatched C++/WinRT headers."); #define CPPWINRT_VERSION "%" )"; @@ -137,7 +158,7 @@ namespace cppwinrt if (found != c.namespaces().end() && has_projected_types(found->second)) { - w.write_root_include(parent); + w.write_root_include_guarded(parent); } else { @@ -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..097bc11c4 100644 --- a/cppwinrt/component_writers.h +++ b/cppwinrt/component_writers.h @@ -136,7 +136,11 @@ namespace cppwinrt static void write_module_g_cpp(writer& w, std::vector const& classes) { + w.write("#ifdef WINRT_MODULE\n"); + w.write("#ifdef WINRT_IMPORT_STD\nimport std;\n#endif\nimport winrt;\n"); + w.write("#else\n"); w.write_root_include("base"); + w.write("#endif\n"); auto format = R"(% bool __stdcall %_can_unload_now() noexcept { @@ -397,9 +401,14 @@ catch (...) { return winrt::to_hresult(); } return; } - auto wrap_type = wrap_type_namespace(w, type_namespace); + // In module mode, the constructor/static definitions are already available + // from the winrt module (via the namespace header folded into winrt.ixx). + // Guard them so they're only emitted in header mode. + { + auto wrap_type = wrap_type_namespace(w, type_namespace); + w.write("#ifndef WINRT_MODULE\n"); - for (auto&&[factory_name, factory] : get_factories(w, type)) + for (auto&&[factory_name, factory] : get_factories(w, type)) { if (factory.type && is_always_disabled(factory.type)) { @@ -546,6 +555,9 @@ catch (...) { return winrt::to_hresult(); } } } } + + w.write("#endif\n"); + } } static void write_component_override_dispatch_base(writer& w, TypeDef const& type) diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index ed9386b4e..d08538514 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -10,7 +10,20 @@ namespace cppwinrt { auto wrap_file_guard = wrap_open_file_guard(w, "BASE"); + w.write(R"( +#ifndef WINRT_IMPL_INCLUDES_HANDLED +)"); w.write(strings::base_includes); + w.write(R"( +#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD) +import std; +#else +)"); + w.write(strings::base_std_includes); + w.write(R"(#endif // __cpp_lib_modules && WINRT_IMPORT_STD +#endif // WINRT_IMPL_INCLUDES_HANDLED +)"); + w.write_root_include("base_macros"); w.write(strings::base_macros); w.write(strings::base_types); w.write(strings::base_extern); @@ -46,6 +59,26 @@ namespace cppwinrt w.flush_to_file(settings.output_folder + "winrt/base.h"); } + // Lightweight header containing only the preprocessor macros needed by + // generated namespace headers. Used when WINRT_MODULE or WINRT_BUILD_MODULE + // is defined (i.e., consuming or building the winrt module). Macros don't + // cross module boundaries, so this provides the macros that base.h would + // normally supply. + static void write_base_macros_h() + { + writer w; + write_preamble(w); + auto format = R"(#pragma once + +#ifndef CPPWINRT_VERSION +#define CPPWINRT_VERSION "%" +#endif +)"; + w.write(format, CPPWINRT_VERSION_STRING); + w.write(strings::base_module_macros); + w.flush_to_file(settings.output_folder + "winrt/base_macros.h"); + } + static void write_fast_forward_h(std::vector const& classes) { writer w; @@ -139,7 +172,7 @@ namespace cppwinrt for (auto&& depends : w.depends) { - w.write_depends(depends.first, '0'); + w.write_depends_guarded(depends.first, '0'); } w.write_depends(w.type_namespace, '0'); @@ -169,7 +202,7 @@ namespace cppwinrt for (auto&& depends : w.depends) { - w.write_depends(depends.first, impl); + w.write_depends_guarded(depends.first, impl); } w.write_depends(w.type_namespace, '1'); @@ -224,7 +257,7 @@ namespace cppwinrt for (auto&& depends : w.depends) { - w.write_depends(depends.first, '2'); + w.write_depends_guarded(depends.first, '2'); } w.write_depends(w.type_namespace, '2'); @@ -250,6 +283,19 @@ namespace cppwinrt write_preamble(w); write_include_guard(w); + // In module mode (WINRT_MODULE defined), SDK types and exported impl + // templates come from 'import winrt;'. Component-specific types come + // from the component's own projection headers. The version assert in + // each header includes winrt_module_namespaces.h (when WINRT_MODULE is + // defined), which provides per-namespace WINRT_MODULE_NS_* macros. + // Cross-namespace deps use these per-namespace guards: platform deps + // (in the module) are skipped, component deps (not in the module) are + // included normally. + w.write("#ifdef WINRT_MODULE\n"); + w.write("#include \"winrt/base_macros.h\"\n"); + w.write("#ifdef WINRT_IMPORT_STD\nimport std;\n#endif\n"); + w.write("import winrt;\n"); + w.write("#endif\n"); for (auto&& depends : w.depends) { w.write_depends(depends.first); diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 70a55b076..f9939f0ff 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -39,6 +39,7 @@ 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 + { "module_filter", 0, option::no_max, "", "Filter which namespaces are included in winrt.ixx (headers are still generated for all)" }, }; static void print_usage(writer& w) @@ -115,6 +116,11 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder settings.exclude.insert(exclude); } + for (auto && ns : args.values("module_filter")) + { + settings.module_filter_include.insert(ns); + } + if (settings.license) { std::string license_arg = args.value("license"); @@ -199,6 +205,14 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder static void build_filters(cache const& c) { + // Build ixx_filter from -module-filter args. + // This allows filtering which namespaces go into winrt.ixx + // without affecting which headers are generated. + if (!settings.module_filter_include.empty()) + { + settings.ixx_filter = { settings.module_filter_include, {} }; + } + if (settings.reference.empty()) { return; @@ -344,9 +358,11 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder group.synchronous(args.exists("synchronous")); writer ixx; write_preamble(ixx); - ixx.write("module;\n"); + ixx.write("module;\n#define WINRT_BUILD_MODULE\n"); ixx.write(strings::base_includes); - ixx.write("\nexport module winrt;\n#define WINRT_EXPORT export\n\n"); + ixx.write(strings::base_std_includes); + ixx.write("\nexport module winrt;\n#define WINRT_EXPORT export\n#define WINRT_IMPL_INCLUDES_HANDLED\n\n"); + ixx.write("#include \"winrt/base.h\"\n\n"); for (auto&&[ns, members] : c.namespaces()) { @@ -355,6 +371,23 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder continue; } + // When -include/-exclude is specified, further filter which + // namespaces go into the ixx and winrt_module_namespaces.h. + // Headers are still generated for all namespaces (so consumers + // can #include them), but only filtered namespaces are in the module. + if (!settings.ixx_filter.empty() && !settings.ixx_filter.includes(members)) + { + // Still generate the headers, just don't add to ixx + 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); + }); + continue; + } + ixx.write("#include \"winrt/%.h\"\n", ns); group.add([&, &ns = ns, &members = members] @@ -369,7 +402,37 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder if (settings.base) { write_base_h(); + write_base_macros_h(); ixx.flush_to_file(settings.output_folder + "winrt/winrt.ixx"); + + // Generate a companion header that declares which namespaces are + // in the module. Consumer projects include this (via WINRT_MODULE) + // so that cross-namespace #include guards can precisely skip only + // namespaces available from the module, while still including + // component and other non-module namespace deps. + // Only generated alongside the ixx (not for component projections + // which may also set -base but shouldn't produce module metadata). + if (!settings.component) + { + writer module_ns; + write_preamble(module_ns); + module_ns.write("#pragma once\n"); + for (auto&&[ns, members] : c.namespaces()) + { + if (!has_projected_types(members) || !settings.projection_filter.includes(members)) + { + continue; + } + if (!settings.ixx_filter.empty() && !settings.ixx_filter.includes(members)) + { + continue; + } + std::string ns_macro{ ns }; + std::replace(ns_macro.begin(), ns_macro.end(), '.', '_'); + module_ns.write("#define WINRT_MODULE_NS_%\n", ns_macro); + } + module_ns.flush_to_file(settings.output_folder + "winrt/winrt_module_namespaces.h"); + } } if (settings.component) diff --git a/cppwinrt/settings.h b/cppwinrt/settings.h index e07df4ea2..9ae251a4c 100644 --- a/cppwinrt/settings.h +++ b/cppwinrt/settings.h @@ -28,6 +28,8 @@ namespace cppwinrt winmd::reader::filter projection_filter; winmd::reader::filter component_filter; + winmd::reader::filter ixx_filter; // Filters which namespaces go into winrt.ixx (from -module-filter) + std::set module_filter_include; // Raw -module-filter values bool fastabi{}; std::map fastabi_cache; diff --git a/cppwinrt/type_writers.h b/cppwinrt/type_writers.h index df17450f1..84df25bf3 100644 --- a/cppwinrt/type_writers.h +++ b/cppwinrt/type_writers.h @@ -564,6 +564,37 @@ namespace cppwinrt settings.brackets ? '>' : '\"'); } + void write_root_include_guarded(std::string_view const& include) + { + // Extract the namespace from the include path. The include is either + // "NS" (for main headers) or "impl/NS.N" (for impl headers). + // The per-namespace guard macro is WINRT_MODULE_NS_. + std::string ns_part; + if (include.size() > 5 && include.substr(0, 5) == "impl/") + { + auto remainder = include.substr(5); // skip "impl/" + auto dot = remainder.rfind('.'); + ns_part = std::string(remainder.substr(0, dot)); + } + else + { + ns_part = std::string(include); + } + std::string ns_macro = ns_part; + std::replace(ns_macro.begin(), ns_macro.end(), '.', '_'); + + auto format = R"(#ifndef WINRT_MODULE_NS_% +#include %winrt/%.h% +#endif +)"; + + write(format, + ns_macro, + settings.brackets ? '<' : '\"', + include, + settings.brackets ? '>' : '\"'); + } + void write_depends(std::string_view const& ns, char impl = 0) { if (impl) @@ -576,6 +607,18 @@ namespace cppwinrt } } + void write_depends_guarded(std::string_view const& ns, char impl = 0) + { + if (impl) + { + write_root_include_guarded(write_temp("impl/%.%", ns, impl)); + } + else + { + write_root_include_guarded(ns); + } + } + void save_header(char impl = 0) { auto filename{ settings.output_folder + "winrt/" }; diff --git a/docs/modules-internals.md b/docs/modules-internals.md new file mode 100644 index 000000000..ddf103fbd --- /dev/null +++ b/docs/modules-internals.md @@ -0,0 +1,448 @@ +# C++/WinRT Code Generator Internals: Module Support + +This document is for maintainers of the cppwinrt code generator. It explains the +internal mechanisms that enable C++20 module support, the design decisions behind +them, and the interactions between the various moving pieces. + +## Table of Contents + +- [File Generation Pipeline](#file-generation-pipeline) +- [winrt.ixx Generation](#winrtixx-generation) +- [Split Standard Library Includes](#split-standard-library-includes) +- [base_macros.h: Why Macros Need Special Handling](#base_macrosh-why-macros-need-special-handling) +- [Per-Namespace Include Guards (WINRT_MODULE_NS_*)](#per-namespace-include-guards-winrt_module_ns) +- [WINRT_EXPORT on Extern Handlers](#winrt_export-on-extern-handlers) +- [The -module Flag: Component Code Generation](#the--module-flag-component-code-generation) +- [The Combined-ixx Approach](#the-combined-ixx-approach) +- [The windowsnumerics.impl.h Warning](#the-windowsnumericsh-warning) +- [import std Integration](#import-std-integration) +- [Code Paths Reference](#code-paths-reference) + +## File Generation Pipeline + +The code generator produces these files (per namespace `NS`): + +``` +write_namespace_0_h(NS) → winrt/impl/NS.0.h (forward decls, enums, ABI, consume structs) +write_namespace_1_h(NS) → winrt/impl/NS.1.h (interface type definitions) +write_namespace_2_h(NS) → winrt/impl/NS.2.h (class/struct/delegate definitions) +write_namespace_h(NS) → winrt/NS.h (consume definitions, produce, std::hash) +write_base_h() → winrt/base.h (core library: types, COM, error handling, etc.) +write_base_macros_h() → winrt/base_macros.h (lightweight macro-only header for module mode) +``` + +For the ixx, `main.cpp` writes directly to a `writer ixx` object. + +For component generation, additional files: +``` +write_component_g_h(type) → Generated Files/.g.h (implementation base template) +write_component_g_cpp(type) → Generated Files/.g.cpp (factory function, opt constructors) +write_module_g_cpp(classes) → Generated Files/module.g.cpp (DLL activation factory) +``` + +## winrt.ixx Generation + +**Source**: `main.cpp`, lines ~345-378 + +The ixx is built by writing directly to a `writer ixx` object: + +```cpp +writer ixx; +write_preamble(ixx); +ixx.write("module;\n"); // global module fragment +ixx.write(strings::base_includes); // platform includes (intrin.h, version, directxmath) +ixx.write(strings::base_std_includes); // standard library #includes +ixx.write("\nexport module winrt;\n#define WINRT_EXPORT export\n\n"); + +for (auto ns : namespaces) + ixx.write("#include \"winrt/NS.h\"\n"); // all namespace headers + +ixx.flush_to_file("winrt/winrt.ixx"); +``` + +Key points: +- `strings::base_includes` contains platform-specific headers (``, + ``, ``) — NOT standard library headers. +- `strings::base_std_includes` contains all standard library `#include` + directives (``, ``, etc.). These are always written + as raw `#include`s in the global module fragment where `import` is prohibited. +- `WINRT_EXPORT` is defined as `export` inside the module purview. +- The namespace headers are `#include`d inside the module purview, making all + their content (types, template specializations) part of the module. + +## Split Standard Library Includes + +**Problem**: The C++ standard prohibits `import` declarations in the global +module fragment (the section between `module;` and `export module winrt;`). +The ixx needs raw `#include` directives there, but `base.h` consumers may +want `import std;` instead. + +**Solution**: The standard library includes are split into a separate file: + +| File | Content | Used by | +|------|---------|--------| +| `strings/base_includes.h` | Platform headers: ``, ``, `` | Both ixx and base.h | +| `strings/base_std_includes.h` | Standard library: ``, ``, ``, etc. | ixx (always), base.h (conditional) | + +In `write_base_h()`, the std includes are written conditionally: + +```cpp +w.write(strings::base_includes); // platform includes (always) +w.write(R"( +#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD) +import std; +#else +)"); +w.write(strings::base_std_includes); // std includes (fallback) +w.write(R"(#endif +)"); +``` + +In the ixx writer, both are written unconditionally as raw includes: + +```cpp +ixx.write(strings::base_includes); // platform +ixx.write(strings::base_std_includes); // std (always #include in GMF) +``` + +This eliminates the need for any guard macros like `WINRT_IMPL_GLOBAL_MODULE_FRAGMENT`. + +## base_macros.h: Why Macros Need Special Handling + +**Problem**: C++20 modules do not export preprocessor macros. When consumer code +does `import winrt;`, macros like `WINRT_EXPORT`, `WINRT_IMPL_EMPTY_BASES`, +`CPPWINRT_VERSION`, and `WINRT_IMPL_ABI_DECL` are not available. + +**Who needs them**: Generated component files (`.g.h`) reference these macros. +In header mode, they come from `#include "winrt/base.h"`. In module mode, +`base.h` is not `#include`d — its content comes from the module. + +**Solution**: `base_macros.h` is a lightweight header containing ONLY the +preprocessor definitions needed by generated code. It's safe to `#include` +alongside `import winrt;` because it has no type declarations that could +conflict with module-exported symbols. + +**Generated by**: `write_base_macros_h()` in `file_writers.h`, using content from +`strings/base_module_macros.h` + +**Used by**: `.g.h` files in module mode: +```cpp +#include "winrt/base_macros.h" +import std; +import winrt; +// ... template code using WINRT_IMPL_EMPTY_BASES etc. +``` + +## Per-Namespace Include Guards (WINRT_MODULE_NS_*) + +**Problem**: After `import winrt;`, consumers textually `#include` component +and reference projection headers. Those headers have cross-namespace `#include` +deps. Platform namespace deps (already in the module) must be skipped to avoid +MSVC redeclaration errors on `inline constexpr` variable template specializations +like `name_v`. But component-to-component cross-namespace deps must NOT be +skipped, or multi-namespace components break. + +**Solution**: cppwinrt generates `winrt/winrt_module_namespaces.h` alongside +`winrt.ixx`. This header defines one macro per namespace in the module: + +```cpp +// Generated winrt/winrt_module_namespaces.h: +#pragma once +#define WINRT_MODULE_NS_Windows_Foundation +#define WINRT_MODULE_NS_Windows_Foundation_Collections +// ... one per namespace in the module +``` + +Generated namespace headers use per-namespace guards for cross-namespace deps: + +```cpp +// In generated TestModuleComponent.h: +#ifndef WINRT_MODULE_NS_Windows_Foundation +#include "winrt/impl/Windows.Foundation.2.h" +#endif +#include "winrt/impl/TestModuleComponent.Widgets.2.h" // NOT guarded (not in module) +#include "winrt/impl/TestModuleComponent.2.h" // self-namespace: never guarded +``` + +When `WINRT_MODULE` is defined, the version assert at the top of each namespace +header includes `winrt_module_namespaces.h`, making the `WINRT_MODULE_NS_*` +macros available for the guards. When `WINRT_BUILD_MODULE` is defined (inside +winrt.ixx), `winrt_module_namespaces.h` is NOT included, so all cross-namespace +deps resolve normally — the ixx needs them to build the module. + +**Implementation**: `write_root_include_guarded()` in `type_writers.h` extracts +the namespace from the include path and emits `#ifndef WINRT_MODULE_NS_`. +Used by: +- `write_depends_guarded()` for cross-namespace impl includes +- `write_parent_depends()` for parent namespace includes + +`write_version_assert()` in `code_writers.h`: +```cpp +#if defined(WINRT_BUILD_MODULE) || defined(WINRT_MODULE) +#include "winrt/base_macros.h" +#endif +#if defined(WINRT_MODULE) && !defined(WINRT_BUILD_MODULE) +#include "winrt/winrt_module_namespaces.h" +#endif +#if !defined(WINRT_BUILD_MODULE) && !defined(WINRT_MODULE) +#include "winrt/base.h" +#endif +``` + +**winrt_module_namespaces.h generation**: In `main.cpp`, emitted alongside +`winrt.ixx` when `settings.base` is true and `settings.component` is false. +This prevents component projections (which may also use `-base`) from generating +a stale version that would mask the module builder's file on the include path. + +**Module namespace filtering** (`-module_filter`): When specified, only matching +namespaces are included in `winrt.ixx` and `winrt_module_namespaces.h`. All +namespace headers are still generated for textual inclusion. This is implemented +via `settings.ixx_filter` (built from `-module_filter` args in `build_filters`). +The NuGet property `CppWinRTModuleFilter` passes this to cppwinrt.exe. + +The component's own impl headers are always included (via `write_depends()`, +unguarded) since they contain the component-specific type definitions. + +## WINRT_EXPORT on Extern Handlers + +**Source**: `strings/base_extern.h` + +The global function pointer variables used for error handling customization +(`winrt_to_hresult_handler`, `winrt_to_message_handler`, +`winrt_throw_hresult_handler`, `winrt_activation_handler`) are prefixed with +`WINRT_EXPORT`: + +```cpp +WINRT_EXPORT __declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(...) noexcept {}; +WINRT_EXPORT __declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(...) {}; +// etc. +``` + +This ensures correct linkage when mixing modules and non-modules translation +units in the same DLL/EXE. Without `WINRT_EXPORT`, the module-compiled TUs +and the header-compiled TUs would see these as separate symbols, leading to +one side's customizations being invisible to the other. + +## The WINRT_MODULE Macro: Component Code Generation + +**Source**: `component_writers.h` (`write_module_g_cpp`, `write_component_g_cpp`), +`file_writers.h` (`write_component_g_h`) + +Module-aware component code generation is controlled entirely by the +`WINRT_MODULE` preprocessor macro, defined at the project level (by the NuGet +targets or manually). The code generator always emits both code paths with +`#ifdef WINRT_MODULE` guards — no special cppwinrt.exe flags are needed. + +### module.g.cpp + +**Source**: `write_module_g_cpp()` in `component_writers.h` + +The generated code uses `#ifdef WINRT_MODULE` to select between import and include: + +```cpp +#ifdef WINRT_MODULE +#ifdef WINRT_IMPORT_STD +import std; +#endif +import winrt; +#else +#include "winrt/base.h" +#endif +``` + +### Toaster.g.h (component base template) + +**Source**: `write_component_g_h()` in `file_writers.h` + +```cpp +#ifdef WINRT_MODULE +#include "winrt/base_macros.h" +#ifdef WINRT_IMPORT_STD +import std; +#endif +import winrt; +#endif +#include "winrt/test_component.h" // always emitted +``` + +When `WINRT_MODULE` is defined, the version assert in each included header +pulls in `winrt_module_namespaces.h`, which provides per-namespace +`WINRT_MODULE_NS_*` macros. Platform namespace deps are skipped by these +guards; component cross-namespace deps are included normally. +When `WINRT_MODULE` is not defined, the headers pull in `base.h` transitively. + +### Toaster.g.cpp (factory + optional optimized constructors) + +**Source**: `write_component_g_cpp()` in `component_writers.h` + +```cpp +void* winrt_make_MyComponent_Toaster() { ... } // always emitted + +WINRT_EXPORT namespace winrt::MyComponent +{ +#ifndef WINRT_MODULE + Toaster::Toaster() : Toaster(make()) {} + // ... other constructors/statics +#endif +} +``` + +The constructor/static overrides are guarded by `#ifndef WINRT_MODULE` because +they're already available from the component's projection header (included by +the `.g.h` after `import winrt;`). The `winrt_make_*` factory function is +always emitted since it's needed by `module.g.cpp` for activation lookup. + +## Exported `winrt::impl` Namespace + +**The key insight**: Component projection headers (`.0.h` files) specialize +templates like `category<>`, `abi<>`, `guid_v<>` from `winrt::impl` namespace. +For these specializations to work after `import winrt;`, the primary templates +must be exported from the module. + +**The solution**: All `namespace winrt::impl` blocks in the `strings/base_*.h` +files and the generated namespace headers use `WINRT_EXPORT namespace winrt::impl`. +In module mode, `WINRT_EXPORT` expands to `export`, making the `impl` templates +part of the module's public interface. In header mode, `WINRT_EXPORT` is empty, +so there's no change to the existing behavior. + +This allows component projection headers to be `#include`d separately after +`import winrt;` — they can specialize the exported `impl` templates without +needing to be folded into `winrt.ixx`. + +## The windowsnumerics.impl.h Warning + +**Source**: `base_macros.h` (strings) + +The `` SDK header is intentionally included in the +module purview (not the global module fragment) because it defines the +`winrt::Windows::Foundation::Numerics` types using `WINRT_EXPORT`, which must +expand to `export` for the types to be exported from the module. + +MSVC warning C5244 flags this as "appears erroneous." We suppress it with +`#pragma warning(suppress: 5244)` on the single line before the include, +matching the approach from the sylveon fork. + +```cpp +#ifdef WINRT_IMPL_NUMERICS +#define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace ... +// The include in the purview of a module is intentional. +#pragma warning(suppress: 5244) +#include +... +#endif +``` + +## import std Integration + +**Source**: `strings/base_includes.h`, `strings/base_std_includes.h`, +`file_writers.h` (`write_base_h`) + +The standard library includes are split into two files: + +- **`strings/base_includes.h`**: Platform headers (``, ``, + ``). Always written as `#include`. +- **`strings/base_std_includes.h`**: Standard library headers (``, + ``, ``, etc.). + +In `write_base_h()`, the std includes are conditional: + +```cpp +// In generated base.h: +#if defined(__cpp_lib_modules) && defined(WINRT_IMPORT_STD) +import std; +#else +#include +#include +... // 25+ individual includes +#endif +``` + +In the ixx global module fragment, both files are written as raw `#include`s +with no conditional — `import` is not permitted there. + +`WINRT_IMPORT_STD` is: +- Defined automatically by NuGet targets when `CppWinRTModuleBuild` or + `CppWinRTModuleConsume` is true and + `BuildStlModules=true` +- Can be defined manually by users as a preprocessor definition + +`__cpp_lib_modules` is defined in `` by MSVC when the STL supports +modules. However, it does NOT guarantee `import std;` will work — that also +requires the build system to compile `std.ixx` (controlled by `BuildStlModules`). +This is why `WINRT_IMPORT_STD` is a separate opt-in: `__cpp_lib_modules` alone +would break existing users whose build systems don't compile the std module. + +## Code Paths Reference + +### Key source files + +| File | Responsibility | +|------|---------------| +| `main.cpp` | CLI parsing, ixx generation, orchestration | +| `file_writers.h` | File-level writers: `write_base_h`, `write_base_macros_h`, `write_component_g_h`, `write_namespace_h` | +| `component_writers.h` | Component template writers: `write_module_g_cpp`, `write_component_g_cpp` | +| `code_writers.h` | Low-level writers: `write_version_assert`, `write_parent_depends`, `write_open_file_guard` | +| `type_writers.h` | Writer class: `write_root_include`, `write_root_include_guarded`, `write_depends`, `write_depends_guarded` | +| `strings/base_includes.h` | Platform includes (``, ``, ``) | +| `strings/base_std_includes.h` | Standard library includes (``, ``, ``, etc.) | +| `strings/base_macros.h` | `WINRT_EXPORT` definition, `windowsnumerics.impl.h` suppress | +| `strings/base_module_macros.h` | Lightweight macros for module consumers (`WINRT_EXPORT`, `WINRT_IMPL_EMPTY_BASES`, etc.) | +| `strings/base_extern.h` | `WINRT_EXPORT`-decorated extern handler variables | + +### Key NuGet targets + +| Target | File | Role | +|--------|------|------| +| `CppWinRTBuildModule` | `Microsoft.Windows.CppWinRT.targets` | Adds ixx to compilation, defines `WINRT_MODULE` and `WINRT_IMPORT_STD` | +| `CppWinRTGetModuleOutputs` | `Microsoft.Windows.CppWinRT.targets` | Exports IFC/OBJ/GeneratedFilesDir for consuming projects | +| `CppWinRTResolveModuleReferences` | `Microsoft.Windows.CppWinRT.targets` | Resolves IFC/OBJ from ProjectReferences, defines `WINRT_MODULE` and `WINRT_IMPORT_STD` | +| `CppWinRTMakeProjections` | `Microsoft.Windows.CppWinRT.targets` | Orchestrates platform/reference/component projection generation | +| `CppWinRTMakeComponentProjection` | `Microsoft.Windows.CppWinRT.targets` | Runs cppwinrt.exe for component stubs | + +### Macro flow diagram + +``` +User sets CppWinRTModuleBuild=true (or CppWinRTModuleConsume=true) + BuildStlModules=true + │ + ├── NuGet targets define WINRT_MODULE on all ClCompile items + ├── NuGet targets define WINRT_IMPORT_STD on all ClCompile items + │ + ▼ +winrt.ixx compilation (WINRT_BUILD_MODULE defined in global module fragment): + module; + #define WINRT_BUILD_MODULE + ← platform includes (base_includes) + ← std library includes (base_std_includes) + export module winrt; + #define WINRT_EXPORT export + #include "winrt/base.h" ← base.h (explicit include) + #include "winrt/Windows.Foundation.h" ← SDK types + specializations + ... ← winrt::impl is exported + Also generates: winrt/winrt_module_namespaces.h (per-namespace macros) + +Consumer .cpp compilation (WINRT_MODULE defined by NuGet targets): + #include "pch.h" ← PCH (no winrt/ headers) + import std; ← std module (optional, needs BuildStlModules) + import winrt; ← winrt module (SDK types + exported impl) + #include "winrt/MyComponent.h" ← reference projection header + → version assert includes winrt_module_namespaces.h (via WINRT_MODULE) + → platform deps guarded by WINRT_MODULE_NS_* (skipped) + → component cross-namespace deps NOT guarded (included normally) + +Component .g.h (WINRT_MODULE defined by NuGet targets): + #include "winrt/base_macros.h" ← macros only (WINRT_EXPORT etc.) + #ifdef WINRT_IMPORT_STD + import std; ← conditional on WINRT_IMPORT_STD + #endif + import winrt; ← SDK types + exported impl templates + #include "winrt/MyComponent.h" ← component projection (specializes impl) + → platform deps skipped by WINRT_MODULE_NS_* guards + → cross-namespace component deps included normally + +module.g.cpp compilation: + #include "pch.h" ← PCH preserved + #ifdef WINRT_IMPORT_STD / import std; ← conditional + import winrt; ← module import + void* winrt_make_MyComponent_Toaster() ← factory function + bool __stdcall ..._can_unload_now() ← DLL entry points +``` diff --git a/docs/modules.md b/docs/modules.md new file mode 100644 index 000000000..eab29c4f2 --- /dev/null +++ b/docs/modules.md @@ -0,0 +1,478 @@ +# C++20 Modules Support in C++/WinRT + +## Overview + +C++/WinRT can generate a C++20 named module (`winrt.ixx`) that allows consumers +to write `import winrt;` instead of using traditional `#include` directives and +precompiled headers. This can significantly improve build times and simplify the +developer experience. + +## Quick Start + +### Consuming the platform projection (app that calls WinRT APIs) + +C++/WinRT module support uses two properties that separate building the module +from consuming it: + +- **`CppWinRTModuleBuild`** — Generate the platform projection and compile + `winrt.ixx` as a C++20 module interface unit. Set this on one project in your + solution (typically a dedicated static library). Other projects reference this + one to share the pre-built module. + +- **`CppWinRTModuleConsume`** — Consume a pre-built winrt module from a + `ProjectReference` that sets `CppWinRTModuleBuild`. The NuGet targets + automatically resolve the module IFC, OBJ, and include paths. + +For a single-project scenario (no shared builder), just set `CppWinRTModuleBuild` +on your project — it builds and consumes the module in-place. + +#### Multi-project setup (recommended for solutions with multiple consumers) + +**Builder project** (static library, no source files needed): + +In Visual Studio: +- Right-click project > **Properties** +- **Configuration Properties > General > Configuration Type**: `Static Library (.lib)` +- **Configuration Properties > C/C++ > General > C++ Language Standard**: `ISO C++20 (/std:c++20)` or later +- **Configuration Properties > C++/WinRT > General > Build C++20 Module**: `Yes` + +Or equivalently in your `.vcxproj`: + +```xml + + true + + + + + stdcpp20 + + +``` + +**Consumer project** (app or component): + +In Visual Studio: +- Right-click project > **Properties** +- **Configuration Properties > C/C++ > General > C++ Language Standard**: `ISO C++20 (/std:c++20)` or later +- **Configuration Properties > C++/WinRT > General > Consume C++20 Module**: `Yes` +- Add a **Project Reference** to the builder project + +Or equivalently in your `.vcxproj`: + +```xml + + true + + + + + stdcpp20 + + + + + + +``` + +#### Single-project setup + +In Visual Studio: +- Right-click project > **Properties** +- **Configuration Properties > C/C++ > General > C++ Language Standard**: `ISO C++20 (/std:c++20)` or later +- **Configuration Properties > C++/WinRT > General > Build C++20 Module**: `Yes` + +Or equivalently in your `.vcxproj`: + +```xml + + true + + + + + stdcpp20 + + +``` + +#### Optionally enabling `import std;` + +`import winrt;` works independently of `import std;`. If you also want to use +`import std;`, enable it separately: + +In Visual Studio: +- **Configuration Properties > C/C++ > General > Build ISO C++23 Standard Library Modules**: `Yes` + +Or in your `.vcxproj`: + +```xml + + + true + + +``` + +> **Note**: On the v143 toolset (VS 2022), `import std;` requires +> `/std:c++latest` — set **C++ Language Standard** to `Preview` in the IDE. +> On the v145 toolset (VS 2026), `import std;` works with `/std:c++20`. + +When `BuildStlModules` is enabled, the NuGet targets automatically define +`WINRT_IMPORT_STD` so that `base.h` uses `import std;` instead of individual +standard library `#include` directives. + +Then in your source files: + +```cpp +import std; // optional — only if BuildStlModules is enabled +import winrt; + +using namespace winrt; +using namespace winrt::Windows::Foundation; + +int main() +{ + init_apartment(); + Uri uri(L"https://example.com"); + // ... +} +``` + +If you don't enable `import std;`, you'll need to `#include` standard library +headers you use (e.g., ``, ``) or include them in your PCH. + +### Component authoring (DLL with IDL) + +Same C++/WinRT module properties as above (`CppWinRTModuleConsume=true` with a +`ProjectReference` to a builder, or `CppWinRTModuleBuild=true` for +single-project), then in your implementation files: + +```cpp +// MyComponent.cpp +#include "pch.h" // PCH is fine — just don't include winrt/ headers in it +import winrt; +#include "MyComponent.h" // includes MyComponent.g.h +#include "MyComponent.g.cpp" // factory function only in module mode +``` + +The NuGet targets automatically: +1. Define `WINRT_MODULE` so generated `.g.h`/`.g.cpp` files use + `import winrt;` instead of `#include` directives for platform types +2. Define `WINRT_IMPORT_STD` when `BuildStlModules` is enabled +3. Resolve the module IFC, OBJ, and include paths from the builder project + (when `CppWinRTModuleConsume` is used) + +## Requirements + +- **Visual Studio 2022** (v143 toolset) or later +- **C++20 or later** language standard (`/std:c++20` or `/std:c++latest`) +- For `import std;` (optional, orthogonal to `import winrt;`): + - **v143 toolset**: requires `/std:c++latest` + `BuildStlModules=true` + - **v145 toolset** (VS 2026): works with `/std:c++20` + `BuildStlModules=true` +- **`BuildStlModules=true`** (optional) in `` metadata enables the + standard library module compilation. This is the "Build ISO C++23 Standard + Library Modules" property in the Visual Studio IDE. Note: on v143, the STL + modules infrastructure (`StdModulesSupported`) is only active when the language + standard is C++23 or later, so setting this property with `/std:c++20` on v143 + has no effect. + +## MSBuild Properties + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `CppWinRTModuleBuild` | bool | `false` | Generate the platform projection and compile `winrt.ixx` as a C++20 module interface unit. The compiled module IFC and OBJ are exported for consumption by projects that set `CppWinRTModuleConsume`. Defines `WINRT_MODULE` for generated component code. | +| `CppWinRTModuleConsume` | bool | `false` | Consume a pre-built winrt module from a `ProjectReference` that sets `CppWinRTModuleBuild`. Automatically resolves the IFC (for `AdditionalModuleDependencies`), OBJ (for linker `AdditionalDependencies`), and the builder's `GeneratedFilesDir` (for `AdditionalIncludeDirectories`). Skips regenerating the platform projection by default. Defines `WINRT_MODULE` for generated component code. | +| `CppWinRTModuleFilter` | string | (empty) | Space-separated namespace prefixes to include in the winrt module. When set, only matching namespaces are compiled into `winrt.ixx` and listed in `winrt_module_namespaces.h`. All namespace headers are still generated for textual inclusion. Example: `Windows.Foundation Windows.Storage` would include only those namespace families in the module. Passed as `-module_filter` to cppwinrt.exe. | +| `BuildStlModules` | ClCompile metadata | `false` | Enables building `std.ixx`/`std.compat.ixx` so `import std;` works. This is orthogonal to `import winrt;` — you can use `import winrt;` without `import std;`. Set via **C/C++ > General > Build ISO C++23 Standard Library Modules** in the IDE, or `true` in `` metadata. On v143, the underlying `StdModulesSupported` infrastructure requires `/std:c++latest`; on v145, `/std:c++20` suffices. | + +When `CppWinRTModuleBuild` or `CppWinRTModuleConsume` is true, the NuGet targets also automatically: +- Define `WINRT_MODULE` so generated component files (`.g.h`, `.g.cpp`, + `module.g.cpp`) use `import winrt;` instead of `#include` directives +- Define `WINRT_IMPORT_STD` as a preprocessor definition when `BuildStlModules` + is enabled, so `base.h` uses `import std;` instead of individual `#include`s +- Define `WINRT_LEAN_AND_MEAN` on the `winrt.ixx` compilation unit (builder only) + +## cppwinrt.exe Command-Line Options + +| Option | Description | +|--------|-------------| +| `-input ` | Windows metadata (.winmd) to include in projection | +| `-reference ` | Windows metadata to reference from projection | +| `-output ` | Location of generated projection and component templates | +| `-component []` | Generate component templates, and optional implementation | +| `-name ` | Specify explicit name for component files | +| `-optimize` | Generate component projection with unified construction support | +| `-base` | Generate base.h unconditionally | +| `-prefix` | Use dotted namespace convention for component files | +| `-pch ` | Specify PCH file name (use `.` to disable) | +| `-library ` | Specify library prefix (defaults to `winrt`) | +| `-fastabi` | Enable Fast ABI support | +| `-verbose` | Show detailed progress information | +| `-overwrite` | Overwrite generated component files | +| `-module_filter ` | Filter which namespaces are included in `winrt.ixx`. Can be specified multiple times. All namespace headers are still generated; only the module contents and `winrt_module_namespaces.h` are filtered. Example: `-module_filter Windows.Foundation` includes `Windows.Foundation` and all sub-namespaces. | + +## Source File Patterns + +### Include ordering rules + +When using `import winrt;`, all `#include` directives must appear **before** +the `import` declarations. This is because textual `#include`s and module +`import`s can conflict if headers define types that the module also exports. + +```cpp +// CORRECT: #includes before imports +#include "catch.hpp" // third-party headers first +#include // Windows SDK headers (non-standard-library) + +import std; // standard library module +import winrt; // C++/WinRT module + +// Your code here... +``` + +### Consumer app (platform types only) + +```cpp +import std; +import winrt; + +using namespace winrt; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Web::Syndication; + +winrt::Windows::Foundation::IAsyncAction FetchFeedAsync() +{ + Uri uri(L"https://blogs.windows.com/feed"); + SyndicationClient client; + auto feed = co_await client.RetrieveFeedAsync(uri); + for (auto const& item : feed.Items()) + { + std::wcout << item.Title().Text().c_str() << std::endl; + } +} +``` + +### Component implementation + +```cpp +// Toaster.cpp +import std; +import winrt; +#include "Toaster.h" // your implementation header +#include "Toaster.g.cpp" // generated factory function + +namespace winrt::MyComponent::implementation +{ + void Toaster::MakeToast(hstring const& message) + { + // implementation... + } +} +``` + +### Component implementation header + +```cpp +// Toaster.h +#pragma once +#include "Toaster.g.h" // generated base class template + +namespace winrt::MyComponent::implementation +{ + struct Toaster : ToasterT + { + Toaster() = default; + void MakeToast(hstring const& message); + }; +} + +namespace winrt::MyComponent::factory_implementation +{ + struct Toaster : ToasterT {}; +} +``` + +## Preprocessor Macros Reference + +### User-facing macros + +| Macro | Purpose | +|-------|---------| +| `WINRT_IMPORT_STD` | When defined alongside `__cpp_lib_modules`, causes `base.h` to emit `import std;` instead of individual standard library `#include` directives. Automatically defined by the NuGet targets when `BuildStlModules` is enabled alongside `CppWinRTModuleBuild` or `CppWinRTModuleConsume`. | +| `WINRT_LEAN_AND_MEAN` | Suppresses `#include ` and `std::hash`/`std::formatter` specializations from generated headers. Reduces header weight. | +| `WINRT_MODULE` | When defined (project-wide), generated component files (`.g.h`, `.g.cpp`, `module.g.cpp`) use `import winrt;` instead of `#include` directives for platform types. Also causes generated namespace headers to skip `#include "base.h"` in their version assert (using `base_macros.h` instead) and to include `winrt_module_namespaces.h` which provides per-namespace `WINRT_MODULE_NS_*` macros for precise cross-namespace include guarding. Automatically defined by the NuGet targets when `CppWinRTModuleBuild` or `CppWinRTModuleConsume` is set. Can also be defined manually for non-NuGet projects. | + +### Internal implementation macros + +These are used by the code generator and should not be set directly by users: + +| Macro | Purpose | +|-------|--------| +| `WINRT_BUILD_MODULE` | Defined by cppwinrt in the winrt.ixx global module fragment. Tells namespace headers to use `base_macros.h` instead of `#include "base.h"` (since base.h is already included explicitly in the ixx). Does NOT include `winrt_module_namespaces.h` — inside the ixx, all cross-namespace deps must resolve normally. | +| `WINRT_MODULE_NS_*` | Per-namespace macros (e.g., `WINRT_MODULE_NS_Windows_Foundation`) defined in the generated `winrt_module_namespaces.h` companion header. Each cross-namespace `#include` dep is guarded by `#ifndef WINRT_MODULE_NS_`, so only namespaces actually in the module are skipped. Component and other non-module namespace deps are always included. | +| `WINRT_EXPORT` | Expands to `export` inside `winrt.ixx` (module purview), empty in header mode. Used on namespace declarations so types are properly exported from the module. Also applied to `winrt_to_hresult_handler` and related extern handler variables in `base_extern.h` for correct linkage when mixing modules and non-modules code in the same binary. Defined in `base_macros.h` (as empty) for use in generated component files that operate alongside the module. | +| `WINRT_IMPL_EMPTY_BASES` | MSVC `__declspec(empty_bases)` optimization. Defined in both `base.h` and `base_macros.h`. | +| `WINRT_IMPL_ABI_DECL` | Combines `WINRT_IMPL_NOVTABLE` and `WINRT_IMPL_PUBLIC` for ABI interface declarations. | +| `CPPWINRT_VERSION` | Version string for header compatibility checking. Defined in `base.h` and `base_macros.h`. | + +## Architecture: How the Module is Built + +### Generated file structure + +``` +winrt/ +├── base.h # Core C++/WinRT library (types, helpers, COM support) +├── base_macros.h # Lightweight macro-only header for module consumers +├── winrt.ixx # C++20 module interface unit +├── Windows.Foundation.h # Platform namespace header (consume definitions) +├── impl/ +│ ├── Windows.Foundation.0.h # Forward decls, enums, ABI, consume structs +│ ├── Windows.Foundation.1.h # Interface definitions +│ └── Windows.Foundation.2.h # Class/struct/delegate definitions +├── test_component.h # Component namespace header (if component) +└── impl/ + ├── test_component.0.h # Component forward decls + template specializations + ├── test_component.1.h # Component interface definitions + └── test_component.2.h # Component class definitions +``` + +### winrt.ixx structure + +``` +module; ← Global module fragment +#define WINRT_BUILD_MODULE ← Defined here, controls header behavior + ← Platform includes (base_includes) + ← Standard library includes (base_std_includes) + +export module winrt; ← Module purview begins +#define WINRT_EXPORT export +#include "winrt/base.h" ← Core library (explicit include) + +#include "winrt/Windows.Foundation.h" ← Platform namespace headers +#include "winrt/Windows.Foundation.Collections.h" +... ← (all platform namespaces) +``` + +The `winrt::impl` namespace is exported alongside `winrt`, so component +projection headers can specialize `impl` templates (like `category`, `abi`, +`guid_v`) after `import winrt;` without needing to be folded into the ixx. + +### How component and reference projection headers work with modules + +The `winrt::impl` namespace is exported from the module, which means the +primary templates (`category`, `abi`, `guid_v`, `name_v`, `default_interface`, +`consume`) are visible to external code. Component projection headers +(`.0.h` files) specialize these templates for the component's types. + +**Consumer experience**: After `import winrt;`, consumers can textually `#include` +reference and component projection headers. `WINRT_MODULE` (defined project-wide +by the NuGet targets) tells each namespace header to skip `#include "base.h"` +and use `base_macros.h` instead — base.h types are already available from the +module. Cross-namespace `#include` deps between component namespaces are NOT +skipped, so multi-namespace components work correctly: + +```cpp +import winrt; // platform types from module +#include // root component namespace +#include // sub-namespace — works! + +auto widget = component.CreateWidget(); // returns MyComponent.Widgets.Widget +auto name = widget.Name(); // calls method on cross-namespace type +``` + +Inside generated `.g.h` files, `import winrt;` makes platform types available. +The component's own projection headers are included normally — cross-namespace +deps between component namespaces resolve via unguarded includes, while platform +namespace deps are skipped by per-namespace `WINRT_MODULE_NS_*` guards. + +### Generated file differences when `WINRT_MODULE` is defined + +Generated component files contain `#ifdef WINRT_MODULE` guards. The code generator +always emits both paths — the preprocessor selects at compile time: + +| File | Without `WINRT_MODULE` | With `WINRT_MODULE` | +|------|------------------------|---------------------| +| `Toaster.g.h` | `#include "winrt/test_component.h"` (pulls in `base.h` transitively) | `#include "winrt/base_macros.h"` + `import winrt;` + component headers (platform deps skipped by `WINRT_MODULE_NS_*` guards) | +| `Toaster.g.cpp` | `winrt_make_*` + constructor/static overrides | `winrt_make_*` only (constructors guarded by `#ifndef WINRT_MODULE`) | +| `module.g.cpp` | `#include "winrt/base.h"` | `import winrt;` (`import std;` conditional on `WINRT_IMPORT_STD`) | + +### base_macros.h + +Macros are not exported across C++20 module boundaries. When a `.g.h` file does +`import winrt;`, macros like `WINRT_EXPORT`, `WINRT_IMPL_EMPTY_BASES`, and +`CPPWINRT_VERSION` are not available. The generated `base_macros.h` is a +lightweight textual header that provides just these macros. It is always safe to +`#include` alongside `import winrt;` because it contains only preprocessor +definitions — no type/function declarations that could conflict with the module. + +## NuGet Targets Integration + +### CppWinRTBuildModule target + +Runs when `CppWinRTModuleBuild=true`. After the platform projection is generated: + +1. **Adds** `winrt.ixx` as a `ClCompile` item with `PrecompiledHeader=NotUsing` +2. **Defines** `WINRT_MODULE` so generated component files use `import winrt;` +3. **Defines** `WINRT_IMPORT_STD` when `BuildStlModules=true` is detected + +### CppWinRTGetModuleOutputs target + +Defined in every project that imports CppWinRT.targets, but only returns data +when `CppWinRTModuleBuild=true`. Returns the IFC, OBJ, and GeneratedFilesDir +paths. Called by consuming projects via the `MSBuild` task on `ProjectReference` +items. + +### CppWinRTResolveModuleReferences target + +Runs when `CppWinRTModuleConsume=true`. After `ResolveProjectReferences`: + +1. **Calls** `CppWinRTGetModuleOutputs` on each `ProjectReference` +2. **Adds** the resolved IFC to `AdditionalModuleDependencies` +3. **Adds** the resolved OBJ to linker `AdditionalDependencies` +4. **Adds** the builder's `GeneratedFilesDir` to `AdditionalIncludeDirectories` +5. **Defines** `WINRT_IMPORT_STD` when `BuildStlModules=true` is detected +6. **Errors** if no project references with `CppWinRTModuleBuild` are found + component generation emits `import winrt;` in `.g.h` and `module.g.cpp` + +## Known Limitations + +1. **`import std;` requires C++23 or later on v143**: The v143 toolset (VS 2022) + enables `StdModulesSupported` only when the language standard is C++23 or + `/std:c++latest` (`CppLanguageStandardNumber >= 23`). The v145 toolset + (VS 2026) relaxes this to C++20. On v143 with `/std:c++20`, `import std;` + is not available — use `import winrt;` with textual `#include`s for standard + library types. + +2. **Include ordering**: Standard library `#include` directives and third-party + headers must come before `import std;` and `import winrt;` to avoid + redefinition errors. + +3. **PCH compatibility**: The module interface unit (`winrt.ixx`) cannot use a + precompiled header. Regular source files that `import winrt;` CAN use a PCH, + but the PCH must not include any `winrt/` headers (e.g., `winrt/base.h`, + `winrt/Windows.Foundation.h`) since those types come from the module. A PCH + containing third-party headers, Windows SDK headers, or other project headers + is fine. + +4. **Standard library headers not included transitively**: When using traditional + `#include "winrt/base.h"`, you implicitly get standard library headers like + ``, ``, ``, etc. With `import winrt;`, these are + NOT automatically available. You must either: + - `import std;` (if your toolset/language standard supports it), or + - Explicitly `#include` the standard library headers you need, or + - Include them in your PCH + +5. **WINRT_LEAN_AND_MEAN for consumers**: Pure consumer apps (no component + authoring) should define `WINRT_LEAN_AND_MEAN` to avoid pulling `` + and `produce<>` templates into the module. Component authors should NOT + define it since they need `produce<>`. + +6. **Exported `winrt::impl` namespace**: The `winrt::impl` namespace is exported + from the module so that component projection headers can specialize `impl` + templates (`category`, `abi`, `guid_v`, etc.) after `import winrt;`. This + means component headers can be `#include`d separately without being folded + into `winrt.ixx`. Per-namespace `WINRT_MODULE_NS_*` guards ensure that + platform namespace deps (already in the module) are skipped, while component + and other non-module namespace deps are included normally. diff --git a/natvis/pch.h b/natvis/pch.h index 95e561971..bda5dde50 100644 --- a/natvis/pch.h +++ b/natvis/pch.h @@ -11,6 +11,8 @@ #include #include #include "base_includes.h" +#include "base_std_includes.h" +#include "base_module_macros.h" #include "base_macros.h" #include "base_types.h" #include "base_extern.h" diff --git a/nuget/CppWinrtRules.Project.xml b/nuget/CppWinrtRules.Project.xml index 73f32c6d3..421c9cc64 100644 --- a/nuget/CppWinrtRules.Project.xml +++ b/nuget/CppWinrtRules.Project.xml @@ -86,4 +86,19 @@ Description="Enables the /await:strict compiler option" Category="General" /> + + + + + + diff --git a/nuget/Microsoft.Windows.CppWinRT.nuspec b/nuget/Microsoft.Windows.CppWinRT.nuspec index a63e08bf9..afe9067c3 100644 --- a/nuget/Microsoft.Windows.CppWinRT.nuspec +++ b/nuget/Microsoft.Windows.CppWinRT.nuspec @@ -25,5 +25,6 @@ + diff --git a/nuget/Microsoft.Windows.CppWinRT.targets b/nuget/Microsoft.Windows.CppWinRT.targets index 188e56835..9f10cc2fe 100644 --- a/nuget/Microsoft.Windows.CppWinRT.targets +++ b/nuget/Microsoft.Windows.CppWinRT.targets @@ -26,6 +26,8 @@ Copyright (C) Microsoft Corporation. All rights reserved. $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)))..\..\ $([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory))) $(CppWinRTParameters) -fastabi + + false "$(CppWinRTPackageDir)bin\" "$(CppWinRTPackageDir)" @@ -652,6 +654,7 @@ $(XamlMetaDataProviderPch) <_CppwinrtParameters>$(CppWinRTCommandVerbosity) $(CppWinRTParameters) <_CppwinrtParameters>$(_CppwinrtParameters) @(_CppwinrtInputs->'-in "%(WinMDPath)"', ' ') + <_CppwinrtParameters Condition="'$(CppWinRTModuleFilter)' != ''">$(_CppwinrtParameters) -module_filter $(CppWinRTModuleFilter) <_CppwinrtParameters>$(_CppwinrtParameters) -out "$(GeneratedFilesDir)." @@ -878,6 +881,114 @@ $(XamlMetaDataProviderPch) + + + + + + NotUsing + + + + + + WINRT_MODULE;%(PreprocessorDefinitions) + + + + + + WINRT_IMPORT_STD;%(PreprocessorDefinitions) + + + + + + + + + $(IntDir)winrt.ixx.ifc + $(IntDir)winrt.ixx.obj + $(GeneratedFilesDir) + + + + + + + + + + + + + + + + + + + + @(_CppWinRTResolvedModuleRefs->'%(ModuleIfc)', ';');%(ClCompile.AdditionalModuleDependencies) + @(_CppWinRTResolvedModuleRefs->'%(ModuleGeneratedFilesDir)', ';');%(ClCompile.AdditionalIncludeDirectories) + + + @(_CppWinRTResolvedModuleRefs->'%(ModuleObj)', ';');%(Link.AdditionalDependencies) + + + + + + + WINRT_MODULE;%(PreprocessorDefinitions) + + + + + + + WINRT_IMPORT_STD;%(PreprocessorDefinitions) + + + + diff --git a/nuget/readme.md b/nuget/readme.md index 9379aa716..baed3d673 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -70,7 +70,8 @@ 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 | -\*Default value +| CppWinRTModuleBuild | true \| *false | Generates the platform projection and compiles winrt.ixx as a C++20 module. See [C++20 Modules](#c20-modules) | +| CppWinRTModuleConsume | true \| *false | Consumes a pre-built winrt module from a ProjectReference. See [C++20 Modules](#c20-modules) || CppWinRTModuleFilter | *empty | Namespace prefixes to include in the module (e.g. `Windows.Foundation`). When empty, all platform namespaces are included. |\*Default value To customize common C++/WinRT project properties: * right-click the project node @@ -132,6 +133,48 @@ void DerivedPage::InitializeComponent() } ``` +## C++20 Modules + +C++/WinRT can generate a C++20 named module (`winrt.ixx`) that allows consumers to write `import winrt;` instead of `#include` directives. This can significantly improve build throughput in multi-project solutions. + +Two properties control module support: + +- **`CppWinRTModuleBuild=true`** — Generates the platform projection and compiles `winrt.ixx`. Set this on a dedicated static library project (the "module builder"). Other projects reference it to share the pre-built module. Can also be set on a standalone app project for single-project scenarios. + +- **`CppWinRTModuleConsume=true`** — Consumes a pre-built winrt module via `ProjectReference` to a builder project. The NuGet targets automatically resolve the module IFC, OBJ, and include paths. Skips regenerating the platform projection. + +Both properties define `WINRT_MODULE` so generated component `.g.h` files use `import winrt;`. + +### Quick example (multi-project) + +**Builder project** (static library, no source files needed): + +In Visual Studio, set **C++/WinRT > Build C++20 Module** to `Yes`, or: +```xml +true +``` + +**Consumer project** (app or component DLL): + +In Visual Studio, set **C++/WinRT > Consume C++20 Module** to `Yes` and add a Project Reference to the builder, or: +```xml +true + +``` + +```cpp +// main.cpp +import winrt; +using namespace winrt::Windows::Foundation; +``` + +### Requirements +- C++20 or later (**C++ Language Standard** set to `ISO C++20` or later in the IDE) +- `import std;` is optional and orthogonal — enable **Build ISO C++23 Standard Library Modules** in the IDE if desired +- On v143 (VS 2022), `import std;` requires **C++ Language Standard** set to `Preview (/std:c++latest)`; on v145 (VS 2026), `ISO C++20` suffices + +For full documentation including component authoring, source file patterns, preprocessor macros, and architecture details, see [docs/modules.md](https://github.com/Microsoft/cppwinrt/blob/master/docs/modules.md) (also included in this NuGet package under the `docs` folder). + ## Troubleshooting The msbuild verbosity level maps to msbuild message importance as follows: diff --git a/nuget/readme.txt b/nuget/readme.txt index 049ab13b8..48b533cc4 100644 --- a/nuget/readme.txt +++ b/nuget/readme.txt @@ -16,6 +16,9 @@ Client code can simply #include these headers, which are created in the generate For any IDL file contained in the project, C++/WinRT creates component (producing) projection headers. In addition, C++/WinRT generates templates and skeleton implementations for each runtime class, under the Generated Files directory. +======================================================================== +For C++20 module support (import winrt;), see docs/modules.md in this package, +or visit: https://github.com/Microsoft/cppwinrt/blob/master/docs/modules.md ======================================================================== For more information, visit: https://github.com/Microsoft/cppwinrt/tree/master/nuget 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 9544dcf8c..c5dca1b90 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..b40247dab 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; 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..cde752aa3 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 diff --git a/strings/base_coroutine_threadpool.h b/strings/base_coroutine_threadpool.h index 6748906ca..3ffcf7002 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 { 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..5c5c9e2cf 100644 --- a/strings/base_extern.h +++ b/strings/base_extern.h @@ -1,8 +1,8 @@ -__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 {}; +WINRT_EXPORT extern "C++" __declspec(selectany) std::int32_t(__stdcall* winrt_to_hresult_handler)(void* address) noexcept {}; +WINRT_EXPORT extern "C++" __declspec(selectany) winrt::hstring(__stdcall* winrt_to_message_handler)(void* address) {}; +WINRT_EXPORT extern "C++" __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 extern "C++" __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_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_includes.h b/strings/base_includes.h index d6808792b..614cc4c84 100644 --- a/strings/base_includes.h +++ b/strings/base_includes.h @@ -1,31 +1,5 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #if __has_include() #include @@ -35,25 +9,3 @@ #define WINRT_IMPL_NUMERICS #include #endif - -#ifndef WINRT_LEAN_AND_MEAN -#include -#endif - -#ifdef __cpp_lib_span -#include -#endif - -#ifdef __cpp_lib_format -#include -#endif - -#ifdef __cpp_lib_source_location -#include -#endif - -#ifdef __cpp_lib_coroutine -#include -#elif defined(_RESUMABLE_FUNCTIONS_SUPPORTED) -#error "C++/WinRT no longer supports pre-standardization coroutines. If you use co_await, switch to /await:strict or upgrade to C++20. If you do not, remove /await from the compiler flags." -#endif 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..ccb47fda9 100644 --- a/strings/base_macros.h +++ b/strings/base_macros.h @@ -27,14 +27,12 @@ #define WINRT_IMPL_COROUTINES #endif -#ifndef WINRT_EXPORT -#define WINRT_EXPORT -#endif - #ifdef WINRT_IMPL_NUMERICS #define _WINDOWS_NUMERICS_NAMESPACE_ winrt::Windows::Foundation::Numerics #define _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ WINRT_EXPORT namespace winrt::Windows::Foundation::Numerics #define _WINDOWS_NUMERICS_END_NAMESPACE_ +// The include in the purview of a module is intentional: we need to export the numerics types. +#pragma warning(suppress: 5244) #include #undef _WINDOWS_NUMERICS_NAMESPACE_ #undef _WINDOWS_NUMERICS_BEGIN_NAMESPACE_ @@ -49,30 +47,6 @@ #define WINRT_IMPL_NOINLINE #endif -#if defined(_MSC_VER) -#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) -#else -#define WINRT_IMPL_EMPTY_BASES -#endif - -#if defined(_MSC_VER) -#define WINRT_IMPL_NOVTABLE __declspec(novtable) -#else -#define WINRT_IMPL_NOVTABLE -#endif - -#if defined(__clang__) && defined(__has_attribute) -#if __has_attribute(__lto_visibility_public__) -#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) -#else -#define WINRT_IMPL_PUBLIC -#endif // __has_attribute(__lto_visibility_public__) -#else -#define WINRT_IMPL_PUBLIC -#endif - -#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC - #if defined(__clang__) #define WINRT_IMPL_HAS_DECLSPEC_UUID __has_declspec_attribute(uuid) #elif defined(_MSC_VER) @@ -140,7 +114,7 @@ typedef struct _GUID GUID; #define WINRT_IMPL_BUILTIN_FUNCTION nullptr #endif -namespace winrt::impl +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 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_macros.h b/strings/base_module_macros.h new file mode 100644 index 000000000..2c9babfad --- /dev/null +++ b/strings/base_module_macros.h @@ -0,0 +1,33 @@ + +// This header provides the preprocessor macros needed by generated C++/WinRT +// headers when the full base.h is not #included (e.g., when using 'import winrt;'). +// Macros are not exported across C++20 module boundaries, so this lightweight +// header must be included textually. + +#ifndef WINRT_EXPORT +#define WINRT_EXPORT +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_EMPTY_BASES __declspec(empty_bases) +#else +#define WINRT_IMPL_EMPTY_BASES +#endif + +#if defined(_MSC_VER) +#define WINRT_IMPL_NOVTABLE __declspec(novtable) +#else +#define WINRT_IMPL_NOVTABLE +#endif + +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(__lto_visibility_public__) +#define WINRT_IMPL_PUBLIC __attribute__((lto_visibility_public)) +#else +#define WINRT_IMPL_PUBLIC +#endif +#else +#define WINRT_IMPL_PUBLIC +#endif + +#define WINRT_IMPL_ABI_DECL WINRT_IMPL_NOVTABLE WINRT_IMPL_PUBLIC 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_security.h b/strings/base_security.h index a912487e4..799ec3e36 100644 --- a/strings/base_security.h +++ b/strings/base_security.h @@ -69,31 +69,38 @@ WINRT_EXPORT namespace winrt check_bool(WINRT_IMPL_SetThreadToken(nullptr, get())); } - auto operator()() const - { - struct guard - { - guard(access_token&& previous) noexcept : m_previous(std::move(previous)) - { - } - - ~guard() - { - m_previous.revert(); - } - - guard(guard const&) - { - // A Visual C++ compiler bug (550631) requires the copy constructor even though it is never called. - WINRT_ASSERT(false); - } + auto operator()() const; + }; +} - private: +WINRT_EXPORT namespace winrt +{ + struct access_token_guard + { + access_token_guard(handle&& previous) noexcept : m_previous(std::move(previous)) + { + } - access_token const m_previous; - }; + ~access_token_guard() + { + check_bool(WINRT_IMPL_SetThreadToken(nullptr, m_previous.get())); + } - return guard(impersonate()); + access_token_guard(access_token_guard const&) + { + // A Visual C++ compiler bug (550631) requires the copy constructor even though it is never called. + WINRT_ASSERT(false); } + + private: + // Work around modules bug that claims access_token is undefined/incomplete here. + handle m_previous; }; + + inline auto access_token::operator()() const + { + auto previous = thread(); + check_bool(WINRT_IMPL_SetThreadToken(nullptr, get())); + return access_token_guard(std::move(previous)); + } } diff --git a/strings/base_std_hash.h b/strings/base_std_hash.h index 864c31b13..cdffb13c6 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 { diff --git a/strings/base_std_includes.h b/strings/base_std_includes.h new file mode 100644 index 000000000..4da15b0c3 --- /dev/null +++ b/strings/base_std_includes.h @@ -0,0 +1,50 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef WINRT_LEAN_AND_MEAN +#include +#endif + +#ifdef __cpp_lib_span +#include +#endif + +#ifdef __cpp_lib_format +#include +#endif + +#ifdef __cpp_lib_source_location +#include +#endif + +#ifdef __cpp_lib_coroutine +#include +#elif defined(_RESUMABLE_FUNCTIONS_SUPPORTED) +#error "C++/WinRT no longer supports pre-standardization coroutines. If you use co_await, switch to /await:strict or upgrade to C++20. If you do not, remove /await from the compiler flags." +#endif diff --git a/strings/base_string.h b/strings/base_string.h index c2ba6c742..92e2ffc6d 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..b2d5c0c1d 100644 --- a/test/nuget/NuGetTest.sln +++ b/test/nuget/NuGetTest.sln @@ -47,6 +47,14 @@ 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}") = "TestModuleBuilder", "TestModuleBuilder\TestModuleBuilder.vcxproj", "{A1B2C3D4-1111-2222-3333-444455556666}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleConsumerApp", "TestModuleConsumerApp\TestModuleConsumerApp.vcxproj", "{A1B2C3D4-5555-6666-7777-888899990000}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleComponent", "TestModuleComponent\TestModuleComponent.vcxproj", "{A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestModuleSingleProject", "TestModuleSingleProject\TestModuleSingleProject.vcxproj", "{A1B2C3D4-FFFF-0000-1111-222233334444}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -279,6 +287,54 @@ 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 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|ARM64.Build.0 = Debug|ARM64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x86.ActiveCfg = Debug|Win32 + {A1B2C3D4-1111-2222-3333-444455556666}.Debug|x86.Build.0 = Debug|Win32 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|ARM64.ActiveCfg = Release|ARM64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|ARM64.Build.0 = Release|ARM64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|x64.Build.0 = Release|x64 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|x86.ActiveCfg = Release|Win32 + {A1B2C3D4-1111-2222-3333-444455556666}.Release|x86.Build.0 = Release|Win32 + {A1B2C3D4-5555-6666-7777-888899990000}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A1B2C3D4-5555-6666-7777-888899990000}.Debug|ARM64.Build.0 = Debug|ARM64 + {A1B2C3D4-5555-6666-7777-888899990000}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-5555-6666-7777-888899990000}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-5555-6666-7777-888899990000}.Debug|x86.ActiveCfg = Debug|Win32 + {A1B2C3D4-5555-6666-7777-888899990000}.Debug|x86.Build.0 = Debug|Win32 + {A1B2C3D4-5555-6666-7777-888899990000}.Release|ARM64.ActiveCfg = Release|ARM64 + {A1B2C3D4-5555-6666-7777-888899990000}.Release|ARM64.Build.0 = Release|ARM64 + {A1B2C3D4-5555-6666-7777-888899990000}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-5555-6666-7777-888899990000}.Release|x64.Build.0 = Release|x64 + {A1B2C3D4-5555-6666-7777-888899990000}.Release|x86.ActiveCfg = Release|Win32 + {A1B2C3D4-5555-6666-7777-888899990000}.Release|x86.Build.0 = Release|Win32 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Debug|ARM64.Build.0 = Debug|ARM64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Debug|x86.ActiveCfg = Debug|Win32 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Debug|x86.Build.0 = Debug|Win32 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Release|ARM64.ActiveCfg = Release|ARM64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Release|ARM64.Build.0 = Release|ARM64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Release|x64.Build.0 = Release|x64 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Release|x86.ActiveCfg = Release|Win32 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000}.Release|x86.Build.0 = Release|Win32 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Debug|ARM64.Build.0 = Debug|ARM64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Debug|x86.ActiveCfg = Debug|Win32 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Debug|x86.Build.0 = Debug|Win32 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Release|ARM64.ActiveCfg = Release|ARM64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Release|ARM64.Build.0 = Release|ARM64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Release|x64.Build.0 = Release|x64 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Release|x86.ActiveCfg = Release|Win32 + {A1B2C3D4-FFFF-0000-1111-222233334444}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/nuget/TestModuleBuilder/PropertySheet.props b/test/nuget/TestModuleBuilder/PropertySheet.props new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/nuget/TestModuleBuilder/PropertySheet.props @@ -0,0 +1 @@ + diff --git a/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj new file mode 100644 index 000000000..2aa196293 --- /dev/null +++ b/test/nuget/TestModuleBuilder/TestModuleBuilder.vcxproj @@ -0,0 +1,95 @@ + + + + + true + true + true + Windows.Foundation + true + 15.0 + {A1B2C3D4-1111-2222-3333-444455556666} + Win32Proj + TestModuleBuilder + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + StaticLibrary + Unicode + + + true + + + false + true + + + + + + + + + + + + + stdcpplatest + NOMINMAX;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + true + %(AdditionalOptions) /permissive- /bigobj + true + NotUsing + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + + + + diff --git a/test/nuget/TestModuleBuilder/readme.txt b/test/nuget/TestModuleBuilder/readme.txt new file mode 100644 index 000000000..e5c36fd68 --- /dev/null +++ b/test/nuget/TestModuleBuilder/readme.txt @@ -0,0 +1,9 @@ +This test project builds the winrt C++20 module (winrt.ixx) via the NuGet targets. + +It sets CppWinRTModuleBuild=true, which causes the NuGet targets to: +1. Generate the platform SDK projection headers +2. Compile winrt.ixx as a C++20 module interface unit +3. Export the module IFC/OBJ paths via CppWinRTGetModuleOutputs + +Other projects reference this one and set CppWinRTModuleConsume=true +to automatically pick up the IFC, OBJ, and include paths. diff --git a/test/nuget/TestModuleComponent/TestModuleComponent.def b/test/nuget/TestModuleComponent/TestModuleComponent.def new file mode 100644 index 000000000..d04a02ea8 --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponent.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/test/nuget/TestModuleComponent/TestModuleComponent.vcxproj b/test/nuget/TestModuleComponent/TestModuleComponent.vcxproj new file mode 100644 index 000000000..a1e617d20 --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponent.vcxproj @@ -0,0 +1,128 @@ + + + + + true + true + true + true + 15.0 + {A1B2C3D4-AAAA-BBBB-CCCC-DDDDEEEE0000} + Win32Proj + TestModuleComponent + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + DynamicLibrary + Unicode + + + true + + + false + true + + + + + + + + + + + + + stdcpplatest + Use + pch.h + _WINRT_DLL;NOMINMAX;%(PreprocessorDefinitions) + Level4 + true + %(AdditionalOptions) /permissive- /bigobj + true + + + TestModuleComponent.def + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + + + + Create + + + + + + + + Designer + + + Designer + + + + + + + + diff --git a/test/nuget/TestModuleComponent/TestModuleComponentClass.cpp b/test/nuget/TestModuleComponent/TestModuleComponentClass.cpp new file mode 100644 index 000000000..7abd3a528 --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponentClass.cpp @@ -0,0 +1,27 @@ +#include "pch.h" +#include "TestModuleComponentClass.h" +#include "TestModuleComponentWidget.h" +#include "TestModuleComponentClass.g.cpp" + +namespace winrt::TestModuleComponent::implementation +{ + void TestModuleComponentClass::Test() + { + } + + winrt::TestModuleComponent::Widgets::Widget TestModuleComponentClass::CreateWidget() + { + return winrt::make(L"FromComponent"); + } + + winrt::TestModuleComponent::WidgetInfo TestModuleComponentClass::GetWidgetInfo() + { + auto widget = CreateWidget(); + return { widget.Size(), L"A test widget" }; + } + + winrt::Windows::Foundation::Uri TestModuleComponentClass::GetUri() + { + return winrt::Windows::Foundation::Uri(L"http://aka.ms/cppwinrt"); + } +} diff --git a/test/nuget/TestModuleComponent/TestModuleComponentClass.h b/test/nuget/TestModuleComponent/TestModuleComponentClass.h new file mode 100644 index 000000000..313a384fe --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponentClass.h @@ -0,0 +1,20 @@ +#pragma once +#include "TestModuleComponentClass.g.h" + +namespace winrt::TestModuleComponent::implementation +{ + struct TestModuleComponentClass : TestModuleComponentClassT + { + TestModuleComponentClass() = default; + void Test(); + winrt::TestModuleComponent::Widgets::Widget CreateWidget(); + winrt::TestModuleComponent::WidgetInfo GetWidgetInfo(); + winrt::Windows::Foundation::Uri GetUri(); + }; +} +namespace winrt::TestModuleComponent::factory_implementation +{ + struct TestModuleComponentClass : TestModuleComponentClassT + { + }; +} diff --git a/test/nuget/TestModuleComponent/TestModuleComponentClass.idl b/test/nuget/TestModuleComponent/TestModuleComponentClass.idl new file mode 100644 index 000000000..993b37213 --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponentClass.idl @@ -0,0 +1,20 @@ +import "TestModuleComponentWidget.idl"; + +namespace TestModuleComponent +{ + struct WidgetInfo + { + TestModuleComponent.Widgets.WidgetSize Size; + String Description; + }; + + [default_interface] + runtimeclass TestModuleComponentClass + { + TestModuleComponentClass(); + void Test(); + TestModuleComponent.Widgets.Widget CreateWidget(); + WidgetInfo GetWidgetInfo(); + Windows.Foundation.Uri GetUri(); + } +} diff --git a/test/nuget/TestModuleComponent/TestModuleComponentWidget.cpp b/test/nuget/TestModuleComponent/TestModuleComponentWidget.cpp new file mode 100644 index 000000000..f1d3fcde6 --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponentWidget.cpp @@ -0,0 +1,3 @@ +#include "pch.h" +#include "TestModuleComponentWidget.h" +#include "Widgets.Widget.g.cpp" diff --git a/test/nuget/TestModuleComponent/TestModuleComponentWidget.h b/test/nuget/TestModuleComponent/TestModuleComponentWidget.h new file mode 100644 index 000000000..f2234c80d --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponentWidget.h @@ -0,0 +1,20 @@ +#pragma once +#include "Widgets.Widget.g.h" + +namespace winrt::TestModuleComponent::Widgets::implementation +{ + struct Widget : WidgetT + { + Widget() = default; + Widget(hstring const& name) : m_name(name) {} + hstring Name() const { return m_name; } + winrt::TestModuleComponent::Widgets::WidgetSize Size() const { return { 100.0f, 50.0f }; } + + private: + hstring m_name{ L"DefaultWidget" }; + }; +} +namespace winrt::TestModuleComponent::Widgets::factory_implementation +{ + struct Widget : WidgetT {}; +} diff --git a/test/nuget/TestModuleComponent/TestModuleComponentWidget.idl b/test/nuget/TestModuleComponent/TestModuleComponentWidget.idl new file mode 100644 index 000000000..7a1814f7e --- /dev/null +++ b/test/nuget/TestModuleComponent/TestModuleComponentWidget.idl @@ -0,0 +1,17 @@ +namespace TestModuleComponent.Widgets +{ + struct WidgetSize + { + Single Width; + Single Height; + }; + + [default_interface] + runtimeclass Widget + { + Widget(); + Widget(String name); + String Name{ get; }; + WidgetSize Size{ get; }; + } +} diff --git a/test/nuget/TestModuleComponent/pch.cpp b/test/nuget/TestModuleComponent/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/nuget/TestModuleComponent/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/nuget/TestModuleComponent/pch.h b/test/nuget/TestModuleComponent/pch.h new file mode 100644 index 000000000..c1a1a1446 --- /dev/null +++ b/test/nuget/TestModuleComponent/pch.h @@ -0,0 +1,4 @@ +#pragma once +// PCH for a module-consuming component: do NOT include winrt/ headers here. +// Platform types come from 'import winrt;' in the .g.h files. +#include diff --git a/test/nuget/TestModuleComponent/readme.txt b/test/nuget/TestModuleComponent/readme.txt new file mode 100644 index 000000000..0b0468db9 --- /dev/null +++ b/test/nuget/TestModuleComponent/readme.txt @@ -0,0 +1,9 @@ +This test project is a WinRT component (DLL) that consumes a pre-built winrt +C++20 module from TestModuleBuilder. + +It sets CppWinRTModuleConsume=true and has IDL files, which exercises the +component authoring workflow with modules: +- The NuGet targets pass -module to cppwinrt.exe +- Generated .g.h files use 'import winrt;' instead of #include directives +- The PCH does NOT include winrt/ headers (they come from the module) +- Component projection is still generated for the project's own types diff --git a/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj new file mode 100644 index 000000000..bfab231ad --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/TestModuleConsumerApp.vcxproj @@ -0,0 +1,117 @@ + + + + + true + true + true + true + 15.0 + {A1B2C3D4-5555-6666-7777-888899990000} + Win32Proj + TestModuleConsumerApp + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + Application + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + stdcpplatest + _CONSOLE;NOMINMAX;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + true + %(AdditionalOptions) /permissive- /bigobj + true + NotUsing + + + Console + false + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + false + + + + + + + + + diff --git a/test/nuget/TestModuleConsumerApp/main.cpp b/test/nuget/TestModuleConsumerApp/main.cpp new file mode 100644 index 000000000..aac02868f --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/main.cpp @@ -0,0 +1,35 @@ +import std; +import winrt; + +// Include just the root component namespace header — NOT the Widgets namespace. +// CreateWidget() returns a Widgets::Widget, but we can call it and hold the +// result without including the Widgets header, as long as we don't call +// methods on the Widget object itself. +#include + +using namespace winrt; +using namespace winrt::Windows::Foundation; + +void test_platform_types(); +void test_widget_usage(); + +int main() +{ + init_apartment(); + + // Platform types (from the winrt module) + Uri uri(L"http://aka.ms/cppwinrt"); + std::printf("Hello, %ls!\n", uri.AbsoluteUri().c_str()); + + // Component types — call a method that returns a cross-namespace type + // without including the Widgets namespace header. + winrt::TestModuleComponent::TestModuleComponentClass component; + component.Test(); + auto widget = component.CreateWidget(); // Returns Widgets::Widget + // We can hold 'widget' but to call Widget.Name() we'd need the Widgets header. + std::printf("Component test passed.\n"); + + // Delegate to other TUs that test more scenarios + test_platform_types(); + test_widget_usage(); +} diff --git a/test/nuget/TestModuleConsumerApp/platform_test.cpp b/test/nuget/TestModuleConsumerApp/platform_test.cpp new file mode 100644 index 000000000..ffad075ec --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/platform_test.cpp @@ -0,0 +1,13 @@ +import std; +import winrt; + +// Test using platform types from the module in a separate TU. +// No component headers needed here. +using namespace winrt; +using namespace winrt::Windows::Foundation; + +void test_platform_types() +{ + Uri uri(L"https://example.com"); + std::printf("Platform types in separate TU: %ls\n", uri.Domain().c_str()); +} diff --git a/test/nuget/TestModuleConsumerApp/readme.txt b/test/nuget/TestModuleConsumerApp/readme.txt new file mode 100644 index 000000000..f199ccfe8 --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/readme.txt @@ -0,0 +1,10 @@ +This test project consumes a pre-built winrt C++20 module from TestModuleBuilder. + +It sets CppWinRTModuleConsume=true, which causes the NuGet targets to: +1. Resolve the IFC/OBJ from the TestModuleBuilder project reference +2. Add the IFC to AdditionalModuleDependencies +3. Add the OBJ to linker AdditionalDependencies +4. Add the builder's GeneratedFilesDir to AdditionalIncludeDirectories +5. Skip regenerating the platform projection (CppWinRTEnablePlatformProjection=false) + +Source files use 'import winrt;' instead of #include directives. diff --git a/test/nuget/TestModuleConsumerApp/widget_test.cpp b/test/nuget/TestModuleConsumerApp/widget_test.cpp new file mode 100644 index 000000000..8da4c962c --- /dev/null +++ b/test/nuget/TestModuleConsumerApp/widget_test.cpp @@ -0,0 +1,27 @@ +import std; +import winrt; + +// This TU includes BOTH component namespace headers so it can call methods +// on the Widget type returned by CreateWidget(). +#include +#include + +void test_widget_usage() +{ + winrt::TestModuleComponent::TestModuleComponentClass component; + auto widget = component.CreateWidget(); + + // Now we CAN call Widget methods because we included the Widgets header. + auto name = widget.Name(); + std::printf("Widget name: %ls\n", name.c_str()); + + // Test the cross-namespace struct (WidgetSize field from Widgets namespace + // in a struct defined in the root TestModuleComponent namespace). + auto info = component.GetWidgetInfo(); + std::printf("Widget info: %ls (%.0fx%.0f)\n", info.Description.c_str(), info.Size.Width, info.Size.Height); + + // Test calling a method that returns a platform type (Windows.Foundation.Uri). + // The platform type comes from the module, the component type doesn't. + auto uri = component.GetUri(); + std::printf("Component URI: %ls\n", uri.AbsoluteUri().c_str()); +} diff --git a/test/nuget/TestModuleSingleProject/TestModuleSingleProject.vcxproj b/test/nuget/TestModuleSingleProject/TestModuleSingleProject.vcxproj new file mode 100644 index 000000000..53f2b9afe --- /dev/null +++ b/test/nuget/TestModuleSingleProject/TestModuleSingleProject.vcxproj @@ -0,0 +1,112 @@ + + + + + true + true + true + Windows.Foundation + true + 15.0 + {A1B2C3D4-FFFF-0000-1111-222233334444} + Win32Proj + TestModuleSingleProject + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + Application + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + stdcpplatest + _CONSOLE;NOMINMAX;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + true + %(AdditionalOptions) /permissive- /bigobj + true + NotUsing + + + Console + false + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + false + + + + + diff --git a/test/nuget/TestModuleSingleProject/main.cpp b/test/nuget/TestModuleSingleProject/main.cpp new file mode 100644 index 000000000..f399aa59b --- /dev/null +++ b/test/nuget/TestModuleSingleProject/main.cpp @@ -0,0 +1,12 @@ +import std; +import winrt; + +using namespace winrt; +using namespace winrt::Windows::Foundation; + +int main() +{ + init_apartment(); + Uri uri(L"http://aka.ms/cppwinrt"); + std::printf("Hello, %ls!\n", uri.AbsoluteUri().c_str()); +} diff --git a/test/nuget/TestModuleSingleProject/readme.txt b/test/nuget/TestModuleSingleProject/readme.txt new file mode 100644 index 000000000..ec2f23236 --- /dev/null +++ b/test/nuget/TestModuleSingleProject/readme.txt @@ -0,0 +1,9 @@ +This test project both builds and consumes the winrt C++20 module in a single project. + +It sets CppWinRTModuleBuild=true only (no CppWinRTModuleConsume), which causes +the NuGet targets to: +1. Generate the platform SDK projection headers +2. Compile winrt.ixx as a C++20 module interface unit +3. Make the module available for import within the same project + +This validates the single-project scenario where there is no separate builder. diff --git a/test/test/coro_threadpool.cpp b/test/test/coro_threadpool.cpp index 4c31f8a03..42e6fb234 100644 --- a/test/test/coro_threadpool.cpp +++ b/test/test/coro_threadpool.cpp @@ -1,8 +1,13 @@ // Intentionally not using pch... -#include "catch.hpp" +#include +#include +#ifdef WINRT_TEST_MODULES +import winrt; +#else // Only need winrt/base.h for coroutine thread pool support. #include "winrt/base.h" +#endif using namespace winrt; diff --git a/test/test_component_module/Toaster.cpp b/test/test_component_module/Toaster.cpp new file mode 100644 index 000000000..e56300784 --- /dev/null +++ b/test/test_component_module/Toaster.cpp @@ -0,0 +1,4 @@ +#include "pch.h" + +#include "Toaster.h" +#include "Toaster.g.cpp" diff --git a/test/test_component_module/Toaster.h b/test/test_component_module/Toaster.h new file mode 100644 index 000000000..d23c4f026 --- /dev/null +++ b/test/test_component_module/Toaster.h @@ -0,0 +1,34 @@ +#pragma once +#include "Toaster.g.h" + +namespace winrt::test_component_module::implementation +{ + struct Toaster : ToasterT + { + Toaster() = default; + Toaster(hstring const& name) : m_name(name) {} + + hstring Name() const { return m_name; } + void Name(hstring const& value) { m_name = value; } + + Windows::Foundation::IAsyncAction ToastAsync() + { + co_return; + } + + static test_component_module::Toaster CreateDefault() + { + return make(L"Default Toaster"); + } + + private: + hstring m_name{ L"Unnamed" }; + }; +} + +namespace winrt::test_component_module::factory_implementation +{ + struct Toaster : ToasterT + { + }; +} diff --git a/test/test_component_module/exports.def b/test/test_component_module/exports.def new file mode 100644 index 000000000..12bc1c6d8 --- /dev/null +++ b/test/test_component_module/exports.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow PRIVATE +DllGetActivationFactory PRIVATE diff --git a/test/test_component_module/module.cpp b/test/test_component_module/module.cpp new file mode 100644 index 000000000..a685bc824 --- /dev/null +++ b/test/test_component_module/module.cpp @@ -0,0 +1,34 @@ +#include "pch.h" + +#ifdef WINRT_IMPORT_STD +import std; +#endif +import winrt; + +bool __stdcall test_module_can_unload_now() noexcept; +void* __stdcall test_module_get_activation_factory(std::wstring_view const& name); + +std::int32_t __stdcall DllCanUnloadNow() noexcept +{ + return test_module_can_unload_now() ? 0 : 1; +} + +std::int32_t __stdcall DllGetActivationFactory(void* classId, void** factory) noexcept +{ + try + { + std::wstring_view const name{ *reinterpret_cast(&classId) }; + *factory = test_module_get_activation_factory(name); + + if (*factory) + { + return 0; + } + + return winrt::hresult_class_not_available(name).to_abi(); + } + catch (...) + { + return winrt::to_hresult(); + } +} diff --git a/test/test_component_module/pch.cpp b/test/test_component_module/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/test/test_component_module/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/test/test_component_module/pch.h b/test/test_component_module/pch.h new file mode 100644 index 000000000..c757a4cd9 --- /dev/null +++ b/test/test_component_module/pch.h @@ -0,0 +1,7 @@ +#pragma once + +#ifndef WINRT_IMPORT_STD +#include +#include +#include +#endif diff --git a/test/test_component_module/test_component_module.idl b/test/test_component_module/test_component_module.idl new file mode 100644 index 000000000..55572d2dc --- /dev/null +++ b/test/test_component_module/test_component_module.idl @@ -0,0 +1,14 @@ +import "Windows.Foundation.Numerics.idl"; + +namespace test_component_module +{ + runtimeclass Toaster + { + Toaster(); + Toaster(String name); + String Name{ get; set; }; + Windows.Foundation.IAsyncAction ToastAsync(); + + static Toaster CreateDefault(); + } +} diff --git a/test/test_component_module/test_component_module.vcxproj b/test/test_component_module/test_component_module.vcxproj new file mode 100644 index 000000000..97ec03b6d --- /dev/null +++ b/test/test_component_module/test_component_module.vcxproj @@ -0,0 +1,171 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {E1D3004A-7F92-4C2B-B25A-3D3A29E316A1} + test_component_module + test_component_module + 20 + + + + DynamicLibrary + true + v143 + + + DynamicLibrary + true + v143 + + + DynamicLibrary + false + true + v143 + + + DynamicLibrary + false + true + v143 + + + DynamicLibrary + true + v143 + + + DynamicLibrary + false + true + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + Midl + + + + .;$(ProjectDir)Generated Files;$(SolutionDir)test\test_cpp20_module_winrt\Generated Files;Generated Files + NOMINMAX;WINRT_MODULE;%(PreprocessorDefinitions) + Use + true + $(IntDir)..\test_cpp20_module_winrt\winrt.ixx.ifc;%(AdditionalModuleDependencies) + + + exports.def + $(IntDir)..\test_cpp20_module_winrt\winrt.ixx.obj;%(AdditionalDependencies) + + + true + $(OutputPath)test_component_module.winmd + $(OutputPath)test_component_module.h + /nomidl %(AdditionalOptions) + C:\Windows\System32\WinMetadata + true + + + + $(CppWinRTDir)cppwinrt -input $(OutputPath)test_component_module.winmd -comp "$(ProjectDir)Generated Files" -out "$(ProjectDir)Generated Files" -include test_component_module -ref sdk -verbose -prefix -opt -lib test_module -overwrite -name test_component_module + + Projecting Windows and component metadata + Generated Files\module.g.cpp + $(CppWinRTDir)cppwinrt.exe;$(OutputPath)test_component_module.winmd + + + + + Disabled + MultiThreadedDebug + + + + + MaxSpeed + true + true + MultiThreaded + + + true + true + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/test_cpp20_module/async.cpp b/test/test_cpp20_module/async.cpp new file mode 100644 index 000000000..285fc2030 --- /dev/null +++ b/test/test_cpp20_module/async.cpp @@ -0,0 +1,37 @@ +// All #include directives must come before module imports to avoid +// redefinition conflicts with types already in the module. +#include "pch.h" + +import winrt; + +using namespace winrt; +using namespace winrt::Windows::Foundation; + +// Verify coroutines work through the module by exercising IAsyncOperation +static IAsyncOperation AddAsync(int a, int b) +{ + co_return a + b; +} + +static IAsyncAction DoNothingAsync() +{ + co_return; +} + +TEST_CASE("module_async_operation") +{ + auto result = AddAsync(3, 4).get(); + REQUIRE(result == 7); +} + +TEST_CASE("module_async_action") +{ + DoNothingAsync().get(); +} + +TEST_CASE("module_async_await") +{ + auto op = AddAsync(10, 20); + auto result = op.get(); + REQUIRE(result == 30); +} diff --git a/test/test_cpp20_module/component.cpp b/test/test_cpp20_module/component.cpp new file mode 100644 index 000000000..3f4b5f4c3 --- /dev/null +++ b/test/test_cpp20_module/component.cpp @@ -0,0 +1,48 @@ +// Test consuming a custom WinRT component through a module. +// The component projection header is included AFTER importing winrt, +// which tests the interaction between module-imported base types and +// #include-based component projection headers. +// WINRT_MODULE (defined project-wide) causes namespace headers to use +// per-namespace WINRT_MODULE_NS_* guards so platform deps are skipped +// while component deps are included normally. +#include "catch.hpp" +#include + +import std; +import winrt; + +#include "winrt/test_component_module.h" + +using namespace winrt; +using namespace winrt::test_component_module; + +TEST_CASE("module_component_create") +{ + auto toaster = Toaster(); + REQUIRE(toaster.Name() == L"Unnamed"); +} + +TEST_CASE("module_component_create_with_name") +{ + auto toaster = Toaster(L"My Toaster"); + REQUIRE(toaster.Name() == L"My Toaster"); +} + +TEST_CASE("module_component_property") +{ + auto toaster = Toaster(); + toaster.Name(L"Updated"); + REQUIRE(toaster.Name() == L"Updated"); +} + +TEST_CASE("module_component_static") +{ + auto toaster = Toaster::CreateDefault(); + REQUIRE(toaster.Name() == L"Default Toaster"); +} + +TEST_CASE("module_component_async") +{ + auto toaster = Toaster(); + toaster.ToastAsync().get(); +} diff --git a/test/test_cpp20_module/interop.cpp b/test/test_cpp20_module/interop.cpp new file mode 100644 index 000000000..e265bd800 --- /dev/null +++ b/test/test_cpp20_module/interop.cpp @@ -0,0 +1,131 @@ +#include "pch.h" + +import winrt; + +using namespace winrt; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; + +// ---- Standard library interop ---- + +TEST_CASE("module_std_wstring_interop") +{ + // Construct hstring from std::wstring + std::wstring ws = L"hello from std::wstring"; + hstring h{ ws }; + REQUIRE(h == L"hello from std::wstring"); + + // Convert back + std::wstring_view view = h; + REQUIRE(view == L"hello from std::wstring"); + + // Concatenation + std::wstring combined = std::wstring(h) + L" world"; + REQUIRE(combined == L"hello from std::wstring world"); +} + +TEST_CASE("module_box_std_wstring") +{ + // box_value with a wide string literal + auto boxed = box_value(L"boxed literal"); + REQUIRE(unbox_value(boxed) == L"boxed literal"); + + // box_value with hstring + hstring hs = L"boxed hstring"; + auto boxed2 = box_value(hs); + REQUIRE(unbox_value(boxed2) == L"boxed hstring"); +} + +TEST_CASE("module_box_numeric_types") +{ + auto boxed_int = box_value(42); + REQUIRE(unbox_value(boxed_int) == 42); + + auto boxed_double = box_value(3.14); + REQUIRE(unbox_value(boxed_double) == 3.14); + + auto boxed_bool = box_value(true); + REQUIRE(unbox_value(boxed_bool) == true); +} + +// ---- Collections ---- + +TEST_CASE("module_vector_view") +{ + auto vec = single_threaded_vector({ L"one", L"two", L"three" }); + REQUIRE(vec.Size() == 3); + REQUIRE(vec.GetAt(0) == L"one"); + REQUIRE(vec.GetAt(2) == L"three"); + + // Iterate + int count = 0; + for (auto const& item : vec) + { + count++; + REQUIRE(!item.empty()); + } + REQUIRE(count == 3); +} + +TEST_CASE("module_map") +{ + auto map = single_threaded_map(); + map.Insert(L"a", 1); + map.Insert(L"b", 2); + map.Insert(L"c", 3); + + REQUIRE(map.Size() == 3); + REQUIRE(map.Lookup(L"b") == 2); + REQUIRE(map.HasKey(L"c")); + REQUIRE(!map.HasKey(L"d")); +} + +TEST_CASE("module_observable_vector") +{ + auto vec = single_threaded_observable_vector(); + int callback_count = 0; + vec.VectorChanged([&](auto&&, auto&&) { callback_count++; }); + + vec.Append(L"first"); + vec.Append(L"second"); + REQUIRE(vec.Size() == 2); + REQUIRE(callback_count == 2); +} + +// ---- Errors and HRESULT ---- + +TEST_CASE("module_hresult_error") +{ + try + { + throw hresult_invalid_argument(L"test error message"); + } + catch (hresult_error const& e) + { + REQUIRE(e.code() == E_INVALIDARG); + REQUIRE(to_string(e.message()).find("test error message") != std::string::npos); + } +} + +// ---- GUIDs ---- + +TEST_CASE("module_guid") +{ + auto g = guid_of(); + REQUIRE(g != guid{}); + + // Round-trip through string + auto str = to_hstring(g); + REQUIRE(!str.empty()); +} + +// ---- Format (C++20) ---- + +#ifdef __cpp_lib_format +TEST_CASE("module_format") +{ + hstring str = L"World"; + auto result = std::format(L"Hello {}", str); + REQUIRE(result == L"Hello World"); +} +#endif diff --git a/test/test_cpp20_module/main.cpp b/test/test_cpp20_module/main.cpp new file mode 100644 index 000000000..98794553b --- /dev/null +++ b/test/test_cpp20_module/main.cpp @@ -0,0 +1,53 @@ +#include + +#define CATCH_CONFIG_RUNNER +#define CATCH_CONFIG_WINDOWS_SEH + +#include "catch.hpp" + +import winrt; + +using namespace winrt; +using namespace winrt::Windows::Foundation; + +int main(int const argc, char** argv) +{ + init_apartment(); + std::set_terminate([] { reportFatal("Abnormal termination"); std::abort(); }); + _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()); +} + +TEST_CASE("module_hstring") +{ + hstring str = L"Hello, C++/WinRT Modules!"; + REQUIRE(str == L"Hello, C++/WinRT Modules!"); + REQUIRE(str.size() == 25); + + std::wstring_view view = str; + REQUIRE(view.length() == 25); +} + +TEST_CASE("module_uri") +{ + Uri uri(L"https://example.com/path?query=1"); + REQUIRE(uri.Domain() == L"example.com"); + REQUIRE(uri.Path() == L"/path"); + REQUIRE(uri.SchemeName() == L"https"); +} + +TEST_CASE("module_collections") +{ + Collections::PropertySet props; + props.Insert(L"key", box_value(L"value")); + REQUIRE(unbox_value(props.Lookup(L"key")) == L"value"); + REQUIRE(props.Size() == 1); +} 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..005155af0 --- /dev/null +++ b/test/test_cpp20_module/pch.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include 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..4324c972c --- /dev/null +++ b/test/test_cpp20_module/test_cpp20_module.vcxproj @@ -0,0 +1,164 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {B85004D5-2C57-4A53-BA7F-FE0B614EA1DE} + unittests + test_cpp20_module + 20 + + + + Application + true + v143 + + + Application + true + v143 + + + Application + false + true + v143 + + + Application + false + true + v143 + + + Application + true + v143 + + + Application + false + true + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)test\test_cpp20_module_winrt\Generated Files;..\;%(AdditionalIncludeDirectories) + NOMINMAX;WINRT_LEAN_AND_MEAN;WINRT_IMPORT_STD;WINRT_MODULE;WINRT_TEST_MODULES;%(PreprocessorDefinitions) + Use + true + $(IntDir)..\test_cpp20_module_winrt\winrt.ixx.ifc;%(AdditionalModuleDependencies) + + + Console + $(IntDir)..\test_cpp20_module_winrt\winrt.ixx.obj;%(AdditionalDependencies) + + + + + Disabled + MultiThreadedDebug + + + + + MaxSpeed + true + true + MultiThreaded + + + true + true + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + Create + Create + Create + Create + Create + Create + + + + + + + + + \ No newline at end of file diff --git a/test/test_cpp20_module_winrt/test_cpp20_module_winrt.vcxproj b/test/test_cpp20_module_winrt/test_cpp20_module_winrt.vcxproj new file mode 100644 index 000000000..31160eda5 --- /dev/null +++ b/test/test_cpp20_module_winrt/test_cpp20_module_winrt.vcxproj @@ -0,0 +1,133 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {F39B6BA2-3E78-4A01-BF72-D52C39AC3D8C} + test_cpp20_module_winrt + test_cpp20_module_winrt + 20 + + + + StaticLibrary + true + v143 + + + StaticLibrary + true + v143 + + + StaticLibrary + false + true + v143 + + + StaticLibrary + false + true + v143 + + + StaticLibrary + true + v143 + + + StaticLibrary + false + true + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)Generated Files;%(AdditionalIncludeDirectories) + NotUsing + true + + + $(CppWinRTDir)cppwinrt -in sdk -out "$(ProjectDir)Generated Files" -module_filter Windows.Foundation -verbose + Generating C++/WinRT SDK projection (filtered: Windows.Foundation) + + + + + Disabled + MultiThreadedDebug + + + + + MaxSpeed + true + true + MultiThreaded + + + + + + + + + diff --git a/test/test_cpp23_module/test_cpp23_module.vcxproj b/test/test_cpp23_module/test_cpp23_module.vcxproj new file mode 100644 index 000000000..b098ebbac --- /dev/null +++ b/test/test_cpp23_module/test_cpp23_module.vcxproj @@ -0,0 +1,151 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {C8D4E5F6-7A8B-4C9D-BE0F-1A2B3C4D5E6F} + test_cpp23_module + test_cpp23_module + + + + Application + true + v143 + + + Application + true + v143 + + + Application + false + true + v143 + + + Application + false + true + v143 + + + Application + true + v143 + + + Application + false + true + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)test\test_cpp23_module_winrt\Generated Files;..\;%(AdditionalIncludeDirectories) + NOMINMAX;WINRT_LEAN_AND_MEAN;WINRT_IMPORT_STD;WINRT_MODULE;WINRT_TEST_MODULES;%(PreprocessorDefinitions) + Use + true + stdcpplatest + $(IntDir)..\test_cpp23_module_winrt\winrt.ixx.ifc;%(AdditionalModuleDependencies) + + + Console + $(IntDir)..\test_cpp23_module_winrt\winrt.ixx.obj;%(AdditionalDependencies) + + + + + Disabled + MultiThreadedDebug + + + + + MaxSpeed + true + true + MultiThreaded + + + true + true + + + + + + NotUsing + + + + + NotUsing + + + Create + + + + + + + + + diff --git a/test/test_cpp23_module_winrt/test_cpp23_module_winrt.vcxproj b/test/test_cpp23_module_winrt/test_cpp23_module_winrt.vcxproj new file mode 100644 index 000000000..de6e7f22c --- /dev/null +++ b/test/test_cpp23_module_winrt/test_cpp23_module_winrt.vcxproj @@ -0,0 +1,131 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {A7C3B845-6F1D-4E3A-9B82-7D4F5E6C8A12} + test_cpp23_module_winrt + test_cpp23_module_winrt + + + + StaticLibrary + true + v143 + + + StaticLibrary + true + v143 + + + StaticLibrary + false + true + v143 + + + StaticLibrary + false + true + v143 + + + StaticLibrary + true + v143 + + + StaticLibrary + false + true + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)Generated Files;%(AdditionalIncludeDirectories) + NotUsing + true + stdcpplatest + WINRT_IMPORT_STD;%(PreprocessorDefinitions) + + + $(CppWinRTDir)cppwinrt -in sdk -out "$(ProjectDir)Generated Files" -module_filter Windows.Foundation -verbose + Generating C++/WinRT SDK projection + + + + + Disabled + MultiThreadedDebug + + + + + MaxSpeed + true + true + MultiThreaded + + + + + + + + +