Port cswinrt.exe (projection generator) from C++ to C##2415
Merged
Sergio0694 merged 320 commits intoMay 9, 2026
Conversation
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>
manodasanW
reviewed
May 7, 2026
manodasanW
reviewed
May 7, 2026
manodasanW
reviewed
May 7, 2026
manodasanW
reviewed
May 7, 2026
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.
manodasanW
reviewed
May 7, 2026
manodasanW
reviewed
May 7, 2026
manodasanW
reviewed
May 7, 2026
manodasanW
reviewed
May 8, 2026
manodasanW
reviewed
May 8, 2026
manodasanW
reviewed
May 8, 2026
manodasanW
reviewed
May 8, 2026
manodasanW
reviewed
May 8, 2026
manodasanW
reviewed
May 8, 2026
manodasanW
reviewed
May 8, 2026
Member
|
Maybe we want to put this in staging/CodeWriters until you are done with the remaining refactoring PRs? |
manodasanW
approved these changes
May 8, 2026
Member
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
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>
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
Port the legacy C++
cswinrt.exeprojection generator (src/cswinrt/) to a new C# libraryWinRT.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 inCsWinRTGenerateProjection.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:
IndentedTextWriter-based emission.IndentedTextWriterfrom the source generator and is unit-testable via theWinRT.Projection.Generator.Writer.TestRunnerconsole driver.The new tool is invoked through the new
RunCsWinRTProjectionRefGeneratorMSBuild task in the existingCsWinRTGenerateProjectiontarget. All historicalCsWinRT*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 isProjectionWriter.Run(ProjectionWriterOptions). Internally:Generation/ProjectionGenerator.cs: top-level orchestration mirroringcswinrt::runinmain.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 ofhelpers.h/cmd_reader.hlogic.Metadata/MetadataCache.cs,TypeSemantics.cs,TypeCategorization.cs: AsmResolver-based metadata layer (replaceswinmd::reader).Resources/Additions/andResources/Base/: embedded namespace additions (Microsoft.UI.Xaml.CornerRadius,Windows.UI.Color, etc.) — port ofsrc/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 inCsWinRTGenerateProjectionwith the newRunCsWinRTProjectionRefGeneratortask. Reconstructs the include/exclude/input lists from existingCsWinRTFilters/CsWinRTPrivateFiltersso user-facing MSBuild properties are unchanged.nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets,nuget/Microsoft.Windows.CsWinRT.nuspec: register the newcswinrtprojectionrefgen.exetool path.src/build.cmd: publishcswinrtprojectionrefgen.exealongside 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)
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).var __arg_X = ...;locals, qualified vs unqualified type refs) — already deemed semantically equivalent.src/cswinrt/C++ tool once we're confident the C# port has shipped a stable release.WriteGuidSignature/WriteIidGuidPropertyForClassInterfaces/WriteAbiType— currently masked byWindowsMetadataExpanderpopulating the cache correctly in production builds, but worth firming up.