Skip to content

Port cswinrt.exe (projection generator) from C++ to C##2415

Merged
Sergio0694 merged 320 commits into
staging/CodeWritersfrom
user/sergiopedri/csharp-cswinrt-exe
May 9, 2026
Merged

Port cswinrt.exe (projection generator) from C++ to C##2415
Sergio0694 merged 320 commits into
staging/CodeWritersfrom
user/sergiopedri/csharp-cswinrt-exe

Conversation

@Sergio0694
Copy link
Copy Markdown
Member

Summary

Port the legacy C++ cswinrt.exe projection generator (src/cswinrt/) to a new C# library WinRT.Projection.Generator.Writer, exposed via a clean public API. Wire it into the build pipeline as a new MSBuild task (RunCsWinRTProjectionRefGenerator) backed by a Native AOT-published CLI tool (cswinrtprojectionrefgen.exe), replacing the <Exec cswinrt.exe> invocation in CsWinRTGenerateProjection.

This first PR focuses on a 1:1 functional port with parity to the original C++ tool. Refactoring, cleanup, and style alignment will follow in separate PRs.

Motivation

The legacy C++ projection generator predates CsWinRT 3.0 and is harder to evolve, debug, and integrate with the rest of the (now C#) tooling stack. The C# port unlocks several benefits:

  • Cleaner public API modeled after Roslyn — the generator can now be invoked programmatically from any .NET tool, not only spawned as a child process. The new MSBuild task calls it in-process.
  • Single tooling stack: the projection generator now lives alongside the impl/interop/winmd generators, sharing AsmResolver-based metadata loading and IndentedTextWriter-based emission.
  • Better formatted output: the new emitter produces consistently indented sources (so e.g. test-component compile errors are easier to read).
  • Easier maintenance and testing: reuses the in-repo IndentedTextWriter from the source generator and is unit-testable via the WinRT.Projection.Generator.Writer.TestRunner console driver.

The new tool is invoked through the new RunCsWinRTProjectionRefGenerator MSBuild task in the existing CsWinRTGenerateProjection target. All historical CsWinRT* MSBuild properties (filters, includes/excludes, CsWinRTComponent, CsWinRTEmbedded, CsWinRTGenerateReferenceProjection, CsWinRTPublicExclusiveToInterfaces, etc.) are preserved end-to-end.

Validated by extensively comparing generated sources against ground-truth output produced by the C++ tool from staging/3.0 (across AuthoringTest, Windows SDK, WinUI/Microsoft.UI projections), reaching 0 functional differences (only stylistic diffs remain — addressed in follow-up PRs).

Changes

  • src/WinRT.Projection.Generator.Writer/: new C# library that emits projection sources. Public API is ProjectionWriter.Run(ProjectionWriterOptions). Internally:
    • Generation/ProjectionGenerator.cs: top-level orchestration mirroring cswinrt::run in main.cpp.
    • Writers/CodeWriters.*.cs: code-emission helpers split by concern (Abi, Interface, Class, Methods, ObjRefs, Guids, TypeNames, Constructors, etc.).
    • Helpers/MappedTypes.cs, WindowsMetadataExpander.cs, TypeFilter.cs, etc.: ports of helpers.h/cmd_reader.h logic.
    • Metadata/MetadataCache.cs, TypeSemantics.cs, TypeCategorization.cs: AsmResolver-based metadata layer (replaces winmd::reader).
    • Resources/Additions/ and Resources/Base/: embedded namespace additions (Microsoft.UI.Xaml.CornerRadius, Windows.UI.Color, etc.) — port of src/cswinrt/strings/additions/.
  • src/WinRT.Projection.Ref.Generator/: new Native AOT CLI host (cswinrtprojectionrefgen.exe) that parses the response file, calls the writer library, and reports errors. Mirrors the existing pattern of the impl/interop/projection generators.
  • src/WinRT.Projection.Generator.Writer.TestRunner/: console test driver used during development to compare generator output against ground-truth.
  • src/WinRT.Generator.Tasks/RunCsWinRTProjectionRefGenerator.cs: new MSBuild task wrapper around the CLI.
  • nuget/Microsoft.Windows.CsWinRT.targets: replace the <Exec "$(CsWinRTExe)" @rsp> invocation in CsWinRTGenerateProjection with the new RunCsWinRTProjectionRefGenerator task. Reconstructs the include/exclude/input lists from existing CsWinRTFilters/CsWinRTPrivateFilters so user-facing MSBuild properties are unchanged.
  • nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets, nuget/Microsoft.Windows.CsWinRT.nuspec: register the new cswinrtprojectionrefgen.exe tool path.
  • src/build.cmd: publish cswinrtprojectionrefgen.exe alongside the other tools.
  • src/cswinrt.slnx: add the new projects.
  • src/WinRT.Projection.Generator/ (existing): minor updates so the existing projection generator still references the writer library where shared (MetadataCache, WindowsMetadataExpander).
  • src/Directory.Build.props, src/Projections/Directory.Build.targets: small adjustments for the new tool/task plumbing.

Out of scope (follow-up PRs)

  • Refactoring and code cleanup in WinRT.Projection.Generator.Writer (the writers split is intentionally a 1:1 port for now; we'll split, rename, and tidy in a follow-up).
  • Style alignment of the generated output (trailing newlines, intermediate var __arg_X = ...; locals, qualified vs unqualified type refs) — already deemed semantically equivalent.
  • Removing the old src/cswinrt/ C++ tool once we're confident the C# port has shipped a stable release.
  • Defense-in-depth fallbacks for cross-module silent fallthrough cases in WriteGuidSignature/WriteIidGuidPropertyForClassInterfaces/WriteAbiType — currently masked by WindowsMetadataExpander populating the cache correctly in production builds, but worth firming up.

Sergio0694 and others added 30 commits May 6, 2026 14:56
Mirrors C++ write_abi_method_call_marshalers Out branch. For each Out parameter:
- Function pointer signature: void** (string/object/runtime class), <BlittableStruct>*, or <Primitive>*
- Declare local with appropriate ABI type (default-initialized)
- Pass &__<name> to the vtable call
- After call: write back to caller's 'out' var via the appropriate ConvertToManaged
  (HStringMarshaller, WindowsRuntimeObjectMarshaller, <Marshaller>, or direct cast)
- Free string/object/runtime-class out values in finally

throw null!: 680 -> 621 (-59)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Span<RectInt32>)

throw null!: 621 -> 589 (-32)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…objref fields

Mirrors C++ truth: CopyTo dispatches via IDictionaryMethods_<K>_<V>_CopyTo(null, <objRef>, <enumObjRef>, array, arrayIndex). The CopyTo accessor takes an extra WindowsRuntimeObjectReference parameter for the enumerable.

throw null!: 589 -> 570 (-19)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…legates

For non-blittable runtime class / delegate ReferenceImpl, emit:

    <Projected> unboxedValue = (<Projected>)ComInterfaceDispatch.GetInstance<object>((ComInterfaceDispatch*)thisPtr);
    void* value = <Name>Marshaller.ConvertToUnmanaged(unboxedValue).DetachThisPtrUnsafe();
    *(void**)result = value;

Mirrors C++ write_reference_impl runtime-class/delegate path.

throw null!: 570 -> 509 (-61)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Function pointer signature gets the ABI scalar/blittable-struct type directly (passed by value to the vtable).

throw null!: 509 -> 489 (-20)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors C++ truth: 'in Guid target' generates 'fixed(Guid* _target = &target) { ... pass _target ... }' and the function pointer signature uses 'Guid*' for the parameter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
For static event members on classes:
- Public static event with add/remove that delegates to <ABIClass>.<Name>(_objRef, _objRef).Subscribe(value) / .Unsubscribe(value)
- Static ABI Methods class now emits per-event ConditionalWeakTable<object, EventSource> static field plus a static method that returns an EventSource for the (thisObject, thisReference) pair, lazily creating the source via [UnsafeAccessor(Constructor)] for generic events or new <DelegateName>EventSource(...) for non-generic delegates.
- Vtable slot for events advances by 2 per event (add + remove).

throw null!: 489 -> 414 (-75)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
For static ABI Methods that return Exception/HResult:
- Function pointer return: 'global::ABI.System.Exception*'
- Local: 'global::ABI.System.Exception __retval = default;'
- Return: 'return global::ABI.System.ExceptionMarshaller.ConvertToManaged(__retval);'

throw null!: 414 -> 327 (-87)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
throw null!: 327 -> 322 (-5)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each delegate now emits:
- <DelegateName>Vftbl struct (QI/AddRef/Release/Invoke)
- <DelegateName>NativeDelegate static class with <DelegateName>Invoke extension method
- <DelegateName>InterfaceEntriesImpl file static class
- <DelegateName>Impl internal static class (CCW vtable, with Invoke UnmanagedCallersOnly)
- <DelegateName>ReferenceImpl file static class (IReference vtable, get_Value)

The Invoke body for the CCW dispatch handles bool/byte, char/ushort, enum/underlying,
and string conversions, plus the standard pattern for runtime classes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ppersCallback

Non-generic delegate ComWrappersMarshallerAttribute now emits:
- GetOrCreateComInterfaceForObject (uses TrackerSupport flag)
- ComputeVtables (uses InterfaceEntriesImpl.Entries)
- CreateObject (uses WindowsRuntimeDelegateMarshaller.UnboxToManaged)

Non-generic delegate ComWrappersCallback (emitted as 'file abstract') now contains
a CreateObject body that creates the object reference and constructs the projected
delegate via the NativeDelegate Invoke extension method when supported.

Generic delegates retain throw null bodies (no per-instantiation infrastructure
emitted in the projection layer).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Even non-blittable struct types (e.g. those with bool/char fields) can be
copied directly via 'var value = (T)GetInstance<object>(...); *(T*)result = value;'
since the C# struct field layout matches the WinRT ABI for these primitives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… class

Almost-blittable structs are structs with only primitive fields (including bool/char,
which have matching C# and WinRT ABI layouts) and no reference type fields.

These can pass directly across the WinRT ABI without per-field marshalling. Now:
- EmitAbiMethodBodyIfSimple supports them for params, returns, and out params
- WriteStructEnumMarshallerClass emits BoxToUnmanaged/UnboxToManaged with the simple
  WindowsRuntimeValueTypeMarshaller pattern
- Complex structs (with string/object fields) still emit throw null stubs for
  ConvertToUnmanaged/ConvertToManaged/Dispose pending field-level marshalling support

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WinRT metadata represents 'out byte[]' as 'byte[]&' (ByRef wrapping SzArray).
Previously this was categorized as ParamCategory.Out and emitted as 'out byte value'
instead of 'out byte[] value'. Now ParamHelpers.GetParamCategory peels through ByRef
to detect array params, and WriteProjectionParameterType for ReceiveArray peels
through ByRef to extract the element type.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds full marshalling for 'out byte[]' / 'out T[]' style parameters where T is a
blittable primitive or almost-blittable struct. Pattern follows the existing
ReceiveArray return value support: declare uint length + T* data locals, pass
&__name_length, &__name_data to the vtable, then convert via UnsafeAccessor
ConvertToManaged_<name> and free in the finally block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…BI Methods

Truth uses 'fixed(void* _name = name)' + (uint)name.Length, _name pattern for
Span<T> params regardless of whether they're in (PassArray) or in/out (FillArray).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Factory callback now handles ReadOnlySpan<T>/Span<T> params (PassArray/FillArray)
for blittable element types. Args struct binding uses Span<T>/ReadOnlySpan<T>,
and the callback invokes via 'fixed(void* _name = name)' + (uint)name.Length, _name
pattern matching the truth.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Now emits *__retval writeback after calling the managed delegate, with proper
conversion: bool -> byte, char -> ushort, enum -> underlying, string via
HStringMarshaller, runtime class via Marshaller.ConvertToUnmanaged + DetachThisPtrUnsafe.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Element ABI type is 'void*' (function pointer signature uses 'void**'),
local is 'void**', UnsafeAccessor type path is rooted at the element type's
namespace (e.g. 'ABI.Windows.AI.Actions.Hosting.<...>ArrayMarshaller') for
runtime classes, or 'ABI.System.<...>ArrayMarshaller' for strings/blittable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Complex structs (with string fields, but no other reference types) now emit:
- ConvertToUnmanaged: builds ABI struct via field-by-field assignment with
  HStringMarshaller.ConvertToUnmanaged for string fields
- ConvertToManaged: constructs projected struct via constructor accepting
  the marshalled fields
- Dispose: frees string fields via HStringMarshaller.Free
- BoxToUnmanaged: same simple WindowsRuntimeValueTypeMarshaller pattern
- UnboxToManaged: unboxes to ABI struct then ConvertToManaged

Also fixes ABI struct field types: enum/bool now use the projected type
(matching the truth) instead of the ABI primitive form (int/byte). The C#
struct layout matches the WinRT ABI directly for these primitives.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emits UnsafeAccessor static externs to call ConvertToManaged for generic
instance types (e.g. IEnumerable<T>) and uses them in the dispatch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EmitNativeDelegateBody now handles delegates with simple return types: blittable
primitives, almost-blittable structs, strings, runtime classes, objects.
Patterns mirror the truth: declare __retval local of ABI type, pass &__retval
to vtable, convert back to managed return type, free if needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Complex structs (with reference type fields like string or Nullable<T>) now
get full marshalling: ABI struct local + Marshaller.ConvertToManaged + Dispose
in finally. Mirrors the truth pattern for properties returning structs like
AccessListEntry / StorePackageUpdateStatus.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use ternary expressions for elementAbi assignment in receive-array path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…odies

Ports the C++ logic from cswinrt code_writers.h:
- For runtime class/object element: declare InlineArray16<nint> + ArrayPool<nint>
  fallback for the data buffer, fixed(void* _name = __name_span), emit
  CopyToUnmanaged_<name> UnsafeAccessor and call before vtable, Dispose_<name>
  + ArrayPool<nint>.Shared.Return in finally.
- For string element: also declare InlineArray16<HStringHeader> + InlineArray16<nint>
  (pinned handles), use HStringArrayMarshaller.ConvertToUnmanagedUnsafe to fill,
  HStringArrayMarshaller.Dispose + return all 3 ArrayPools in finally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aller path)

Allow PassArray of string elements in the simple-body check, and emit a combined
fixed block 'fixed(void* _name = __name_span, _name_inlineHeaderArray = __name_headerSpan)'
to provide both the data pointer and the header pointer needed by
HStringArrayMarshaller.ConvertToUnmanagedUnsafe. Mirrors C++ cswinrt
write_locals/write_fixed_marshaler/write_dispose patterns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In WinMD metadata, Nullable<T> is encoded as Windows.Foundation.IReference<T>.
Detect this in the field walk and emit the appropriate marshalling:
- ConvertToUnmanaged: ABI.System.<TypeName>Marshaller.BoxToUnmanaged(value.<f>).DetachThisPtrUnsafe()
- ConvertToManaged: ABI.System.<TypeName>Marshaller.UnboxToManaged(value.<f>)
- Dispose: WindowsRuntimeUnknownMarshaller.Free(value.<f>)
- ABI struct field: void* (the IReference<T>* pointer)
- BoxToUnmanaged: use CreateComInterfaceFlags.TrackerSupport when struct has reference fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Construct ReadOnlySpan<T>/Span<T> from the (length, pointer) ABI pair before
calling the managed delegate's Invoke. Mirrors the C++ truth pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…method

Adds fixed(void* _name = name) block + (uint)name.Length, _name args to call.
Updates IsDelegateInvokeNativeSupported predicate to allow PassArray of blittable
elements. This also enables ComWrappersCallback.CreateObject to construct the
delegate via the NativeDelegate Invoke method group.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update WriteAbiType to emit the projected struct type (not ABI) for almost-blittable
structs (those with only primitive fields, no string/Nullable<T>/object fields).
Also extend EmitDoAbiBodyIfSimple to support these structs in returns and Out params.

This matches the truth pattern where e.g. BandwidthStatistics is passed across the
WinRT ABI directly without an ABI struct wrapper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/WinRT.Projection.Generator.Writer.TestRunner/Program.cs
Comment thread src/WinRT.Projection.Generator.Writer/Writers/TypeWriter.cs
Sergio0694 and others added 5 commits May 7, 2026 01:40
CsWinRT FastAbi compresses a class's exclusive_to interfaces into a single
"default" interface vtable at runtime. The class members on Simple (Method2,
InvokeEvent0, etc.) all dispatch through ISimple's vtable using slot
indices that include the merged contributions of ISimple2-9, IOrange,
IBanana etc. There is NO separate vtable for ISimple5 on the COM object.

Before this commit, our generator was emitting:
  - A separate _objRef_test_component_fast_ISimple5 field (and lazy QI)
  - A separate ISimple5Methods static class
  - Class member dispatch through ISimple5Methods.Method2(_objRef..ISimple5)

QI for ISimple5 either failed or returned an unrelated vtable, so the call
to slot 6 of "ISimple5" produced 0xC0000005 access violations at runtime
(visible in CI as Fatal error 0xC0000005 in
ABI.test_component_fast.ISimple5Methods.Method2 from
test_component_fast.Simple.Method2 in UnitTest.TestWinRT.Fast_Abi_Simple).

This commit ports the C++ FastAbi handling to match cswinrt.exe exactly:

1. WriteClassObjRefDefinitions (CodeWriters.ObjRefs.cs): skip emitting
   _objRef_* fields for fast-abi non-default exclusive interfaces. Mirrors
   C++ code_writers.h:2960 (write_class_objrefs_definition).

2. WriteInterfaceMarshallerStub (CodeWriters.Abi.cs):
   - Skip Methods class for fast-abi non-default exclusive interfaces.
     Mirrors C++ code_writers.h:9082-9089 (early return on
     contains_other_interface).
   - For the default interface of a fast-abi class, emit a merged Methods
     class containing the default interface's members AT slot 6+ followed
     by each [ExclusiveTo] non-default interface's members at progressively
     increasing slot indices. Slot index advancement uses
     CountMethods(default) + GetClassHierarchyIndex(class) for the first
     other interface, then adds CountMethods(other) per next interface.
     Mirrors C++ code_writers.h:9113-9128.
   - Refactored the existing per-interface emission body into the
     EmitMethodsClassMembersFor helper for reuse.

3. WriteInterfaceMembers (CodeWriters.ClassMembers.cs): when a fast-abi
   class member's interface is exclusive_to that class, redirect the ABI
   Methods static class name AND the _objRef_ field to the default
   interface. Mirrors C++ code_writers.h:4250-4251 (semantics_for_abi_call
   assignment).

New helpers in CodeWriters.Class.cs:
  - FindFastAbiClassType(iface): returns the [ExclusiveTo] class if it's
    fast-abi (mirrors C++ find_fast_abi_class_type).
  - GetFastAbiClassForInterface(iface): returns (class, default, others)
    if iface is exclusive_to a fast-abi class (mirrors C++
    get_fast_abi_class_for_interface).
  - IsFastAbiOtherInterface(iface): true if iface is in a fast-abi class's
    other_interfaces list.
  - IsFastAbiDefaultInterface(iface): true if iface is the default of
    a fast-abi class.

In CodeWriters.Abi.cs:
  - GetClassHierarchyIndex(class): mirrors C++ get_class_hierarchy_index
    in helpers.h:1496.
  - CountMethods(iface), HasEmittableMembers, InterfacesEqualByName.

Validated against test_component_fast (the affected scenario):
  - ISimpleMethods now has all merged members at correct slots (6,7,8,9,
    10,11,12,13,16,17,18,...) matching truth byte-for-byte
  - ISimple5Methods refs in generated output: 0 (was N before, matched truth)
  - _objRef_test_component_fast_ISimple5: 0 (was 1 before, matched truth)
  - Class Simple methods all dispatch through ISimpleMethods +
    _objRef_test_component_fast_ISimple (matches truth exactly)

Validated regression-free against:
  - All 5 standard regen scenarios: 0 throw null!
  - AuthoringTest aligned: 92/92 IIDs match (no regression)
  - WinUI aligned (with test_component winmds available): missing=0
    extra=0 diff=0 (was 4 missing before this fix)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
For a fast-abi class (e.g. test_component_fast.Simple), an event declared on
a non-default exclusive interface (e.g. ISimple5.Event0) was emitted with the
inline _eventSource_<name> field pattern using a vtable slot computed from
that interface's own method order. At runtime the merged native vtable
exposes only the default interface's vtable; the secondary interface's slot
index is therefore invalid and AddRef/Release on the resulting EventSource
walks a bogus pointer, producing 0xC0000005 in the unit test
'TestWinRT.Fast_Abi_Simple' at 'Simple.add_Event0'.

C++ truth (code_writers.h:4293) gates the inline path on
'!is_fast_abi_iface || is_default_interface'. When false (i.e. fast-abi
exclusive non-default), it dispatches through the merged ABI Methods class
helper instead, which uses the correct merged-vtable slot and a
ConditionalWeakTable for per-instance event-source caching:

    add => global::ABI.<Ns>.<DefaultIface>Methods.<EventName>(
        (WindowsRuntimeObject)this, _objRef_<...>).Subscribe(value);
    remove => ... .Unsubscribe(value);

WriteInterfaceMembers already had this ABI-class rerouting for methods and
properties (commit ee6aa4c); extend the same rerouting to events.

Validation: regenerated all 8 standard scenarios; the 'event Event0' block
in 'Simple' now matches truth byte-for-byte; non-fast-abi events still emit
the inline _eventSource_ field pattern (refgen-windows still has 4074
_eventSource_ refs); AuthoringTest IIDs preserved at 92/92.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… merged generator)

The previous setup added the ref generator as a '<ProjectReference>' from
'src/Projections/Directory.Build.targets', which caused all projection projects
under 'src/Projections/' to acquire it as a build-time project reference.
'<ProjectReference>' flows the consuming project's Platform to the reference,
so when a projection was built for 'Platform=x86', MSBuild tried to build the
ref generator csproj at 'PlatformTarget=x86'. The ref generator has
'PublishAot=true', which auto-resolves 'RuntimeIdentifier=win-x64' (the host
RID), and the SDK rejects the combination with NETSDK1032:

  error NETSDK1032: The RuntimeIdentifier platform 'win-x64' and the
  PlatformTarget 'x86' must be compatible.
  [src\\WinRT.Projection.Ref.Generator\\WinRT.Projection.Ref.Generator.csproj]

Mirror the existing pattern used for the other NAOT-published generators
('WinRT.Projection.Generator', 'WinRT.Interop.Generator', 'WinRT.Impl.Generator'):
declare a '<BuildDependency>' on the ref generator inside 'src/cswinrt.slnx'
for each of the eight 'Projections/*/...csproj' entries. Solution-level
BuildDependency does not flow Platform/PlatformTarget into the dependency,
so the ref generator builds at its own default 'AnyCPU' platform regardless
of which projection-project platform is being built.

Removed 'src/Projections/Directory.Build.targets' entirely (its only purpose
was the now-removed ProjectReference; MSBuild walks up to the parent
'src/Directory.Build.targets' automatically).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Delete many generated/auxiliary source files under src/cswinrt/strings (and additions subfolders) and update cswinrt.vcxproj and cswinrt.vcxproj.filters to remove their <None> entries and filters. Keeps the project file/folder filters in sync with the removed files.
Remove inclusion of strings.h and the code paths that generated additional C# outputs from strings::additions and strings::base. This eliminates custom namespace additions and the extra file-generation (e.g., ComInteropExtensions and other base string files), so those additional source files will no longer be produced by main.cpp.
Comment thread src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.Interface.cs Outdated
Comment thread src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.cs Outdated
Comment thread src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.cs Outdated
Comment thread src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.Class.cs
Comment thread src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.Class.cs
Comment thread src/WinRT.Projection.Generator.Writer/Writers/CodeWriters.Abi.cs
Comment thread src/WinRT.Projection.Generator.Writer/Helpers/AttributedTypes.cs Outdated
@manodasanW
Copy link
Copy Markdown
Member

Maybe we want to put this in staging/CodeWriters until you are done with the remaining refactoring PRs?

@manodasanW
Copy link
Copy Markdown
Member

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Sergio0694 and others added 3 commits May 8, 2026 13:44
The check `(baseNs == "WindowsRuntime" && baseName == "WindowsRuntimeObject")`
in `WriteTypeInheritance` was unreachable: `WindowsRuntime.WindowsRuntimeObject`
is a managed type defined in `WinRT.Runtime` and is never referenced as a
base type in any `.winmd`. The writer only ingests `.winmd` files (verified
by `MetadataCache.Load` and `ProjectionWriterOptions.InputPaths` usage), so
the branch never fires.

The C++ tool's equivalent `write_type_inheritance` (code_writers.h:7395)
also only checks for `object_type` (System.Object) -- there is no
`WindowsRuntimeObject` check. The check in our C# port was added
speculatively/defensively during early porting.

Replaces the two-condition `isObject` check with a direct
`!(baseNs == "System" && baseName == "Object")`, matching C++ behavior.

Validation: regenerated all 8 standard scenarios (full, windows, everything,
everything-with-ui, pushnot, authoring-aligned, winsdk-aligned, winui-fast)
-- all byte-identical to the pre-change baseline (SHA256 of every emitted
`.cs` unchanged). The branch was indeed unreachable for the entire WinSDK +
WinUI + test_component projection corpus (1,500+ runtime classes).

Addresses PR #2415 review comment r3205242890.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- `AddGenericTypeReferencesInType(TypeDefinition)` -- pure no-op (just `_ = type`).
  The C++ original populated a concurrent set never read in `main.cpp`'s output
  pipeline, so the C# port stubbed it as a no-op. Also removed the lone call site
  in `ProjectionGenerator.cs:301`.
- `WriteInterfacePlaceholder(TypeWriter, TypeDefinition)` -- emitted a
  `partial interface X { /* TODO: interface members */ }` stub. Never called;
  the real implementation lives in `CodeWriters.Interface.cs`.
- `WriteClassPlaceholder(TypeWriter, TypeDefinition)` -- emitted a
  `partial class X { /* TODO: class members */ }` stub. Never called;
  the real implementation lives in `CodeWriters.Class.cs`.
- `WriteAttributePlaceholder(TypeWriter, TypeDefinition)` -- emitted a
  `sealed class X : System.Attribute { /* TODO: attribute members */ }` stub.
  Never called.

Verified via repository-wide grep that the three placeholders have no call sites
outside their own definition, and that `AddGenericTypeReferencesInType` is only
called from the now-removed `ProjectionGenerator.cs:301` line.

Validation: regenerated all 8 standard scenarios (full, windows, everything,
everything-with-ui, pushnot, authoring-aligned, winsdk-aligned, winui-fast)
-- all byte-identical to the pre-change baseline (SHA256 of every emitted
`.cs` unchanged).

Addresses PR #2415 review comments r3205476577, r3205485522 and r3205486785.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`GetVisibility` extracts the visibility enum value from a `[ComposableAttribute]`.
The attribute's constructor signature is

    ComposableAttribute(Type factoryInterface, CompositionType visibility, uint version, ...)

so the visibility enum (marshalled as `int` in the custom-attribute blob) is
always at fixed-argument index 1. The previous loop-based "find the first
`int`-typed arg" implementation was unnecessarily general and obscured the
intent (and the doc comment already specifically said "for `[ComposableAttribute]`").

Replace with a list-pattern that matches index 1 directly:

    if (attr.Signature is { FixedArguments: [_, { Element: int e }, ..] })
    {
        return e;
    }
    return 0;

Validation: regenerated all 8 standard scenarios (full, windows, everything,
everything-with-ui, pushnot, authoring-aligned, winsdk-aligned, winui-fast)
-- all byte-identical to the pre-change baseline. Confirms the visibility is
in fact at index 1 across the full WinSDK + WinUI + test_component corpus
(would have shifted `protected`/`public` composable factories otherwise).

Addresses PR #2415 review comment r3205714019.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Sergio0694 Sergio0694 changed the base branch from staging/3.0 to staging/CodeWriters May 8, 2026 21:11
@Sergio0694 Sergio0694 merged commit c580404 into staging/CodeWriters May 9, 2026
11 checks passed
@Sergio0694 Sergio0694 deleted the user/sergiopedri/csharp-cswinrt-exe branch May 9, 2026 09:06
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.

2 participants