WinUI XAML Compiler Generate Module-Unaware C++ Code and Lacks C++/WinRT 3.x Named Module Build Integration

Author: hoshiizumiyaCreated Aug 14, 2026Updated Sep 18, 2026
Labelsbugarea-XamlCompilerarea-C++/WinRTteam-Core

C++/WinRT 3.x named-module projects require first-class module-aware integration in the WinUI XAML compiler

Summary

C++/WinRT 3.x supports per-namespace C++20 named modules such as import winrt.Windows.Foundation;, import winrt.Microsoft.UI.Xaml;, and import winrt.MyProject.Controls; when a project enables <CppWinRTBuildModule>true</CppWinRTBuildModule>. The current WinUI XAML compiler and its MSBuild integration, however, still generate C++/WinRT code according to the traditional textual projection-header model. This is not limited to one generated source file. The module-mode state is not propagated into the XAML compiler at all, the compiler's internal dependency model commonly lowers WinRT type dependencies directly to winrt/<Namespace>.h filenames, and multiple C++/WinRT XAML generators independently emit projection headers. As a result, a real module-enabled WinUI C++ project must build a compatibility layer around XAML-generated code by manually importing projections, defining WINRT_IMPORT_MODULE, force-including a project-owned preamble into generated translation units, and carefully managing textual WinRT/COM/Win32/STL headers that the XAML compiler still emits.

The C++/WinRT modules documentation already calls XAML projects a special case and documents an /FI module-preamble workaround for XamlTypeInfo.g.cpp, XamlTypeInfo.Impl.g.cpp, XamlMetaDataProvider.cpp, and XamlLibMetadataProvider.g.cpp: https://github.com/microsoft/cppwinrt/blob/d6cff316a991152071038668fb586a8b5ab1d51f/nuget/modules.md?plain=1#L361-L419. That workaround is useful as migration guidance, but it should not be the permanent framework contract. The XAML compiler already discovers most of the semantic WinRT dependencies required by its generated code. The missing piece is a supported integration path that preserves those dependencies as semantic projection dependencies until they can be emitted in a form compatible with the selected C++/WinRT consumption mode.

A real-world conversion that demonstrates the scale of this gap is OpenNet: https://github.com/hoshiizumiya/OpenNet/commit/92dacc87cefe4a3ee29e46c23c1c3490108ac42a. The project enables both <CppWinRTBuildModule>true</CppWinRTBuildModule> and <BuildStlModules>true</BuildStlModules>, but still has to add project-specific MSBuild targets and a forced-include compatibility header for XAML-generated C++. This is evidence of a framework integration gap, not a proposal that every application should copy the same workaround.

Source revisions used for this analysis

The implementation analysis below was checked against microsoft/microsoft-ui-xaml revision 6112d936461edb6d81ce7db983c74cc60ea2bc28 and the current C++/WinRT module implementation/documentation around revision d6cff316a991152071038668fb586a8b5ab1d51f. Stable commit-pinned source links are preferable for implementation discussion because generated templates and MSBuild targets can change independently on main. The user-facing documentation links can still point at master/main so readers reach the current guidance.

Why this is a framework-level integration problem rather than a single generated-file bug

It would be easy to describe the problem narrowly as "XamlTypeInfo.g.cpp emits #include <winrt/...h> when a module project needs import winrt...;", but source inspection shows that the mismatch starts earlier and affects more of the C++/WinRT XAML code-generation pipeline. The XAML compiler currently has no explicit input that tells it that the containing C++/WinRT project is operating in named-module mode. Its project model has no corresponding mode state. Its page dependency analysis stores C++/WinRT dependencies in a header-shaped representation. App, binding, type-info, and metadata-provider generators contain their own textual projection includes. The XAML compiler therefore cannot make a correct module-aware decision because the information and abstraction required to make that decision do not currently exist at the appropriate layer.

The important distinction is between a semantic dependency and its C++ spelling. If generated code requires the Microsoft.UI.Xaml.Controls projection, the semantic dependency is "this generated unit needs the Microsoft.UI.Xaml.Controls C++/WinRT projection." In traditional mode that may be lowered to #include <winrt/Microsoft.UI.Xaml.Controls.h>. In named-module mode it may instead be satisfied by import winrt.Microsoft.UI.Xaml.Controls;, or by an automatically generated module preamble whose imports are supplied before the generated code. The compiler should not conceptually treat the header filename itself as the dependency.

Current C++/WinRT module model

C++/WinRT 3.x can generate .ixx module interface files for WinRT namespaces. The supported project switch is:

xml
<PropertyGroup>
  <CppWinRTBuildModule>true</CppWinRTBuildModule>
</PropertyGroup>

For import std;, the project also enables:

xml
<ItemDefinitionGroup>
  <ClCompile>
    <BuildStlModules>true</BuildStlModules>
  </ClCompile>
</ItemDefinitionGroup>

A consumer can then use projection modules such as:

cpp
import winrt.Windows.Foundation;
import winrt.Microsoft.UI.Xaml;
import winrt.Microsoft.UI.Xaml.Controls;

C++/WinRT also supports module-generation filtering and cross-project consumption. Relevant properties and metadata include CppWinRTModuleInclude, CppWinRTModuleExclude, and CppWinRTConsumeModule. A namespace module can therefore be produced by the current project, supplied by a module-builder project, supplied through a referenced static library's BMI propagation, or intentionally excluded from local generation. This matters for the XAML integration design: the XAML compiler should identify semantic projection dependencies, while C++/WinRT/MSBuild should remain responsible for resolving where the corresponding IFC/BMI comes from.

The official C++/WinRT modules guide also documents three constraints that explain why legacy generated XAML code becomes difficult to consume in a module project. Module imports cannot simply be placed in a traditional PCH. Include-then-import is generally possible but defeats much of the purpose of using C++/WinRT modules because expensive projection headers are still parsed textually. Import-then-include of the same declarations is not supported because it can produce conflicting declarations. To make existing code that textually includes C++/WinRT projection headers compatible with already imported projections, C++/WinRT provides WINRT_IMPORT_MODULE; when this macro is defined, generated projection headers become mostly inert while still establishing the expected include guards. The XAML compiler is exactly the kind of external code generator that currently forces applications to depend on that compatibility behavior.

Root cause 1: the XAML MSBuild integration does not propagate C++/WinRT module mode

The first structural problem is visible at the CompileXaml task boundary. The XAML build targets invoke CompileXaml with inputs such as Language, RootNamespace, XamlPages, XamlApplications, CIncludeDirectories, ClIncludeFiles, ReferenceAssemblies, FeatureControlFlags, TargetPlatformMinVersion, and PrecompiledHeaderFile. The task contract does not currently expose a property that corresponds to CppWinRTBuildModule or another explicit C++/WinRT projection-consumption mode.

Relevant source: https://github.com/microsoft/microsoft-ui-xaml/blob/6112d936461edb6d81ce7db983c74cc60ea2bc28/src/XamlCompiler/Targets/Microsoft.UI.Xaml.Markup.Compiler.interop.targets#L12-L1156. MarkupCompilePass1 and MarkupCompilePass2 both pass the normal native/XAML build inputs into CompileXaml, but they do not pass $(CppWinRTBuildModule) or an equivalent module capability. This means the fact that the C++/WinRT targets are generating named modules is lost at the boundary where XAML C++ generation begins.

The task implementation confirms the same limitation. https://github.com/microsoft/microsoft-ui-xaml/blob/6112d936461edb6d81ce7db983c74cc60ea2bc28/src/XamlCompiler/BuildTasks/CompileXaml.cs#L4-L339 exposes many ICompileXamlInputs properties, including PrecompiledHeaderFile, but no C++/WinRT module-mode property. The serializable compiler-input model in https://github.com/microsoft/microsoft-ui-xaml/blob/6112d936461edb6d81ce7db983c74cc60ea2bc28/src/XamlCompiler/Exe/Microsoft.UI.Xaml.Markup.Compiler.MSBuildInterop/CompilerInputs.cs#L4-L199 likewise has no module-mode field, and CompilerInputs.FromMSBuildTaskInputs therefore has nothing to copy. Finally, the compiler-side project model in https://github.com/microsoft/microsoft-ui-xaml/blob/6112d936461edb6d81ce7db983c74cc60ea2bc28/src/XamlCompiler/BuildTasks/Microsoft/Xaml/XamlCompiler/XamlProjectInfo.cs#L4-L127 contains C++-specific information such as ClassToHeaderFileMap and PrecompiledHeaderFile, but again no state indicating whether projection dependencies should be consumed through named modules.

Conceptually the current pipeline is:

.vcxproj
  |
  | CppWinRTBuildModule=true
  v
C++/WinRT MSBuild targets
  |
  | know module mode
  | generate winrt.<Namespace>.ixx
  | build/resolve IFCs
  v

  [module-mode information is not propagated into XAML compilation]

WinUI XAML MSBuild targets
  |
  v
CompileXaml task
  |
  v
CompilerInputs / CompileXamlInternal
  |
  v
XamlProjectInfo
  |
  v
CppWinRT_* generators

The current implementation is therefore not equivalent to an incorrect branch such as if (CppWinRTBuildModule) GenerateHeaders();. There is no module-aware branch to take. A complete solution needs an explicit integration contract before any individual T4 template can reliably emit module-compatible C++.

The existing input model already demonstrates that code-generation modes can be carried through this boundary when needed. FeatureCtrlFlags contains mode/capability information such as UsingCSWinRT, and the MSBuild targets construct and pass XAML feature-control flags into the compiler. The absence of an equivalent C++/WinRT named-module capability is therefore not a fundamental limitation of the architecture; it is a missing integration contract that can be added in the same general data-flow path or through a dedicated input if a feature flag is not semantically appropriate.

Root cause 2: page dependency analysis lowers semantic WinRT dependencies directly to header filenames

The second structural issue appears in https://github.com/microsoft/microsoft-ui-xaml/blob/6112d936461edb6d81ce7db983c74cc60ea2bc28/src/XamlCompiler/BuildTasks/Microsoft/Xaml/XamlCompiler/CodeGenerators/PageDefinition.cs#L1-L363. PageDefinition maintains a collection named NeededCppWinRTProjectionHeaderFiles. During EnsureNeededXamlHeaderFilesCalculated, a discovered WinRT type is converted into a path using logic equivalent to:

csharp
neededCppWinRTProjectionHeaderFiles.Add($"winrt/{adjustedType.Namespace}.h");

The dependency analysis itself is valuable and already substantially complete, but it is important to describe what the dependency represents and why the generated filename takes its current form.

C++/WinRT projections are fundamentally organized around WinRT metadata namespaces. An IDL declaration such as:

idl
namespace OpenNet.UI.Xaml.Control.Graph
{
    runtimeclass LiveGraph : Microsoft.UI.Xaml.Controls.Control
    {
        LiveGraph();
    }
}

is represented in the generated Windows Metadata with a type whose namespace is:

OpenNet.UI.Xaml.Control.Graph

C++/WinRT reads that metadata namespace (TypeDef::TypeNamespace()), groups projected types by namespace, and generates one public projection header for that namespace. In the traditional projection model, the resulting files are named directly from the metadata namespace, for example:

winrt/OpenNet.UI.Xaml.Control.Graph.h
winrt/impl/OpenNet.UI.Xaml.Control.Graph.0.h
winrt/impl/OpenNet.UI.Xaml.Control.Graph.1.h
winrt/impl/OpenNet.UI.Xaml.Control.Graph.2.h

The public C++ projection is correspondingly exposed under:

cpp
winrt::OpenNet::UI::Xaml::Control::Graph

With C++/WinRT 3.x named modules enabled, the same metadata namespace is also the identity used for the namespace module:

cpp
import winrt.OpenNet.UI.Xaml.Control.Graph;

with a generated module interface file named approximately:

winrt/winrt.OpenNet.UI.Xaml.Control.Graph.ixx

Therefore the traditional projection header and the named module are not unrelated dependencies. They are two C++ consumption representations of the same underlying WinRT metadata namespace:

WinRT metadata namespace
    OpenNet.UI.Xaml.Control.Graph
            |
            +--> traditional projection
            |    winrt/OpenNet.UI.Xaml.Control.Graph.h
            |
            +--> named-module projection
                 winrt.OpenNet.UI.Xaml.Control.Graph

This distinction matters when looking at the XAML compiler implementation. PageDefinition does not begin with an arbitrary header filename. It first resolves XAML types and obtains their underlying WinRT metadata namespaces. Its dependency analysis examines generated named-element fields, event types, event declaring types, compiled-binding member and declaring types, and nested generic argument types. For a projected type, it then immediately lowers the namespace to the traditional C++/WinRT header convention with logic equivalent to:

csharp
neededCppWinRTProjectionHeaderFiles.Add(
    $"winrt/{adjustedType.Namespace}.h");

For example, if a XAML page uses a control whose underlying WinRT type is:

OpenNet.UI.Xaml.Control.Graph.LiveGraph

the XAML compiler has already resolved the namespace:

OpenNet.UI.Xaml.Control.Graph

and currently materializes that dependency as:

cpp
#include <winrt/OpenNet.UI.Xaml.Control.Graph.h>

That mapping is correct for the traditional C++/WinRT projection-header model: it mirrors the naming convention of the projection generated by C++/WinRT from the same WinRT metadata namespace.

The integration problem is therefore not that the XAML compiler fails to discover the relevant namespace, nor that its traditional header filename is incorrect. The problem is that the dependency is lowered to a header-specific representation too early.

With C++/WinRT 3.x, the same semantic projection dependency can instead be consumed as:

cpp
import winrt.OpenNet.UI.Xaml.Control.Graph;

Consequently the information that should survive dependency analysis is conceptually:

requires C++/WinRT projection namespace:
    OpenNet.UI.Xaml.Control.Graph

rather than only:

requires header:
    winrt/OpenNet.UI.Xaml.Control.Graph.h

The final representation can then be selected according to the C++/WinRT consumption mode:

WinRT metadata namespace dependency
            |
            v
OpenNet.UI.Xaml.Control.Graph
            |
       +----+----+
       |         |
       v         v
header mode     module mode
       |         |
       v         v
#include         import
<winrt/          winrt.
OpenNet.UI...h>  OpenNet.UI...;

In other words, dependency discovery is largely already present. What is missing is a module-agnostic representation of the discovered C++/WinRT projection dependency and a consumption-mode-aware lowering step.

cpp
#include <winrt/OpenNet.UI.Xaml.Control.Graph.h>

In named-module mode the same semantic dependency could instead be satisfied by:

cpp
import winrt.OpenNet.UI.Xaml.Control.Graph;

or by an automatically generated preamble that imports that projection before the generated declarations are parsed. The compiler already has the type/namespace information required to make this decision; it currently lacks an abstraction that keeps the dependency semantic until final C++ emission.

A module-compatible internal model would conceptually look more like:

CppWinRTProjectionDependency
  Namespace = OpenNet.UI.Xaml.Control.Graph

with a later lowering step that chooses the correct C++ representation. The exact class/property names do not matter. What matters is separating "requires projection namespace X" from "include file winrt/X.h".

__has_include does not provide module awareness

The current generators frequently wrap projection headers in #if __has_include(<winrt/Namespace.h>). This protects builds where a projection header is genuinely unavailable, but it cannot select between header consumption and named-module consumption. C++/WinRT continues to generate projection headers alongside module interfaces, so __has_include(<winrt/Namespace.h>) can remain true in a module-enabled project. The existence of the header therefore says nothing about whether the intended declaration source for the translation unit is the textual header or winrt.Namespace IFC. It also says nothing about CppWinRTModuleInclude, CppWinRTModuleExclude, or whether an IFC is supplied by another project. Module selection has to come from build/configuration state, not from probing for a projection header.

This also explains why the current __has_include pattern can silently appear harmless when WINRT_IMPORT_MODULE is force-defined: the conditional succeeds, the header is included, and C++/WinRT deliberately makes most of the header body inert. In that configuration the generated source is not truly module-aware; it is being made compatible by an external macro that changes the meaning of the legacy include. That distinction matters because the required modules still have to be imported somewhere before those now-inert headers are encountered.

TypeInfo dependency discovery has a narrower but still semantic source

TypeInfoDefinition.NeededCppWinRTProjectionHeaderFiles is not identical to the page dependency set. Its LookupNeededCppWinRTProjectionHeaderFiles walks generated TypeInfo entries and adds a projection dependency when generated metadata has a direct code reference, such as activation, collection handling, dictionary handling, or enum conversion. This is another reason to avoid a simplistic global string replacement: Page and TypeInfo discover dependencies from different semantic inputs, but both ultimately lower those dependencies to the same winrt/<Namespace>.h representation. A shared projection-dependency abstraction can preserve the different discovery algorithms while unifying only the final consumption strategy.

Root cause 3: the problem affects multiple C++/WinRT XAML generators, not only TypeInfo Pass2

Page Pass1

https://github.com/microsoft/microsoft-ui-xaml/blob/6112d936461edb6d81ce7db983c74cc60ea2bc28/src/XamlCompiler/BuildTasks/Microsoft/Xaml/XamlCompiler/CodeGenerators/CppWinRT/CppWinRT_PagePass1.tt#L1-L95 generates the per-page .xaml.g.h declarations. It begins with traditional includes such as:

cpp
#pragma once

#include <unknwn.h>

and then emits every entry in Model.NeededCppWinRTProjectionHeaderFiles as:

cpp
#if __has_include(<winrt/Some.Namespace.h>)
#include <winrt/Some.Namespace.h>
#endif

The generated template then declares PageT/UserControlT infrastructure and generated fields that directly use projected C++/WinRT types. Therefore the module-awareness problem exists before XamlTypeInfo.g.cpp is compiled. A fix that only changes TypeInfo Pass2 would leave the Pass1-generated page headers header-oriented.

App Pass1

https://github.com/microsoft/microsoft-ui-xaml/blob/6112d936461edb6d81ce7db983c74cc60ea2bc28/src/XamlCompiler/BuildTasks/Microsoft/Xaml/XamlCompiler/CodeGenerators/CppWinRT/CppWinRT_AppPass1.tt#L1-L78 is even more explicit. It directly includes projection headers rather than obtaining them from the page dependency collection:

cpp
#include <unknwn.h>

#include "winrt/Microsoft.UI.Xaml.h"
#include "winrt/Microsoft.UI.Xaml.Markup.h"
#include

Source: microsoft/microsoft-ui-xaml