Skip to content

Project Windows Runtime struct fields as C# fields - #2505

Open
Sergio0694 wants to merge 5 commits into
staging/3.0from
user/sergiopedri/project-struct-fields-as-fields
Open

Project Windows Runtime struct fields as C# fields#2505
Sergio0694 wants to merge 5 commits into
staging/3.0from
user/sergiopedri/project-struct-fields-as-fields

Conversation

@Sergio0694

Copy link
Copy Markdown
Member

Summary

Projects the fields of Windows Runtime struct types as C# fields instead of { readonly get; set; } properties, across the projection writer, the manually projected types in WinRT.Runtime, and the custom-mapped XAML types defined by addition files.

Motivation

Projecting struct fields as properties turned out to be problematic, and confusing.

The biggest issue is authoring. cswinrtwinmdgen.exe maps the public instance fields of an authored struct to Windows Runtime struct fields, which is the only shape that can round-trip: an author writing a struct against the projected shape (properties) would silently produce an empty struct in the generated .winmd. Projecting fields as fields makes consumption and authoring symmetrical: the shape you consume is exactly the shape you author.

Beyond authoring, Windows Runtime structs are plain data — there is no accessor to run any logic behind, so a property only obscures that, and it prevents callers from taking a reference to a member (e.g. to pass it as a ref argument, or to mutate an element of an array of structs in place). This also matches what C++/WinRT does, and what CsWinRT 2.x did for generated struct projections.

Changes

Projection writer

  • src/WinRT.Projection.Writer/Builders/ProjectionFileBuilder.cs: WriteStruct now emits a public field per Windows Runtime struct field, declared before the constructor so declaration order (which drives the sequential layout of the struct) mirrors the metadata order.
  • src/WinRT.Projection.Writer/Factories/AbiStructFactory.cs, src/WinRT.Projection.Writer/Factories/StructEnumMarshallerFactory.cs: refresh comments that justified the mapped-struct special cases by claiming their projected public fields don't match the WinMD field layout, which is no longer accurate.

Manually projected types

  • src/WinRT.Runtime2/Windows.Foundation/Point.cs, Rect.cs, Size.cs, src/WinRT.Runtime2/InteropServices/Events/EventRegistrationToken.cs: X, Y, Width, Height and Value become public fields. The negative-value validation that lived in the Rect/Size setters moves into the constructors that document it (which is also what CsWinRT 2.x did). Managed-only members (Left, Top, Right, Bottom, IsEmpty, Empty, ...) are unchanged.

Custom-mapped XAML types (addition files, both the Microsoft.UI.Xaml and Windows.UI.Xaml copies)

  • …/Microsoft.UI.Xaml.CornerRadius.cs: TopLeft, TopRight, BottomRight, BottomLeft
  • …/Microsoft.UI.Xaml.GridLength.cs: Value, GridUnitType
  • …/Microsoft.UI.Xaml.Duration.cs: TimeSpan, Type
  • …/Microsoft.UI.Xaml.Media.Animation.KeyTime.cs: TimeSpan
  • …/Microsoft.UI.Xaml.Media.Animation.RepeatBehavior.cs: Count, Duration, Type
  • …/Microsoft.UI.Xaml.Media.Media3D.Matrix3D.cs: M11-M34, OffsetX, OffsetY, OffsetZ, M44

These types are defined in full by their addition files, so they kept the property-over-private-backing-field shape inherited from the UWP/WPF managed projections. Leaving them alone would have made them inconsistent with their own siblings, since Thickness, Matrix, GeneratorPosition and Color are generated types in CsWinRT 3.0 and therefore now expose fields. Field declaration order (and therefore the sequential layout each of these blittable structs marshals with) is unchanged, and validation that lived in the removed setters is preserved where the constructors already performed it. GridLength and KeyTime are no longer readonly struct-s, since Windows Runtime structs are mutable data. Members that are not Windows Runtime struct fields are left untouched (GridLength.IsAbsolute/IsAuto/IsStar/Auto, Duration.HasTimeSpan/Automatic/Forever, RepeatBehavior.HasCount/HasDuration/Forever, Matrix3D.Identity/IsIdentity/HasInverse, and all operators, conversions and formatting helpers).

Tests and docs

  • src/Tests/ProjectionWriterTest/Test_ProjectedStructs.cs: new tests asserting that projected struct fields are emitted as C# fields, and that no projected struct member is emitted as an auto-property, in both projection modes.
  • docs/cswinrt3.0-spec.md: documents the projection rule, and renames the neighbouring Point/Rect/Size section (which was already about their fields rather than properties).
  • docs/event-infrastructure.md: refreshes the EventRegistrationToken snippet.

Validation

  • ProjectionWriterTest (16), WinMDGeneratorTest (17) and SourceGenerator2Test (129) all pass.
  • The Windows, Windows.UI.Xaml and WinAppSDK projections were regenerated end-to-end and compile cleanly, which covers the emitted structs, their marshallers, and all of the addition files.

Sergio0694 and others added 5 commits August 1, 2026 12:57
Projected structs were emitting each Windows Runtime struct field as a
'{ readonly get; set; }' auto-property. That turned out to be problematic,
most notably for authoring: the WinMD generator maps public instance fields
back to Windows Runtime struct fields, so an authored struct written against
the projected shape produced an empty struct in the generated metadata. It
also prevents callers from taking a reference to a member, and is simply
confusing, given Windows Runtime structs are plain data.

Emit plain public fields instead, declared before the constructor so that
declaration order (which drives the sequential layout of the struct) mirrors
the metadata order.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
'Point', 'Rect', 'Size' and 'EventRegistrationToken' are manually projected in
'WinRT.Runtime', and were mirroring the Windows Runtime struct fields as
'{ readonly get; set; }' properties. Expose them as plain public fields, to
match how projected structs are now generated.

The negative-value validation that lived in the 'Rect.Width'/'Rect.Height' and
'Size.Width'/'Size.Height' setters moves into the constructors that document it
(this is also what CsWinRT 2.x did). The remaining members ('Left', 'Top',
'Right', 'Bottom', 'IsEmpty', 'Empty', ...) are managed-only conveniences and
stay as properties.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
The custom-mapped XAML struct types are defined in full by their addition
files, and mirrored the Windows Runtime struct fields as properties over
private backing fields (a shape inherited from the UWP/WPF managed
projections). Now that generated structs project their fields as C# fields,
these would otherwise be inconsistent with their siblings: 'Thickness',
'Matrix', 'GeneratorPosition' and 'Color' are generated types (so their
members are fields), while 'CornerRadius' and friends are hand-written.

Expose the actual metadata fields as public fields for:
  - 'CornerRadius':   TopLeft, TopRight, BottomRight, BottomLeft
  - 'GridLength':     Value, GridUnitType
  - 'Duration':       TimeSpan, Type
  - 'KeyTime':        TimeSpan
  - 'RepeatBehavior': Count, Duration, Type
  - 'Matrix3D':       M11-M34, OffsetX, OffsetY, OffsetZ, M44

Field declaration order (and therefore the sequential layout each of these
blittable structs marshals with) is unchanged. Managed-only members are left
untouched: 'GridLength.IsAbsolute'/'IsAuto'/'IsStar'/'Auto',
'Duration.HasTimeSpan'/'Automatic'/'Forever', 'RepeatBehavior.HasCount'/
'HasDuration'/'Forever', 'Matrix3D.Identity'/'IsIdentity'/'HasInverse'/... and
all the operators, conversions and formatting helpers.

Validation that lived in the removed property setters is preserved where the
constructors already performed it ('CornerRadius.Validate', 'GridLength',
'KeyTime.FromTimeSpan'). 'GridLength' and 'KeyTime' are no longer 'readonly
struct'-s, since Windows Runtime structs are mutable data.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
Refreshes the 'EventRegistrationToken' snippet in the event infrastructure
doc, and documents the projection rule in the CsWinRT 3.0 spec (renaming the
neighbouring 'Point'/'Rect'/'Size' section, which was already about their
fields rather than properties).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
Both comments justified skipping ABI struct emission / per-field marshalling
for mapped structs by claiming their projected public fields don't match the
WinMD field layout. That is no longer accurate now that the addition files
expose the metadata fields directly. The actual reason is that these types are
defined in full by an addition file rather than generated from metadata, so
they're passed through by value and only get BoxToUnmanaged/UnboxToManaged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d136d263-1a79-45e8-8a92-fb6dd28b2d79
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@Sergio0694
Sergio0694 marked this pull request as ready for review August 1, 2026 22:14
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant