Add CsWinRTMarshallingMode option for non-Windows TFM marshalling#2482
Open
Sergio0694 wants to merge 14 commits into
Open
Add CsWinRTMarshallingMode option for non-Windows TFM marshalling#2482Sergio0694 wants to merge 14 commits into
Sergio0694 wants to merge 14 commits into
Conversation
Add case-insensitive, name-based enum parsing to ResponseFileParser and matching name-based serialization to ResponseFileBuilder, so generator args can expose enum-typed options that round-trip through response files (and debug repros) deterministically. Numeric values are rejected to avoid silently accepting out-of-range enum inputs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add BaseClassLibraryIdentity, which identifies .NET base class library (BCL) and shared-framework assemblies by their well-known Microsoft public key tokens. This is used to let the interop generator skip framework assemblies when discovering user-defined/CCW/generic types. The token set was validated against the Microsoft.NETCore.App, Microsoft.WindowsDesktop.App, and Microsoft.AspNetCore.App shared frameworks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Introduce the CsWinRTMarshallingMode option ('all', 'minimal', 'strict'),
controlling which assemblies are analyzed when discovering user-defined/CCW
types and generic instantiations:
- 'all' (default): analyze every assembly, including those that don't
reference any CsWinRT assembly. 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.
- 'minimal': analyze every assembly except those from the BCL, to reduce
binary size.
- 'strict': only analyze assemblies referencing the Windows Runtime
assembly (the historical behavior).
Assemblies that reference the Windows Runtime assembly are always analyzed,
regardless of the mode. Adds the CsWinRTMarshallingMode enum, the
--marshalling-mode argument (defaulting to 'all'), an IsBaseClassLibraryModule
module helper, and factors the discovery filter into ShouldProcessModule.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a MarshallingMode parameter to the RunCsWinRTInteropGenerator task (with validation against the well-known values) that emits '--marshalling-mode' to the response file. Add the user-facing CsWinRTMarshallingMode MSBuild property (defaulting to 'all'), pass it to the task, and include it in the generator's property-inputs cache hash so changing the mode correctly re-runs generation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Document the new CsWinRTMarshallingMode option in docs/usage.md and nuget/readme.md, and update the interop generator skill with the new --marshalling-mode argument and the mode-based input filtering behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Change the default CsWinRTMarshallingMode from 'all' to 'minimal', so the interop generator analyzes every assembly (even those not targeting a Windows TFM) except the .NET base class library (BCL) by default. This keeps the usability win for plain .NET projects while avoiding the binary-size and analysis cost of crawling the BCL. The enum members are kept ordered from least to most restrictive (All, Minimal, Strict); the default is expressed via [DefaultValue] and the MSBuild/task defaults. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
4fe50b6 to
77e5823
Compare
Move the 'DefaultMarshallingMode' constant and 'ValidMarshallingModes' static field to the top of the class (constants and static fields before instance members), and quote the mode string literals in the XML docs (e.g. <c>"all"</c>) to make clear they are string values. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
With the marshalling mode defaulting to 'minimal', the interop generator now
analyzes assemblies that don't reference the Windows Runtime, including
third-party libraries that target .NET Standard or .NET Framework (e.g. Roslyn
and BenchmarkDotNet, referenced by the SourceGenerator2Test and Benchmarks
projects). Such assemblies are scoped to a legacy corlib ('netstandard' or
'mscorlib'), while the emit phase builds its references from the application's
modern .NET runtime corlib ('System.Runtime'). Because AsmResolver's
'SignatureComparer' treats these corlib scopes as distinct, generic collection
instantiations from those assemblies (e.g. 'IEnumerator<char>',
'IEnumerator<object>') failed the emit-time well-known interface IID lookup,
producing CSWINRTINTEROPGEN0055/0020 errors.
Legacy/portable-runtime assemblies cannot reference the Windows Runtime 3.0
projections in the first place, so they never contain marshalling-relevant
types. Skip them during discovery via the new 'ModuleDefinition.TargetsLegacyRuntime'
check. This restores the pre-marshalling-mode behavior for such assemblies
(previously only Windows-Runtime-referencing assemblies, which always target a
modern .NET runtime, were ever analyzed).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add the 'CsWinRTMarshallingEnabledAssembly' MSBuild item, which lets users
force the interop generator to analyze specific assemblies for discovery,
regardless of the marshalling mode. This enables fine-tuning for size: for
example, using the 'strict' mode while opting in a few specific assemblies
known to be needed, without switching to 'minimal' (which increases binary
size more).
The item flows through the RunCsWinRTInteropGenerator task as the
'--marshalling-enabled-assembly-names' argument, and is matched (by assembly
name, ignoring the '.dll' extension and any directory) in 'ShouldProcessModule'
so opted-in assemblies are always analyzed.
Add three diagnostics for the opt-in entries:
- CSWINRTINTEROPGEN0098 (warning): an entry doesn't match any referenced
assembly (likely a typo or stale reference).
- CSWINRTINTEROPGEN0099 (message): an entry already targets Windows and is
therefore always analyzed, so it's redundant and can be removed.
- CSWINRTINTEROPGEN0100 (message): the marshalling mode is 'all' (which
already analyzes everything), so all entries are redundant.
Add a WellKnownInteropMessage type (surfaced by MSBuild as a build message
rather than a warning) to support the two informational diagnostics.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Document the new CsWinRTMarshallingEnabledAssembly opt-in item in docs/usage.md and nuget/readme.md, and update the interop generator skill with the new --marshalling-enabled-assembly-names argument, the three opt-in diagnostics (CSWINRTINTEROPGEN0098-0100), and the legacy-runtime skip behavior. Also bump the interop generator error ID range in the copilot instructions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move the inline 'MarshallingEnabledAssemblies' comma-joining logic in RunCsWinRTInteropGenerator into a new AppendResponseFileOptionalCommand overload (taking an ITaskItem[]), keeping GenerateResponseFileCommands easy to read and consistent with the other AppendResponseFile* helpers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move WellKnownInteropMessage from the interop generator to the shared WinRT.Generator.Core project (renamed WellKnownGeneratorMessage) so all generators can use it. Its Log method now writes through a provided logger delegate (ConsoleApp.Log from ConsoleAppFramework) instead of calling Console.WriteLine directly, so any future change to that logger is reflected here too. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The previous rationale for skipping legacy/portable-runtime assemblies was
inaccurate: such assemblies can still use custom-mapped types (e.g.
'IEnumerable<T>') that would need marshalling. The actual reason the skip is
relevant is that the entire interop generator infrastructure identifies
well-known types 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 types against a different corlib ('netstandard' or
'mscorlib'), which AsmResolver's SignatureComparer treats as a distinct scope,
so the generator can't match them. Update the TargetsLegacyRuntime doc comment,
the discovery comment, and the usage/skill docs accordingly.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The 'TargetsLegacyRuntime' check is part of the 'should this module be analyzed' decision, so move it (and its explanatory comment) from LoadAndProcessModule into ShouldProcessModule, keeping LoadAndProcessModule easier to read. The check runs first (before the opt-in check), preserving the existing behavior that legacy-runtime assemblies are skipped even when explicitly opted in. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a new
CsWinRTMarshallingModeMSBuild option (defaultminimal) that lets the interop generator (cswinrtinteropgen) analyze assemblies that don't target a-windowsTFM (i.e. don't reference any CsWinRT assembly) when discovering user-defined/CCW types and generic instantiations. Also add aCsWinRTMarshallingEnabledAssemblyitem for opting in specific assemblies regardless of the mode.Motivation
Today, the interop generator only harvests marshalling info from
.dlls that reference the Windows Runtime assembly (i.e. those built targeting a-windowsTFM). This forces developers to put any type that needs to be marshalled to native (e.g. MVVM viewmodels implementing WinRT interfaces) into a-windowsproject, even when that project conceptually shouldn't target Windows at all and doesn't use any Windows API. Ideally, a project with just viewmodels should be able to target plain .NET.This change removes that restriction: by default, the generator now analyzes all (non-BCL) assemblies for user-defined/CCW/generic types, regardless of their TFM, so marshalling those types just works without forcing a
-windowsTFM.Behavior and modes
CsWinRTMarshallingModesupports three modes:all: analyze all assemblies, including those from the .NET base class library (BCL).minimal(default): same asall, 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-windowsTFM). This is the previous behavior.Assemblies that reference the Windows Runtime assembly are always analyzed, regardless of the mode. Assemblies targeting a legacy/portable runtime (.NET Standard or .NET Framework) are never analyzed (see below). BCL/framework assemblies are identified by their well-known Microsoft public key tokens.
Opting in specific assemblies
The
CsWinRTMarshallingEnabledAssemblyitem forces the generator to analyze specific assemblies regardless of the mode. This enables fine-tuning binary size — e.g. keepingstrictmode while opting in a few assemblies known to be needed, without switching tominimal(which increases binary size more):Three diagnostics validate the opt-in entries:
CSWINRTINTEROPGEN0098(warning): an entry doesn't match any referenced assembly.CSWINRTINTEROPGEN0099(message): an entry already targets Windows, so it's redundant.CSWINRTINTEROPGEN0100(message): the mode isall, so all entries are redundant.Skipping legacy-runtime assemblies (fixes a regression from the new default)
Defaulting to
minimalmeans the generator now analyzes third-party libraries that target .NET Standard / .NET Framework (e.g. Roslyn and BenchmarkDotNet, referenced bySourceGenerator2TestandBenchmarks). The entire interop generator infrastructure identifies well-known types — including custom-mapped types such asIEnumerable<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), whichAsmResolver'sSignatureComparertreats as a distinct scope — so the generator can't match them, and generating marshalling code for them failed withCSWINRTINTEROPGEN0055/0020. Such assemblies could in principle still use custom-mapped types that need marshalling, but for simplicity they're now skipped during discovery via a newModuleDefinition.TargetsLegacyRuntimecheck.Changes
src/WinRT.Generator.Core/: add case-insensitive, name-based parsing/serialization of enum-typed response file arguments;BaseClassLibraryIdentity(identify BCL assemblies by public key token); and the sharedWellKnownGeneratorMessagetype (informational build messages, logged through the ConsoleAppFramework logger).src/WinRT.Interop.Generator/: add theCsWinRTMarshallingModeenum +--marshalling-modeargument (defaultMinimal), the--marshalling-enabled-assembly-namesopt-in argument and its diagnostics, theIsBaseClassLibraryModule/TargetsLegacyRuntimemodule helpers, and theShouldProcessModule/ValidateMarshallingEnabledAssembliesdiscovery logic.src/WinRT.Generator.Tasks/RunCsWinRTInteropGenerator.cs: add theMarshallingModeandMarshallingEnabledAssembliestask parameters, with validation and response-file emission.nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets: add theCsWinRTMarshallingModeproperty (defaultminimal) and theCsWinRTMarshallingEnabledAssemblyitem, plumbed to the task and the generator's property-inputs cache hash.docs/usage.md,nuget/readme.md,.github/skills/interop-generator/SKILL.md,.github/copilot-instructions.md: document the new options, diagnostics, and behavior.Testing
All affected tooling projects build clean in Release (warnings-as-errors, 0 warnings). The full argument pipeline (item → task → response file → parser → discovery) was validated end-to-end against the built
cswinrtinteropgen. The corlib-scope root cause and theTargetsLegacyRuntimefix were validated against real net10.0 (System.Runtime) and .NET Standard 2.0 (netstandard) assemblies.