Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,7 @@ All five .NET build tools (`cswinrtprojectionrefgen`, `cswinrtprojectiongen`, `c
| Interop Generator | `CSWINRTINTEROPGENxxxx` | `0001`–`0100`, `9999` |
| WinMD Generator | `CSWINRTWINMDGENxxxx` | `0001`–`0014`, `9999` |
| Runtime (obsolete markers) | `CSWINRT3xxx` | `CSWINRT3001` (deriving from `WindowsRuntimeObject`), `CSWINRT3002` (type map group types), `CSWINRT3003` (component authoring attributes), `CSWINRT3004` (`WindowsRuntimeReferenceAssemblyAttribute`) |
| Projections (user-facing markers) | `CSWINRT3xxx` | `CSWINRT3005` (using an experimental Windows Runtime API; emitted by the projection writer as `[Experimental]`, see `docs/attribute-projections.md`) |

---

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Generated C# source can be compiled into interop assemblies, similar to how [C++
- [C#/WinRT version history](docs/version-history.md)
- [Repository structure](docs/structure.md)
- [COM Interop guide](docs/interop.md)
- [Attribute projections](docs/attribute-projections.md)
- [Object lifetime and reference tracking](docs/memory-management.md)
- Related projects
- [xlang](https://github.com/microsoft/xlang)
Expand Down
70 changes: 70 additions & 0 deletions docs/attribute-projections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Attribute projections

## Overview

Most Windows Runtime attributes are projected like any other Windows Runtime type: the attribute type itself is projected into C#, and every application of it is carried over onto the projected member. A handful are **custom-projected** instead: CsWinRT replaces them with a .NET attribute that models the same concept, or consumes them without ever emitting them.

There are two reasons to do that:

- **A .NET counterpart already exists.** `[Windows.Foundation.Metadata.Deprecated]` and `[Obsolete]`, or `[Windows.Foundation.Metadata.Experimental]` and `[Experimental]`, mean the same thing. Projecting the Windows Runtime one as-is would leave the .NET tooling (compilers, analyzers, IDEs) unable to act on it.
- **The attribute is metadata for CsWinRT itself.** `[Guid]` becomes the interface IID, `[AllowMultiple]` becomes part of the projected `[AttributeUsage]`, and `[Default]`, `[ExclusiveTo]`, `[Activatable]` and friends shape the generated code. Carrying them over would add permanent metadata that nothing reads.

This document is the reference for those cases, in both directions: `.winmd` → C# (**projection**, what a consumer sees) and C# → `.winmd` (**authoring**, what a component author writes). Everything not listed explicitly is covered by the catch-all row at the end of each table.

## Projection (`.winmd` → C#)

What CsWinRT emits for a Windows Runtime attribute application when projecting Windows Runtime metadata into C#.

The **Modes** column says which projection assemblies the result lands in. Reference projections are what user code, compilers, analyzers and metadata tooling compile against; implementation projections are only ever loaded at runtime. Attribute blobs cannot be trimmed by ILLink or ILC, so anything emitted into an implementation projection is permanent, unremovable metadata in the shipped application. Anything whose only consumer is a compiler or analyzer is therefore reference-projection-only.

| Windows Runtime metadata | Projected as | Modes |
| --- | --- | --- |
| `[Windows.Foundation.Metadata.Deprecated(message, DeprecationType.Deprecate, ...)]` | `[System.Obsolete(message)]` | both |
| `[Windows.Foundation.Metadata.Deprecated(message, DeprecationType.Remove, ...)]` | *nothing* — a removed member is omitted from the projection, and its ABI vtable slot is preserved but stubbed to return `E_NOTIMPL`; a removed type is omitted from the projection and the ABI alike | both |
| `[Windows.Foundation.Metadata.Experimental]` | `[System.Diagnostics.CodeAnalysis.Experimental("CSWINRT3005", UrlFormat = ..., Message = ...)]`, see [CSWINRT3005](diagnostics/cswinrt3005.md) | reference only |
| `[Windows.Foundation.Metadata.AttributeUsage(targets)]` | `[System.AttributeUsage(targets)]`, with `Windows.Foundation.Metadata.AttributeTargets` mapped to `System.AttributeTargets` | both |
| `[Windows.Foundation.Metadata.AllowMultiple]` | *nothing on its own* — it is folded into the `AllowMultiple` argument of the projected `[AttributeUsage]` (which is synthesized if the metadata declares none) | both |
| `[Windows.Foundation.Metadata.Guid(...)]` | `[System.Runtime.InteropServices.Guid("...")]`, as the IID of the projected interface or delegate | both |
| `[Windows.Foundation.Metadata.ContractVersion(contract, version)]` | itself, plus a synthesized `[System.Runtime.Versioning.SupportedOSPlatform("WindowsX.Y.Z.0")]` for the first contract that maps to a Windows release | reference only |
| `[Windows.Foundation.Metadata.GCPressure]`, `[Windows.Foundation.Metadata.Version]`, and every other `Windows.Foundation.Metadata` attribute not listed above | *nothing* — they are either consumed to shape the generated code (`[Default]`, `[ExclusiveTo]`, `[Activatable]`, `[Static]`, `[Composable]`, `[Overridable]`, `[FastAbi]`, ...) or have no meaning in a .NET projection | — |
| `[Windows.Foundation.Metadata.Overload(name)]`, `[Windows.Foundation.Metadata.DefaultOverload]`, and attributes from any other namespace (e.g. `[Microsoft.UI.Xaml.TemplatePart]`) | themselves — those attribute types are projected too, so their applications are carried over unchanged | reference only |

Two consequences of the custom-mapped rows are worth calling out, because they are the ones that show up as "missing" types:

- `Windows.Foundation.Metadata.ExperimentalAttribute`, `AttributeUsageAttribute` and `AttributeTargets` are **not** projected as types, since their .NET counterparts take their place. `ApiContractAttribute` and `ContractVersionAttribute` are not projected either: they are shipped by `WinRT.Runtime.dll` instead, so that projections can apply them without redefining them.
- `DeprecatedAttribute`, `DeprecationType`, `OverloadAttribute`, `DefaultOverloadAttribute`, `VersionAttribute` and the rest of `Windows.Foundation.Metadata` **are** projected as ordinary types, so component authors can apply them (see the next section).

Generated projection code suppresses `CS0612`/`CS0618` and `CSWINRT3005`: a projection has to name a deprecated or experimental type in order to project it at all, so those markers are guidance for the consumers of a projection, not for the projection itself. The suppressions are per file, so user code calling such an API still gets the diagnostic.

## Authoring (C# → `.winmd`)

What the WinMD generator emits into an authored component's `.winmd` for an attribute applied in C#.

| C# attribute | Emitted into the `.winmd` as | Notes |
| --- | --- | --- |
| `[System.Diagnostics.CodeAnalysis.Experimental(id, ...)]` | `[Windows.Foundation.Metadata.Experimental]` | The diagnostic id, `UrlFormat` and `Message` are dropped: Windows Runtime metadata has nowhere to carry them, and CsWinRT synthesizes its own when the component is projected back. On a property or event the attribute is emitted on the accessor method, matching MIDL. On an assembly, a module or a constructor it is dropped, and reported as [CSWINRT2021](#cswinrt2021-unsupported-experimental-targets). |
| `[Windows.Foundation.Metadata.Deprecated(...)]` | itself | On a property or event it is emitted on the accessor method, matching MIDL. |
| `[System.Runtime.InteropServices.Guid("...")]` | `[Windows.Foundation.Metadata.Guid(...)]` | Without one, the IID is derived from the type name (UUID v5, as MIDL does). |
| `[System.AttributeUsage(targets)]` | `[Windows.Foundation.Metadata.AttributeUsage(targets)]`, with `System.AttributeTargets` mapped to `Windows.Foundation.Metadata.AttributeTargets` | |
| `[System.Flags]` | itself | Windows Runtime metadata uses the same `System.FlagsAttribute` for flag enums. |
| `[Windows.Foundation.Metadata.Version(v)]` | itself | Emitted from the value the author specified, or the component's assembly major version when absent. |
| `[Windows.Foundation.Metadata.Overload(name)]` | itself | Emitted with the author-specified name, or a generated one for overloads that need disambiguating. |
| `[WindowsRuntime.Xaml.GeneratedCustomPropertyProvider]`, `[System.Reflection.DefaultMember]`, and anything under `System.Runtime.CompilerServices` | *nothing* | Either handled by CsWinRT itself or meaningless in Windows Runtime metadata. |
| Any other public attribute type | itself | Non-public attribute types, and attributes whose signature cannot be read, are skipped. |

> **Note**: `[System.Obsolete]` is **not** translated into `[Windows.Foundation.Metadata.Deprecated]`. It is copied as-is, so it is invisible to every other language projection. Use `[Windows.Foundation.Metadata.Deprecated]` to deprecate an API of an authored component. `[Experimental]` is the exception to that rule only because the Windows Runtime attribute has no projected form to apply.

### CSWINRT2021: unsupported `[Experimental]` targets

The .NET `[Experimental]` attribute supports more targets than the Windows Runtime one it is translated into. Types (runtime classes, interfaces, structs, enums and delegates), methods, properties, events and fields (individual enum members and struct fields) all translate; **assemblies**, **modules** and **constructors** have no Windows Runtime metadata target that can carry the marker:

- An assembly or a module has no counterpart at all: the generator produces a fresh `.winmd` containing only the authored types.
- A constructor is exposed through an activation factory method (`IFooFactory.CreateFoo`), and the `.ctor` row on the runtime class is not where markers live. MIDL never emits one there, and no `.ctor` row in the Windows SDK carries a `[Deprecated]` or `[Experimental]` attribute.

Rather than emit the marker where nothing would read it, which would silently make the API look stable to every other language projection, those applications are dropped and `CSWINRT2021` reports them at the source. Mark the whole runtime class as experimental to cover its constructors.

## Related documentation

- [CSWINRT3005](diagnostics/cswinrt3005.md): using an experimental Windows Runtime API
- [Authoring C#/WinRT components](authoring.md)
- [CsWinRT 3.0 overview](cswinrt3.0-spec.md)
2 changes: 2 additions & 0 deletions docs/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ C#/WinRT supports authoring out-of-process components that can be consumed by Wi

Here are some resources that demonstrate authoring C#/WinRT components and the details discussed in this document.

See also [attribute projections](attribute-projections.md), which covers the attributes CsWinRT translates between C# and Windows Runtime metadata (for instance, how to mark an authored API as deprecated or experimental).

1. [Simple C#/WinRT component sample](https://github.com/microsoft/CsWinRT/tree/master/src/Samples/AuthoringDemo) and associated [walkthrough on creating a C#/WinRT component and consuming it from C++/WinRT](https://docs.microsoft.com/windows/uwp/csharp-winrt/create-windows-runtime-component-cswinrt)

2. [Background Task component sample](https://github.com/microsoft/CsWinRT/tree/master/src/Samples/BgTaskComponent) demonstrating consuming an out-of-process C#/WinRT component from a packaged .NET WPF app
Expand Down
45 changes: 45 additions & 0 deletions docs/diagnostics/cswinrt3005.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# CsWinRT error CSWINRT3005

Windows Runtime APIs can be marked as experimental in their Windows Runtime metadata, with the `[Windows.Foundation.Metadata.Experimental]` attribute (`[experimental]` in MIDL). Such an API is published for evaluation purposes only: it can change shape or be removed entirely in any future Windows SDK, with no compatibility guarantee. CsWinRT projects that marker as `[System.Diagnostics.CodeAnalysis.Experimental]` with the `CSWINRT3005` diagnostic id, so the compiler reports every use site.

For instance, the following sample generates CSWINRT3005:

```csharp
using Windows.Graphics.Capture;

// CSWINRT3005: 'IDisplayGraphicsCaptureSession' is marked as experimental
void Capture(IDisplayGraphicsCaptureSession session)
{
}
```

## Additional resources

`CSWINRT3005` is reported when user code references a Windows Runtime API that CsWinRT projected with `[Experimental]`. Windows Runtime metadata has no per-API diagnostic id (the metadata attribute takes no arguments), so all experimental Windows Runtime APIs share this one id.

Following the [experimental attribute](https://learn.microsoft.com/dotnet/csharp/language-reference/proposals/csharp-12.0/experimental-attribute) design, this is reported as an **error** rather than a warning, so that depending on an experimental API is always a deliberate choice. It can be suppressed exactly like a warning, which is how the opt-in is expressed.

Generated projection code suppresses this diagnostic: a projection has to name an experimental type in order to project it at all, so the marker is guidance for the consumers of a projection rather than for the projection itself.

> **Note**: previous versions of CsWinRT relied on the C# compiler recognizing `Windows.Foundation.Metadata.ExperimentalAttribute` by name and reporting `CS8305`. That warning was not actionable per API: it could not be suppressed for one API without suppressing it for all of them, and it carried no link to any documentation. Suppressions of `CS8305` should be replaced with `CSWINRT3005`.

## Recommended action

- Prefer a stable API when one exists, and treat the experimental one as temporary.
- If you do want to depend on an experimental API, opt in explicitly and as narrowly as possible:

```csharp
#pragma warning disable CSWINRT3005
IDisplayGraphicsCaptureSession session = CreateSession();
#pragma warning restore CSWINRT3005
```

Or, to opt in for a whole project, add the id to `NoWarn`:

```xml
<PropertyGroup>
<NoWarn>$(NoWarn);CSWINRT3005</NoWarn>
</PropertyGroup>
```

- Be ready for the API to change: because it is experimental, an update to the Windows SDK your project targets can change its signature or remove it, and no servicing guarantee applies.
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ CSWINRT2016 | WindowsRuntime.SourceGenerator | Warning | Public authored type mi
CSWINRT2017 | WindowsRuntime.SourceGenerator | Warning | Public authored type mixing '[ContractVersion]' and '[Version]'
CSWINRT2018 | WindowsRuntime.SourceGenerator | Warning | '[WindowsRuntimeNativeExposedType]' target type cannot be instantiated
CSWINRT2019 | WindowsRuntime.SourceGenerator | Warning | '[WindowsRuntimeNativeExposedType]' target type is not a projected class
CSWINRT2020 | WindowsRuntime.SourceGenerator | Warning | Duplicate '[WindowsRuntimeNativeExposedType]' target type
CSWINRT2020 | WindowsRuntime.SourceGenerator | Warning | Duplicate '[WindowsRuntimeNativeExposedType]' target type
CSWINRT2021 | WindowsRuntime.SourceGenerator | Warning | Unsupported '[Experimental]' target for Windows Runtime metadata
Loading