Skip to content
Draft
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
10 changes: 8 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ Not all WinRT types are generated automatically by the projection writer into SD

#### Custom-mapped types

These are built-in C#/.NET types that are mapped to WinRT types. Because the .NET type already exists in the BCL (and is not owned by CsWinRT), it cannot be "projected" in the usual sense. Instead, CsWinRT associates the necessary WinRT metadata with it via attributes such as `[WindowsRuntimeMappedMetadata]` and dedicated ABI marshalling code in the `ABI/System/` directory. Some of these types map to identically-named WinRT types (e.g. `int` ↔ `Int32`, `Guid` ↔ `Guid`), while others map to a different WinRT type entirely. The `EventHandler` delegate is especially noteworthy: the non-generic `System.EventHandler` is handled as a special case (see `ABI/System/EventHandler.cs`), while `System.EventHandler<TEventArgs>` maps to `Windows.Foundation.EventHandler<T>`, and `System.EventHandler<TSender, TEventArgs>` (a two-parameter generic delegate projected by CsWinRT) maps to `Windows.Foundation.TypedEventHandler<TSender, TResult>`.
These are built-in C#/.NET types that are mapped to WinRT types. Because the .NET type already exists in the BCL (and is not owned by CsWinRT), it cannot be "projected" in the usual sense. Instead, CsWinRT associates the necessary WinRT metadata with it via attributes such as `[WindowsRuntimeType]` (a parameterless marker; see below) and dedicated ABI marshalling code in the `ABI/System/` directory. Some of these types map to identically-named WinRT types (e.g. `int` ↔ `Int32`, `Guid` ↔ `Guid`), while others map to a different WinRT type entirely. The `EventHandler` delegate is especially noteworthy: the non-generic `System.EventHandler` is handled as a special case (see `ABI/System/EventHandler.cs`), while `System.EventHandler<TEventArgs>` maps to `Windows.Foundation.EventHandler<T>`, and `System.EventHandler<TSender, TEventArgs>` (a two-parameter generic delegate projected by CsWinRT) maps to `Windows.Foundation.TypedEventHandler<TSender, TResult>`.

The following table lists all custom-mapped types where the .NET type maps to a **differently-named** WinRT type:

Expand Down Expand Up @@ -323,6 +323,12 @@ These are WinRT types that are defined directly in `WinRT.Runtime` rather than b

Some of these types — particularly the bindable collection interfaces (`IEnumerable`, `IList`) and XAML-related types — have **different IIDs and/or runtime class names** depending on whether `Windows.UI.Xaml.*` (UWP XAML) or `Microsoft.UI.Xaml.*` (WinUI) support is being used (controlled by the `CsWinRTUseWindowsUIXamlProjections` MSBuild property). This requires further special handling in both the generated projection code and the interop generator to ensure that the correct marshalling and metadata info is associated with them at publish time.

#### The `[WindowsRuntimeType]` marker and the metadata-types lookup

Every projected type, plus every proxy type for a custom-mapped type, is tagged with the parameterless, implementation-only **`[WindowsRuntimeType]`** marker (`Attributes/WindowsRuntimeTypeAttribute.cs`). The runtime and the build tools only ever check for the *presence* of this marker to decide whether a type participates in Windows Runtime marshalling — they never read any per-type metadata value at runtime. Proxy types are distinguished from projected types by `[WindowsRuntimeMappedType]` (which proxies carry to point at their public type); the `System.EventHandler` proxy intentionally stays unmarked, as it is a pure custom type rather than a real Windows Runtime metadata type.

The mapping from a projected type to its source `.winmd` module name (its "contract"/"stem") — needed only by build-time tooling (the interop and WinMD generators) — is **not** stored on each type. Instead, the projection generator emits a single centralized, fully trimmable lookup type, **`ABI.WindowsRuntimeMetadataTypes`**, carrying one **`[WindowsRuntimeMetadata(typeof(T), "stem")]`** entry per projected type (`Attributes/WindowsRuntimeMetadataAttribute.cs`, repurposed to a `(Type, string)`, `class`-targeting, `AllowMultiple` attribute). This mirrors how `ABI.WindowsRuntimeDefaultInterfaces` centralizes default interfaces, and lets the metadata be dead-code-eliminated when unused. Both the marker and the lookup type are implementation-only and are stripped from reference projections. `WinRT.Runtime`'s own manually-projected types do not contribute to a lookup type: the interop generator addresses them with the well-known `#CsWinRT` assembly identifier, and the WinMD generator reads their contract from the real `[ContractVersion]` metadata in the `WinRT.Runtime` reference assembly.

### 2. WinRT.SourceGenerator2 (`src/Authoring/WinRT.SourceGenerator2/`)

A Roslyn incremental source generator and diagnostic analyzer package. Runs at **design time** (IntelliSense) and **build time**. It is intentionally lightweight — heavy codegen is deferred to the post-build CLI tools.
Expand Down Expand Up @@ -635,7 +641,7 @@ A small build project that produces **`WindowsRuntime.Internal.winmd`** — the

**Project settings:**

- **Target**: `net10.0-windows10.0.26100.1` (the `.1` TFM revision selects the `cswinrt3` Windows SDK projection reference assemblies, which carry `[WindowsRuntimeMetadata]` attributes the WinMD generator reads)
- **Target**: `net10.0-windows10.0.26100.1` (the `.1` TFM revision selects the `cswinrt3` Windows SDK projection reference assemblies, which carry the `[ContractVersion]` (and `[WindowsRuntimeType]`) Windows Runtime metadata the WinMD generator reads)
- **Assembly name**: `WindowsRuntime.Internal`
- **Nullable**: `disable` (the Windows Runtime type system does not support nullability annotations)
- **WindowsSdkPackageVersion**: pinned (e.g. `10.0.26100.85-preview`) so the .NET SDK adds the implicit framework reference to the matching `Microsoft.Windows.SDK.NET.Ref` package
Expand Down
4 changes: 2 additions & 2 deletions .github/skills/interop-generator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ The generator processes two categories of assemblies:
- `System.Collections.Concurrent.ConditionalWeakTable<,>` — Memory semantics conflict

**Type inclusion criteria:**
- Must be a projected Windows Runtime type. A type is recognized as projected in any of three ways: it carries the per-type `[WindowsRuntimeMetadata]` attribute (implementation projections and types in `WinRT.Runtime.dll`); it is a public type from an authored component assembly (`IsComponentWindowsRuntimeType`); or it is defined in a reference projection assembly marked `[WindowsRuntimeReferenceAssembly]` (`IsReferenceProjectionWindowsRuntimeType`). The latter two do **not** carry the per-type `[WindowsRuntimeMetadata]` attribute — reference projections shipped in NuGet packages have it stripped (it is an implementation-only attribute, absent from the `WinRT.Runtime.dll` reference assembly they compile against), so the interop generator recognizes them by their assembly-level marker instead.
- Must be a projected Windows Runtime type. A type is recognized as projected in any of three ways: it carries the per-type `[WindowsRuntimeType]` marker (implementation projections and types in `WinRT.Runtime.dll`); it is a public type from an authored component assembly (`IsComponentWindowsRuntimeType`); or it is defined in a reference projection assembly marked `[WindowsRuntimeReferenceAssembly]` (`IsReferenceProjectionWindowsRuntimeType`). The latter two do **not** carry the per-type `[WindowsRuntimeType]` marker — reference projections shipped in NuGet packages have it stripped (it is an implementation-only attribute, absent from the `WinRT.Runtime.dll` reference assembly they compile against), so the interop generator recognizes them by their assembly-level marker instead.
- Generic types must be fully constructed (no open generic parameters)
- Type hierarchy must be fully resolvable (no missing dependencies)
- Must not be a managed-only type (types that never cross the Windows Runtime boundary)
Expand Down Expand Up @@ -732,7 +732,7 @@ Almost everything the generated code calls into lives in `WinRT.Runtime.dll` as
- **Collection adapters and adapter extensions** (`WindowsRuntime.InteropServices`, under `Collections/`) — `IListAdapter<T>`, `IReadOnlyListAdapter<T>`, `IDictionaryAdapter<K, V>`, `IReadOnlyDictionaryAdapter<K, V>`, `IEnumerableAdapter<T>`, `IEnumeratorAdapter<T>`, `BindableIReadOnlyListAdapter`, plus the per-element-category `*AdapterReferenceTypeExtensions` / `*BlittableValueTypeExtensions` / `*ManagedValueTypeExtensions` / `*UnmanagedValueTypeExtensions` / `*KeyValuePairTypeExtensions` / `*NullableTypeExtensions` types. Generated `GetMany` and collection vtable bodies call into the adapter extensions.
- **Object reference and ComWrappers infrastructure** (`WindowsRuntime.InteropServices`, under `ObjectReference/`, `Callbacks/`) — `WindowsRuntimeObjectReference`, `WindowsRuntimeObjectReferenceValue`, and the callback interfaces `IWindowsRuntimeObjectComWrappersCallback`, `IWindowsRuntimeArrayComWrappersCallback`, `IWindowsRuntimeUnsealedObjectComWrappersCallback`. Generated methods take/return `WindowsRuntimeObjectReference`/`WindowsRuntimeObjectReferenceValue` and manage COM lifetime (`AsValue`, `GetThisPtrUnsafe`, `Dispose`); generated ComWrappers callbacks implement the callback interfaces.
- **Event sources** (`WindowsRuntime.InteropServices`, mostly under `Events/`) — `EventSource<T>`, `EventHandlerEventSource<T>`/`EventHandlerEventSource<TSender, TResult>`, `EventRegistrationToken`, `EventRegistrationTokenTable<T>`, `IObservableVectorEventSourceFactory<T>`, `IObservableMapEventSourceFactory<K, V>`. Generated event marshalling derives from or instantiates these.
- **Type-map groups and metadata/marshalling attributes** (`WindowsRuntime`, `WindowsRuntime.InteropServices`, under `TypeMapGroups/`, `Attributes/`) — the markers `WindowsRuntimeComWrappersTypeMapGroup`, `WindowsRuntimeMetadataTypeMapGroup`, `DynamicInterfaceCastableImplementationTypeMapGroup`, and the attributes stamped onto generated types: `WindowsRuntimeMetadataAttribute`, `WindowsRuntimeMappedMetadataAttribute`, `WindowsRuntimeMetadataTypeNameAttribute`, `WindowsRuntimeDefaultInterfaceAttribute`, `WindowsRuntimeExclusiveToInterfaceAttribute`, `WindowsRuntimeReferenceTypeAttribute`, `WindowsRuntimeManagedOnlyTypeAttribute`, `WindowsRuntimeMappedTypeAttribute`, `WindowsRuntimeComWrappersMarshallerAttribute`.
- **Type-map groups and metadata/marshalling attributes** (`WindowsRuntime`, `WindowsRuntime.InteropServices`, under `TypeMapGroups/`, `Attributes/`) — the markers `WindowsRuntimeComWrappersTypeMapGroup`, `WindowsRuntimeMetadataTypeMapGroup`, `DynamicInterfaceCastableImplementationTypeMapGroup`, and the attributes stamped onto generated types: `WindowsRuntimeTypeAttribute` (the parameterless marker placed on projected/proxy types; the type → source `.winmd`-stem mapping is centralized on the `ABI.WindowsRuntimeMetadataTypes` lookup type via `WindowsRuntimeMetadataAttribute(typeof(T), "stem")` instead of on each type), `WindowsRuntimeMetadataTypeNameAttribute`, `WindowsRuntimeDefaultInterfaceAttribute`, `WindowsRuntimeExclusiveToInterfaceAttribute`, `WindowsRuntimeReferenceTypeAttribute`, `WindowsRuntimeManagedOnlyTypeAttribute`, `WindowsRuntimeMappedTypeAttribute`, `WindowsRuntimeComWrappersMarshallerAttribute`.
- **IID table and error helpers** (`WindowsRuntime.InteropServices`) — `WellKnownInterfaceIIDs` (the generator emits `get_IID(...)` calls and reads the reserved-IID set) and `RestrictedErrorInfo` (`ThrowExceptionForHR`, etc., used in generated async/collection bodies to translate HRESULTs to exceptions).

Because these APIs are absent from the `WinRT.Runtime.dll` reference assembly, the generated `WinRT.Interop.dll` references types that exist only in the implementation assembly, and the generator emits assembly-level `[IgnoresAccessChecksTo]` to reach the non-public members among them. This is also why `cswinrtinteropgen` must be version-matched to the `WinRT.Runtime.dll` it targets (see "Version compatibility" above): the shape of these types can change at any time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ These are the well-known assemblies and their compact identifiers:

Compact identifiers are prefixed with `#` to distinguish them from user-defined assembly names.

For types not belonging to any well-known assembly, the implementation also derives the assembly identifier from the type's `[WindowsRuntimeMetadata]` value (the source `.winmd` module name, i.e. its "stem") instead of the actual assembly name. This allows types carrying WinRT metadata to be identified by their canonical Windows Runtime name rather than the .NET assembly they happen to live in. The stem is read directly from the attribute when present — implementation projections, the authored component projection, and manually projected types in `WinRT.Runtime.dll` all carry it. Reference projections shipped in Windows Runtime projection NuGet packages have the attribute **stripped** (it is an implementation-only attribute, absent from the `WinRT.Runtime.dll` reference assembly they compile against); for a type defined in such a reference projection (detected via `IsReferenceProjectionWindowsRuntimeType`), the stem is instead recovered from the matching type in the third-party implementation projection (`WinRT.Projection.dll`), located through `RuntimeContext.GetLoadedAssemblies()`. This recovery is essential because the projection writer encodes that very same stem into the `[UnsafeAccessorType]` references it emits into the implementation projection, and the reference projection's own assembly name can differ from the stem (e.g. when several `.winmd` files are merged into a single projection assembly, as with WinUI / Windows App SDK). If the stem cannot be found anywhere, the raw assembly name is used as-is. The recovery logic lives in `GetWindowsRuntimeMetadataName` (`src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs`).
For types not belonging to any well-known assembly, the implementation also derives the assembly identifier from the type's source `.winmd` module name (its "stem") instead of the actual assembly name. This allows types carrying WinRT metadata to be identified by their canonical Windows Runtime name rather than the .NET assembly they happen to live in. The stem is no longer stored on each projected type; it is recorded on a single centralized, trimmable lookup type — `ABI.WindowsRuntimeMetadataTypes` — which carries one `[WindowsRuntimeMetadata(typeof(T), "stem")]` entry per projected type, emitted into the implementation projection by the projection writer. `GetWindowsRuntimeMetadataName` selects the right implementation projection for the type (via `GetImplementationProjectionModule`: the Windows SDK, Windows SDK XAML, or merged third-party projection) and looks the type up by namespace and name in that module's `GetWindowsRuntimeMetadataTypesLookup()` (a cached read of the lookup type's attributes). This works uniformly for types resolved from implementation projections and from reference projections, since both select the same implementation projection that carries the lookup type. This stem is the authoritative value the projection writer encodes into the `[UnsafeAccessorType]` references it emits into that same implementation projection, and the projection's own assembly name can differ from the stem (e.g. when several `.winmd` files are merged into a single projection assembly, as with WinUI / Windows App SDK). If the stem cannot be found, the raw assembly name is used as-is. The lookup logic lives in `GetWindowsRuntimeMetadataName` (`src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs`) and `GetWindowsRuntimeMetadataTypesLookup` (`ModuleDefinitionExtensions.WindowsRuntimeMetadataTypesLookup.cs`).

> [!NOTE]
> Not all BCL types live in `System.Runtime`. For example, the `System.Numerics` types (`Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector2`, `Vector3`, `Vector4`) are in the `System.Numerics.Vectors` assembly, so their assembly identifier is `System-Numerics-Vectors` (not `#corlib`) after the `.` → `-` substitution. The assembly used is always the one from the type's actual metadata scope, not the namespace.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ private static void IBindableVectorViewType(

// Define the type map entries. We only need one in the marshalling external type map. We can use 'IEnumerable'
// as the trim target for the specialized RCW type (since the 'IReadOnlyList' interface does not exist in .NET).
// This is also why we don't need to emit the '[WindowsRuntimeMappedMetadata]' attribute on the proxy type above.
// This is also why we don't need to emit the '[WindowsRuntimeType]' marker on the proxy type above.
InteropTypeDefinitionBuilder.TypeMapAttributes(
runtimeClassName: runtimeClassName,
metadataTypeName: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,8 @@ public static void Proxy(
bool useWindowsUIXamlProjections,
out TypeDefinition proxyType)
{
// This is a proxy for Windows Runtime arrays, so we also need to emit the '[WindowsRuntimeMappedMetadata]'
// attribute, so that during 'TypeName' marshalling we can detect whether the type is a metadata type. Note
// This is a proxy for Windows Runtime arrays, so we also need to emit the '[WindowsRuntimeType]'
// marker, so that during 'TypeName' marshalling we can detect whether the type is a metadata type. Note
// that arrays with element types that are not Windows Runtime types will still have entries in the marshalling
// type map (as they're treated the same as normal user-defined types), so this allows us to distinguish them.
InteropTypeDefinitionBuilder.Proxy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ public static void Proxy(
/// </summary>
/// <param name="ns">The namespace for the type.</param>
/// <param name="name">The type name.</param>
/// <param name="mappedMetadata">The name of the mapped metadata for the proxy type (if <see langword="null"/>, the attribute will be omitted).</param>
/// <param name="mappedMetadata">Signals whether the proxy represents a Windows Runtime metadata type: when non-<see langword="null"/>, the <c>[WindowsRuntimeType]</c> marker is emitted (the string value itself is not emitted; the runtime only checks for the marker's presence). When <see langword="null"/>, no marker is emitted.</param>
/// <param name="runtimeClassName">The runtime class name for the managed type (if <see langword="null"/>, the attribute will be omitted).</param>
/// <param name="metadataTypeName">The metadata type name for the managed type (if <see langword="null"/>, the attribute will be omitted).</param>
/// <param name="mappedType">The <see cref="TypeSignature"/> for the mapped type the proxy type is for (if <see langword="null"/>, the attribute will be omitted).</param>
Expand Down Expand Up @@ -781,14 +781,13 @@ public static void Proxy(

module.TopLevelTypes.Add(proxyType);

// Add the '[WindowsRuntimeMappedMetadata]' attribute with the provided .winmd name, if available
// Mark metadata-type proxies with '[WindowsRuntimeType]' (the runtime only checks for its presence).
// Proxies that don't represent a Windows Runtime metadata type (mappedMetadata is null) stay unmarked.
if (mappedMetadata is not null)
{
proxyType.CustomAttributes.Add(new CustomAttribute(
constructor: interopReferences.WindowsRuntimeMappedMetadataAttribute_ctor,
signature: new CustomAttributeSignature(new CustomAttributeArgument(
argumentType: interopReferences.String,
value: mappedMetadata))));
constructor: interopReferences.WindowsRuntimeTypeAttribute_ctor,
signature: new CustomAttributeSignature()));
}

// Add the '[WindowsRuntimeClassName]' attribute with the provided runtime class name, if available
Expand Down
Loading
Loading