Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
626dc84
[ComInterfaceGenerator] PoC: property support on [GeneratedComInterface]
AaronRobinsonMSFT May 29, 2026
cc4f4d6
Add e2e tests for properties on [GeneratedComInterface]
AaronRobinsonMSFT May 29, 2026
ded0e05
Support inherited property accessors in GeneratedComInterface
AaronRobinsonMSFT May 29, 2026
a52102b
Add property accessor marshalling-attribute tests
AaronRobinsonMSFT May 29, 2026
6288174
Extend [MarshalUsing] to properties on [GeneratedComInterface]
AaronRobinsonMSFT May 29, 2026
7c87817
Propagate property-level attributes to derived shadow accessors
AaronRobinsonMSFT May 30, 2026
5a27ca4
Add property modifier diagnostics, new-keyword shadow tests, and RCW …
AaronRobinsonMSFT May 30, 2026
639b105
Skip vtable emission for default-implemented members on [GeneratedCom…
AaronRobinsonMSFT May 30, 2026
c9fe1ee
Document GeneratedComInterface property support
AaronRobinsonMSFT May 30, 2026
8f71284
Support indexer properties on [GeneratedComInterface]
AaronRobinsonMSFT Jun 3, 2026
341b92f
Add native-shim tests for [GeneratedComInterface] indexers
AaronRobinsonMSFT Jun 3, 2026
9f33d76
Document indexer support on [GeneratedComInterface]
AaronRobinsonMSFT Jun 3, 2026
f334e6b
Route property-level attributes to accessor value surface only
AaronRobinsonMSFT Jun 3, 2026
3740ef2
Fix JSExportGenerator call site for renamed UnmanagedToManagedStubGen…
AaronRobinsonMSFT Jun 4, 2026
26bcce5
Merge branch 'main' into com_properties
AaronRobinsonMSFT Jun 5, 2026
39920dd
Merge branch 'main' into com_properties
AaronRobinsonMSFT Jun 9, 2026
121866e
Refactor attribute merging logic to prioritize accessor-level attribu…
AaronRobinsonMSFT Jun 9, 2026
ecb951f
Add tests for indexer accessors with CountElementName to document cur…
AaronRobinsonMSFT Jun 9, 2026
0348ddf
Merge remote-tracking branch 'upstream/main' into com_properties
AaronRobinsonMSFT Jun 9, 2026
253e0b2
Merge remote-tracking branch 'upstream/main' into com_properties
AaronRobinsonMSFT Jun 9, 2026
d33ef01
Add additional attributes to property and indexer accessor stubs in C…
AaronRobinsonMSFT Jun 9, 2026
7994559
Address review feedback on COM property generator
AaronRobinsonMSFT Jun 11, 2026
22b2450
Address additional review feedback on COM property generator
AaronRobinsonMSFT Jun 11, 2026
08ac762
Declare missing diagnostics in ComInterfaceGeneratorDiagnosticsAnalyz…
AaronRobinsonMSFT Jun 12, 2026
d415db6
Fix cross-assembly indexer dedup, IPropertyMarshalling fixture, DIM docs
AaronRobinsonMSFT Jun 12, 2026
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
22 changes: 22 additions & 0 deletions docs/design/libraries/ComInterfaceGenerator/Compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,25 @@ Source-generated COM will provide limited opt-in interop with `ComImport`-based
- Casting a "Com Object Wrapper" created using `StrategyBasedComWrappers` to a `ComImport`-based interface type.

This support is achieved through some internal interfaces and reflection-emit to shim a `DynamicInterfaceCastableImplementation` of a `ComImport` interface to use the built-in runtime interop marshalling support. The core of this experience is implemented by the `System.Runtime.InteropServices.Marshalling.ComImportInteropInterfaceDetailsStrategy` class.

## .NET 11

### Properties

A `[GeneratedComInterface]`-attributed interface may now declare ordinary C# properties (`T Name { get; set; }`, `{ get; }`, `{ set; }`). Each accessor maps to a vtable slot — getter first, then setter, in source order — matching the layout the built-in CLR produces for a `[ComVisible(true)]` managed interface. Inherited properties follow the same shadowing rules as inherited methods.

`[MarshalUsing]` may be applied directly to a property and is propagated to both accessor stubs. The `new` modifier may be used to shadow an inherited property. The `init` accessor is **not** supported on a `[GeneratedComInterface]` property — `init`-vs-`set` has no representation in the COM vtable, so the generator rejects it (`SYSLIB1091`).

### Indexers

C# indexers are supported alongside properties starting with .NET 11. Each accessor maps to its own vtable slot in source order, just like a property; the `[IndexerName]` attribute is honored and propagated through derived-interface shadows; and indexer overloads distinguished by index-parameter type are allowed. See the [Indexers](./Properties.md#indexers) section of Properties.md for the full design.

### Default-implemented members (DIM)

A method or property accessor with a user-supplied body on a `[GeneratedComInterface]` interface is now treated as a default-implemented member: it ships as managed-only sugar and is **not** assigned a vtable slot. This is the supported way to wrap a pair of ABI methods with a managed property abstraction, or to add helper methods that the wire ABI does not need to know about.

A property must have all accessors abstract or all accessors bodied — mixing the two is an error (`SYSLIB1091`). `[MarshalUsing]` and `[MarshalAs]` on a default-implemented member emit a warning (`SYSLIB1091`) because they have no ABI effect.

### `new` modifier on members

In earlier releases, `[GeneratedComInterface]` disallowed declaring any methods with the `new` modifier. This is no longer the case. Both methods and properties may now be declared with `new` to explicitly shadow an inherited base member; the shadowing member receives a fresh vtable slot appended after the base interface's slots, and the base member's slot continues to dispatch to its original target.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Default-implemented members (DIM) on `[GeneratedComInterface]`

A method, property accessor, or indexer accessor on a `[GeneratedComInterface]` interface that carries a user-supplied body is treated as a **default-implemented member** (DIM): it is pure managed sugar that ships on the interface, **and it is not assigned a vtable slot**.

```csharp
[GeneratedComInterface, Guid("…")]
public partial interface IFoo
{
// Two vtable slots — ABI methods.
double ReadValue();
void WriteValue(double value);

// Zero vtable slots — managed sugar wrapping the two ABI methods above.
double Value
{
get => ReadValue();
set => WriteValue(value);
}

// Also zero vtable slots — a managed-only helper.
double DoubleIt() => ReadValue() * 2;
}
```

DIM is the user-facing escape hatch for scenarios the canonical "method → one vtable slot" / "property → one or two adjacent vtable slots" rules cannot express, including:

* Wrapping ABI methods whose names do not match the canonical `get_X` / `set_X` accessor naming.
* Wrapping ABI methods that live on different vtable slots than two adjacent slots would imply.
* Adding helper methods that the wire ABI does not need to know about.

## Rules and diagnostics

* **All accessors must agree.** A property must have either all-abstract accessors (`int X { get; set; }`) or all-bodied accessors (`int X { get => …; set => …; }`). Mixing the two emits the error `SYSLIB1091` `PropertyAccessorsMustBeAllOrNothing`. (The C# compiler also rejects bare `get;` paired with a bodied `set { … }` with `CS0525`, because it interprets `get;` as an auto-property accessor and disallows auto-properties on interfaces.)

* **`[MarshalUsing]` and `[MarshalAs]` on a DIM are a warning.** A DIM never participates in marshalling, so any marshal attribute on the DIM (the property itself, an accessor return, a method parameter, etc.) emits a `SYSLIB1091` warning `MarshalAttributeOnDefaultImplementedComInterfaceMember`. The DIM is still generated; the marshal attribute is silently ignored at the ABI layer.

* **Inherited DIMs are skipped from base-class CCW dispatch.** When the generator emits CCW dispatch for an inherited interface, accessor methods that are not `IsAbstract` (i.e. inherited DIMs) are excluded — the runtime resolves them through ordinary virtual dispatch on the managed object.

## The `get_X` / `set_X` name reservation constraint

A natural-looking DIM pattern is to wrap a pair of ABI methods named `get_X` / `set_X` with a property `X`:

```csharp
[GeneratedComInterface, Guid("…")]
public partial interface IFoo
{
double get_Value(); // ABI method
void set_Value(double value); // ABI method
double Value { get => get_Value(); set => set_Value(value); } // DIM wrapping the ABI methods
}
```

**This does not compile.** Whenever a C# interface declares a property `Value`, the language reserves the IL names `get_Value` and `set_Value` for the property's accessors. Declaring an explicit `double get_Value()` method on the same interface fails with `CS0082` ("type already reserves a member called 'get_Value'") and related diagnostics.

The workaround is to give the ABI methods names that do not collide with the property's accessor names, and have the DIM wrap them by call rather than by name:

```csharp
[GeneratedComInterface, Guid("…")]
public partial interface IFoo
{
double ReadValue(); // ABI method
void WriteValue(double value); // ABI method
double Value { get => ReadValue(); set => WriteValue(value); } // DIM
}
```

The same constraint applies to derived-interface shadowing: a derived interface cannot declare a DIM property `X` that shadows an inherited pair of ABI methods named `get_X` / `set_X`. ABI methods and properties with matching names cannot coexist on the same C# interface chain.

## References

* [Properties.md](./Properties.md) — Property and indexer surface that consumes the DIM contract for non-ABI accessors.
* [DerivedComInterfaces.md](./DerivedComInterfaces.md) — Inheritance and shadowing rules; explains why inherited DIMs are skipped from base-class CCW dispatch.
* [Compatibility.md](./Compatibility.md) — Rolling per-release semantic compatibility notes.
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ obj.Method();

The `[ComImport]` code will not call `QueryInterface` for `IComInterface`, but the `[GeneratedComInterface]` model will. The `[ComImport]` pattern will not need to call `QueryInterface`, as the required shadowing method declarations means that the `.Method()` call resolves to `IComInterface2.Method`, whereas the `[GeneratedComInterface]`-based code resolves that call to `IComInterface.Method`. Since the `[GeneratedComInterface]` mechanism doesn't shadow the method, the runtime will try to cast `obj` to `IComInterface`, which will result in a `QueryInterface` call.

To reduce the number of `QueryInterface` calls, the ComInterfaceGenerator will automatically emit shadowing method declarations and corresponding method implementations for all methods from any `[GeneratedComInterface]`-attributed base type and its attributed base types recursively that are visible. This way, we can ensure that the least number of `QueryInterface` calls are required when using the `[GeneratedComInterface]`-based COM interop. Additionally, we will disallow declaring any methods with the `new` modifier on a `[GeneratedComInterface]`-attributed interface to ensure that the user does not try to shadow any base interface members.
To reduce the number of `QueryInterface` calls, the ComInterfaceGenerator will automatically emit shadowing method declarations and corresponding method implementations for all methods (and, since .NET 11, properties and indexers) from any `[GeneratedComInterface]`-attributed base type and its attributed base types recursively that are visible. This way, we can ensure that the least number of `QueryInterface` calls are required when using the `[GeneratedComInterface]`-based COM interop.

Users may also explicitly shadow an inherited member by declaring it with the `new` modifier. An explicitly-shadowed member receives a fresh vtable slot appended after the base interface's slots; the base interface's slot for the original member continues to dispatch to its original target. This applies symmetrically to methods, properties, and indexers (see [Properties.md](./Properties.md) for the property and indexer specific details).

What about when the marshallers used in a base interface method declaration are not accessible by the derived type? We can try to detect this case, but it makes it very fragile to determine which methods are shadowed and which are not. Additionally, removing a shadowing method is a binary breaking change. We have a few options:

Expand Down
Loading
Loading