Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ All five .NET build tools (`cswinrtprojectionrefgen`, `cswinrtprojectiongen`, `c
| Projection Generator (host) | `CSWINRTPROJECTIONGENxxxx` | `0001`–`0011`, `9999` |
| Projection Writer (library) | `CSWINRTPROJECTIONGEN5xxx` | `5003`–`5021`, `9999` (shares the `CSWINRTPROJECTIONGEN` prefix with the host; the writer reserves the 5000+ range so the two never collide) |
| Impl Generator | `CSWINRTIMPLGENxxxx` | `0001`–`0014`, `9999` |
| Interop Generator | `CSWINRTINTEROPGENxxxx` | `0001`–`0097`, `9999` |
| Interop Generator | `CSWINRTINTEROPGENxxxx` | `0001`–`0100`, `9999` |
| WinMD Generator | `CSWINRTWINMDGENxxxx` | `0001`–`0010`, `9999` |
| Runtime (obsolete markers) | `CSWINRT3xxx` | `CSWINRT3001` |

Expand Down
16 changes: 14 additions & 2 deletions .github/skills/interop-generator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ WinRT.Interop.Generator/
├── Errors/ # Error/warning infrastructure
│ ├── UnhandledInteropException.cs # Wraps unexpected errors with phase context
│ ├── WellKnownInteropException.cs # Structured errors with CSWINRTINTEROPGEN### codes
│ ├── WellKnownInteropExceptions.cs # Factory for all error/warning codes (80+ codes)
│ └── WellKnownInteropWarning.cs # Non-fatal warnings (can be promoted to errors)
│ ├── WellKnownInteropExceptions.cs # Factory for all error/warning/message codes (100+ codes)
│ ├── WellKnownInteropWarning.cs # Non-fatal warnings (can be promoted to errors)
│ └── WellKnownInteropMessage.cs # Informational messages (never promoted to errors)
├── Extensions/ # Extension methods (23 files)
│ ├── WindowsRuntimeExtensions.cs # Core: IsProjectedWindowsRuntimeType, IsBlittable, etc.
│ ├── TypeSignatureExtensions.cs # GetAbiType, EnumerateAllInterfaces, etc.
Expand Down Expand Up @@ -230,6 +231,8 @@ The generator is invoked via a response file: `cswinrtinteropgen.exe @path/to/re
| `--winrt-component-assembly-path` | `string?` | `WinRT.Component.dll` path (for authored components) |
| `--generated-assembly-directory` | `string` | Output folder for `WinRT.Interop.dll` |
| `--use-windows-ui-xaml-projections` | `bool` | Use UWP XAML (`Windows.UI.Xaml`) instead of WinUI |
| `--marshalling-mode` | `CsWinRTMarshallingMode` | Which assemblies to analyze for discovery: `All`, `Minimal` (default; skip BCL), or `Strict` (only WinRT-referencing) |
| `--marshalling-enabled-assembly-names` | `string[]` | Assembly names (`.dll`/directory ignored) always analyzed regardless of mode (from the `CsWinRTMarshallingEnabledAssembly` item) |
| `--validate-winrt-runtime-assembly-version` | `bool` | Check version compatibility |
| `--validate-winrt-runtime-dll-version-2-references` | `bool` | Reject CsWinRT 2.x references |
| `--enable-incremental-generation` | `bool` | Enable incremental generation (caching) |
Expand Down Expand Up @@ -282,6 +285,15 @@ The generator processes two categories of assemblies:
- `WinRT.Projection.dll` — 3rd-party component projections (optional)
- `WinRT.Component.dll` — Authored component projections (optional)

**Marshalling mode** (`--marshalling-mode`, `ShouldProcessModule` in `InteropGenerator.Discover.cs`): controls which modules are analyzed for discovery. Modules referencing the Windows Runtime assembly (i.e. those built targeting a Windows TFM) are **always** analyzed; the mode only affects modules that don't reference any CsWinRT assembly:
- `All` — analyze every module, even plain `.NET` assemblies (including the BCL).
- `Minimal` (default) — analyze every module except those from the BCL (detected via well-known .NET/Microsoft public key tokens, see `BaseClassLibraryIdentity` in `WinRT.Generator.Core` and the `ModuleDefinition.IsBaseClassLibraryModule` extension), to reduce binary size. This still lets projects that don't target a Windows TFM (e.g. a class library with just MVVM viewmodels) contribute the marshalling code their types need.
- `Strict` — only analyze modules referencing the Windows Runtime assembly (the historical behavior). Corresponds to `!ReferencesWindowsRuntimeAssembly && !IsWindowsRuntimeModule → skip`.

**Legacy-runtime skip** (`ModuleDefinition.TargetsLegacyRuntime` in `InteropGenerator.Discover.cs`): regardless of mode, modules targeting a legacy/portable runtime (i.e. a `netstandard` or `mscorlib` corlib scope) are **never** analyzed. The entire generator infrastructure identifies well-known types (including custom-mapped types such as `IEnumerable<T>`) by comparing against type references scoped to the modern .NET corlib (e.g. `System.Runtime`), which the emit phase also uses. Legacy-runtime assemblies declare those same types against a different corlib (`netstandard`/`mscorlib`), which `AsmResolver`'s `SignatureComparer` treats as a distinct scope — so the generator can't match them, and generating marshalling code for them would fail with `CSWINRTINTEROPGEN0055`/`0020`. Such assemblies could in principle still use custom-mapped types that need marshalling, but for simplicity they're skipped entirely.

**Opt-in assemblies** (`--marshalling-enabled-assembly-names`, from the `CsWinRTMarshallingEnabledAssembly` MSBuild item): assemblies listed here are **always** analyzed (matched by simple name, ignoring `.dll`/directory), regardless of the mode — checked first in `ShouldProcessModule`. This lets users fine-tune size (e.g. `strict` mode + a few opted-in assemblies). It does **not** override the legacy-runtime skip (legacy assemblies still can't be marshalled). `ValidateMarshallingEnabledAssemblies` emits three diagnostics: `CSWINRTINTEROPGEN0098` (warning: entry doesn't match any referenced assembly), `CSWINRTINTEROPGEN0099` (message: entry already targets Windows, redundant), and `CSWINRTINTEROPGEN0100` (message: mode is `all`, all entries redundant).

**Type exclusions** (`Helpers/TypeExclusions.cs`):
- `System.Threading.Tasks.Task<T>` — Cannot be marshalled across Windows Runtime boundary
- `System.Collections.Concurrent.ConditionalWeakTable<,>` — Memory semantics conflict
Expand Down
37 changes: 37 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,47 @@ Below are the most commonly used MSBuild properties. For a full list, refer to t
| `CsWinRTGenerateProjection` | `true` | Generate C# projection sources from `.winmd` metadata. |
| `CsWinRTGenerateReferenceProjection` | `false` | Generate reference-only projections (for NuGet distribution). |
| `CsWinRTComponent` | `false` | Enable Windows Runtime component authoring mode. |
| `CsWinRTMarshallingMode` | `minimal` | Controls which assemblies the interop generator analyzes for marshalling code (`all`, `minimal`, or `strict`). See below. |
| `CsWinRTIncludes` | *(empty)* | Semicolon-separated namespaces to include in the projection. |
| `CsWinRTExcludes` | `Windows;Microsoft` | Semicolon-separated namespaces to exclude from the projection. |
| `CsWinRTMessageImportance` | `normal` | Build message verbosity (`normal` or `high`). |

### Marshalling mode

By default (`minimal`), the interop generator analyzes **every** assembly in the app to discover user-defined
(CCW) types and generic instantiations that need marshalling code — even assemblies that don't target a
Windows TFM and don't reference any CsWinRT assembly — with the exception of the .NET base class library
(BCL). This means a plain `.NET` class library (e.g. one holding MVVM viewmodels) no longer needs to target
a `-windows` TFM just so its types can be marshalled to native code, while the BCL is skipped to keep the
generated interop assembly small.

`CsWinRTMarshallingMode` controls this behavior:

| Value | Behavior |
|-------|----------|
| `all` | Analyze all assemblies, including those from the .NET base class library (BCL). |
| `minimal` (default) | Same as `all`, but skip assemblies from the .NET base class library (BCL) to reduce binary size. |
| `strict` | Only analyze assemblies referencing the Windows Runtime assembly (i.e. those targeting a Windows TFM). |

Assemblies that reference the Windows Runtime assembly are always analyzed, regardless of the mode. Assemblies targeting a legacy or portable runtime (i.e. .NET Standard or .NET Framework) are never analyzed, since the interop generator can only marshal types declared against a modern .NET runtime.

### Opting in specific assemblies

The `CsWinRTMarshallingEnabledAssembly` item lets you force the interop generator to analyze specific assemblies, regardless of the marshalling mode. This is useful for fine-tuning binary size: for example, you can keep the `strict` mode (the smallest option) while opting in just the few assemblies you know need marshalling support:

```xml
<PropertyGroup>
<CsWinRTMarshallingMode>strict</CsWinRTMarshallingMode>
</PropertyGroup>

<ItemGroup>
<CsWinRTMarshallingEnabledAssembly Include="MyApp.ViewModels" />
<CsWinRTMarshallingEnabledAssembly Include="MyApp.Models" />
</ItemGroup>
```

Each item is an assembly name (the `.dll` extension is optional). The interop generator reports a warning if an entry doesn't match any referenced assembly, and an informational message if an entry is redundant (e.g. it already targets Windows, or the mode is `all` and thus already analyzes everything).

## Author and consume a C#/WinRT component

This is coming soon for CsWinRT 3.0.
Expand Down
17 changes: 17 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<CsWinRTGeneratorStandardErrorImportance Condition="'$(CsWinRTGeneratorStandardErrorImportance)' == ''">High</CsWinRTGeneratorStandardErrorImportance>
<CsWinRTGeneratorLogStandardErrorAsError Condition="'$(CsWinRTGeneratorLogStandardErrorAsError)' == ''">true</CsWinRTGeneratorLogStandardErrorAsError>

<!--
The marshalling mode controls which assemblies 'cswinrtinteropgen' analyzes to discover
user-defined/CCW/generic types that need marshalling code:
- 'minimal' (default): analyze all assemblies (even those not referencing any CsWinRT
assembly), except those from the .NET base class library (BCL). This lets projects that
don't target a Windows TFM (e.g. a class library with just MVVM viewmodels) contribute
the marshalling code their types need, while skipping the BCL to reduce binary size.
- 'all': same as 'minimal', but also analyze assemblies from the .NET base class library (BCL).
- 'strict': only analyze assemblies referencing the Windows Runtime assembly (i.e. those
targeting a Windows TFM).
-->
<CsWinRTMarshallingMode Condition="'$(CsWinRTMarshallingMode)' == ''">minimal</CsWinRTMarshallingMode>

<!-- Emits a SetEntryAssembly module initializer into WinRT.Component.dll to enable JIT TypeMap discovery.
Not needed for AOT (handled via a separate exe-project workaround). Will become unnecessary once
the TypeMappingEntryAssembly MSBuild property is available. -->
Expand Down Expand Up @@ -133,6 +146,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorInteropAssemblyDirectory)" />
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorDebugReproDirectory)" />
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTUseWindowsUIXamlProjections)" />
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTMarshallingMode)" />
<_RunCsWinRTGeneratorInputsCacheToHash Include="@(CsWinRTMarshallingEnabledAssembly)" />
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorValidateWinRTRuntimeAssemblyVersion)" />
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorValidateWinRTRuntimeDllVersion2References)" />
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorEnableIncrementalGeneration)" />
Expand Down Expand Up @@ -185,6 +200,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.
CsWinRTToolsDirectory="$(CsWinRTInteropGenEffectiveToolsDirectory)"
CsWinRTToolsArchitecture="$(CsWinRTToolsArchitecture)"
UseWindowsUIXamlProjections="$(CsWinRTUseWindowsUIXamlProjections)"
MarshallingMode="$(CsWinRTMarshallingMode)"
MarshallingEnabledAssemblies="@(CsWinRTMarshallingEnabledAssembly)"
ValidateWinRTRuntimeAssemblyVersion="$(CsWinRTGeneratorValidateWinRTRuntimeAssemblyVersion)"
ValidateWinRTRuntimeDllVersion2References="$(CsWinRTGeneratorValidateWinRTRuntimeDllVersion2References)"
EnableIncrementalGeneration="$(CsWinRTGeneratorEnableIncrementalGeneration)"
Expand Down
2 changes: 2 additions & 0 deletions nuget/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ C#/WinRT behavior can be customized with these project properties:
| CsWinRTGenerateReferenceProjection | true \| *false | Generate reference-only projections (for NuGet distribution) |
| CsWinRTGenerateInteropAssembly | auto | Generate interop assemblies at build time (defaults to `true` for Exe/WinExe, or Library with `PublishAot=true`) |
| CsWinRTComponent | true \| *false | Enable Windows Runtime component authoring mode |
| CsWinRTMarshallingMode | all \| *minimal \| strict | Controls which assemblies the interop generator analyzes for marshalling code. `all` analyzes every assembly (including the .NET base class library), `minimal` analyzes every assembly except the .NET base class library (BCL), and `strict` only analyzes assemblies referencing the Windows Runtime assembly |
| CsWinRTMarshallingEnabledAssembly | *(item)* | An item listing specific assemblies (by name, `.dll` optional) to always analyze for marshalling code, regardless of `CsWinRTMarshallingMode`. Useful for fine-tuning binary size (e.g. using `strict` and opting in a few assemblies) |
| CsWinRTUseWindowsUIXamlProjections | true \| *false | Use UWP XAML (`Windows.UI.Xaml`) instead of WinUI (`Microsoft.UI.Xaml`) |
| CsWinRTMergeReferencedActivationFactories | true \| *false | Makes `DllGetActivationFactory` forward activation calls to all referenced WinRT components, allowing them to be merged into a single executable |
| CsWinRTIncludes | "" | Semicolon-separated namespaces to include in projection output |
Expand Down
48 changes: 48 additions & 0 deletions src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace WindowsRuntime.Generator.Errors;

/// <summary>
/// A well-known informational message shared by all generators.
/// </summary>
/// <remarks>
/// Unlike a warning, a message is purely informational: it is never promoted to an error, and it is surfaced
/// by MSBuild as a build message rather than a warning (the emitted line intentionally omits the
/// <c>warning</c>/<c>error</c> category keyword that MSBuild's output parser looks for). It is distinct from
/// <see cref="WellKnownGeneratorMessages"/>, which holds the message templates for the shared logical errors.
/// </remarks>
internal sealed class WellKnownGeneratorMessage
{
/// <summary>
/// The id of the message.
/// </summary>
private readonly string _id;

/// <summary>
/// The message text.
/// </summary>
private readonly string _message;

/// <summary>
/// Creates a new <see cref="WellKnownGeneratorMessage"/> instance with the specified parameters.
/// </summary>
/// <param name="id">The id of the message.</param>
/// <param name="message">The message text.</param>
public WellKnownGeneratorMessage(string id, string message)
{
_id = id;
_message = message;
}

/// <summary>
/// Logs the message through the provided logger.
/// </summary>
/// <param name="log">The logger to write the message to (typically <c>ConsoleApp.Log</c> from ConsoleAppFramework).</param>
public void Log(Action<string> log)
{
log($"{_id}: {_message}");
}
}
7 changes: 7 additions & 0 deletions src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ private static string FormatValue(object value)
return string.Join(',', array);
}

// Enum values are serialized by their member name (e.g. 'All'), matching the parser's
// case-insensitive, name-based enum handling so the round-trip stays deterministic.
if (value is Enum enumValue)
{
return enumValue.ToString();
}

// All other primitive values use invariant culture, matching the parser's
// invariant 'Convert.ChangeType' so the round-trip is deterministic.
return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
Expand Down
20 changes: 20 additions & 0 deletions src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,26 @@ private static bool TryConvert(string rawValue, Type targetType, out object? con
return true;
}

// Enum-typed arguments are parsed by name (case-insensitively), so that MSBuild values such
// as 'all' round-trip to the matching enum member (e.g. 'All'). Numeric values are rejected,
// as they carry no meaning in a response file and would silently accept out-of-range inputs.
if (targetType.IsEnum)
{
if (rawValue.Length > 0 &&
!char.IsAsciiDigit(rawValue[0]) &&
rawValue[0] != '-' &&
Enum.TryParse(targetType, rawValue, ignoreCase: true, out object? parsedEnum))
{
converted = parsedEnum;

return true;
}

converted = null;

return false;
}

// For primitives ('bool', 'int', etc.) we rely on the standard 'Convert.ChangeType', which
// is AOT-safe for the built-in primitives and uses invariant culture for deterministic parsing.
try
Expand Down
Loading