diff --git a/docs/design/libraries/ComInterfaceGenerator/Compatibility.md b/docs/design/libraries/ComInterfaceGenerator/Compatibility.md index 8fbc707bc58f17..0350a9b8670c47 100644 --- a/docs/design/libraries/ComInterfaceGenerator/Compatibility.md +++ b/docs/design/libraries/ComInterfaceGenerator/Compatibility.md @@ -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. diff --git a/docs/design/libraries/ComInterfaceGenerator/DefaultImplementedMembers.md b/docs/design/libraries/ComInterfaceGenerator/DefaultImplementedMembers.md new file mode 100644 index 00000000000000..0f84d2013f5987 --- /dev/null +++ b/docs/design/libraries/ComInterfaceGenerator/DefaultImplementedMembers.md @@ -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 code generator does not generate code for the DIM, so the presence of a marshal attribute is misleading. + +* **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. diff --git a/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md b/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md index 83a6d7eb65b8d5..7a130367b46745 100644 --- a/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md +++ b/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md @@ -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: diff --git a/docs/design/libraries/ComInterfaceGenerator/Properties.md b/docs/design/libraries/ComInterfaceGenerator/Properties.md new file mode 100644 index 00000000000000..3a82a556db549d --- /dev/null +++ b/docs/design/libraries/ComInterfaceGenerator/Properties.md @@ -0,0 +1,143 @@ +# Properties and indexers on `[GeneratedComInterface]` + +The ComInterfaceGenerator allows COM interfaces declared with `[GeneratedComInterface]` to expose ordinary C# properties and indexers in addition to methods. This document describes the supported surface, the ABI shape produced, and the design constraints that shape both. Indexers reuse the property pipeline almost entirely; their incremental rules are collected in the [Indexers](#indexers) section. + +## Goals + +* Let users declare COM interface members using natural C# property and indexer syntax instead of hand-rolled `get_X` / `set_X` method pairs. +* Match the vtable layout that the built-in COM CCW produces for `[ComVisible(true)]` managed interfaces, so source-generated and built-in COM remain wire-compatible for the common shapes. +* Keep the implementation a layer on top of the existing per-method ABI pipeline; an accessor is, fundamentally, just an `IMethodSymbol` that happens to back a property declaration. +* Provide a mechanism (default-implemented members; see [DefaultImplementedMembers.md](./DefaultImplementedMembers.md)) for users to declare properties or methods that are pure managed sugar and are *not* assigned vtable slots, so that a property can wrap a pair of unrelated or non-adjacent ABI methods. + +## Non-goals + +* Distinguishing `propput` from `propputref` at the vtable level. The `propputref` concept exists in TLB metadata and in `IDispatch::Invoke`; it has no representation in an `IUnknown`-only vtable. All setters map to a single vtable slot. +* `IDispatch` dispid integration. Properties on a `[GeneratedComInterface]` are exposed through the `IUnknown` vtable only. + +## Vtable layout + +For a property declared in source order *k* on the interface, the generator emits one or two vtable slots immediately following the slots reserved for any preceding members: + +* `T Foo { get; set; }` → **two consecutive slots**: the getter slot first, then the setter slot. +* `T Foo { get; }` → one slot for the getter. +* `T Foo { set; }` → one slot for the setter. + +The getter slot has the signature `HRESULT get_Foo(out T value)`. The setter slot has the signature `HRESULT set_Foo(T value)`. The accessor stubs are produced by the same per-method ABI pipeline that produces method stubs, so the same marshalling rules and the same `PreserveSig`-style HRESULT translation apply to both methods and accessors. + +The slot ordering (get before set, both in source-declaration order) matches the layout the built-in CLR produces for a `[ComVisible(true)]` managed interface that declares the equivalent property. A read-only or write-only property produces a single slot, again matching the built-in layout. + +### Inheritance and vtable layout + +Inherited properties follow the same rules as inherited methods (see [DerivedComInterfaces.md](./DerivedComInterfaces.md)): a `[GeneratedComInterface]` that derives from another `[GeneratedComInterface]` inherits all base accessor slots at their original indices and appends its own accessor slots after them. + +The derived interface also receives generator-emitted user-facing shadow declarations for every inherited property accessor, so that a `T value = derived.BaseProperty;` call does not require a `QueryInterface` to the base interface type. + +## Supported property surface + +The supported surface is intentionally narrow. Anything outside this list is rejected with `SYSLIB1091` ("member will not be source generated") so that supported semantics can be expanded over time without breaking existing users. + +**Allowed:** + +* Auto-property accessors with no body, in any of the three accessor combinations: + + ```csharp + [GeneratedComInterface, Guid("…")] + public partial interface IFoo + { + int Count { get; set; } // two vtable slots + string Name { get; } // one vtable slot + bool Verbose { set; } // one vtable slot + } + ``` + +* Property-level accessibility modifiers (`public`, `internal`, `private`, `protected`). These exist on the C# surface only; the ABI shape is unchanged: + + ```csharp + internal int Count { get; set; } // still two vtable slots + ``` + + (Accessor-level accessibility modifiers such as `int Count { get; private set; }` are rejected by the C# language itself with **CS0442** when the accessors are abstract, so they never reach the generator. To narrow an accessor's visibility you must give it a body, which puts the property on the default-implementation path below.) + +* The `unsafe` modifier on the property declaration. Generated accessor stubs are already emitted inside an `unsafe` partial interface, so this is essentially free; it lets the property's value type be a pointer. + +* The `new` modifier on the property declaration for explicit shadowing of an inherited COM property. The base accessor slots remain at their original indices and the derived interface appends fresh slots for the shadowing accessors, matching the behavior of `new`-keyword method shadowing. + + ```csharp + [GeneratedComInterface, Guid("…")] public partial interface IBase { int Value { get; set; } } + [GeneratedComInterface, Guid("…")] public partial interface IFoo : IBase + { + new int Value { get; set; } // appends two fresh slots; IBase.Value slots remain + } + ``` + +* Default-implemented properties (properties with accessor bodies). See [DefaultImplementedMembers.md](./DefaultImplementedMembers.md). + +**Disallowed (reported as `SYSLIB1091`):** + +* `extern` and `required` modifiers on the property. +* The `init` accessor. `init`-vs-`set` is a C#-call-site distinction with no representation in the COM vtable; declaring an `init` accessor on a `[GeneratedComInterface]` property is rejected regardless of whether the accessor has a body. Use `set` if the accessor should participate in the vtable, or use a default-implemented `init`-style helper through a separate managed-only abstraction. +* Mixed accessor shapes where one accessor has a body and the other does not (e.g. `int Mixed { get; set { … } }`). +* Property-level marshalling attributes other than `[MarshalUsing]`. `[MarshalAs]` is not allowed on a property. + +Property modifiers `virtual`, `abstract`, and `sealed` are not currently supported on `[GeneratedComInterface]` properties; the modifier story for properties is stricter than for methods. (`abstract` is the interface default and need not be written; `virtual` on an interface property requires a body, which would put it on the [DIM](./DefaultImplementedMembers.md) path but isn't currently accepted; `sealed` is rejected outright.) + +## Marshalling attributes on properties + +`[MarshalUsing]` may be applied directly to a property: + +```csharp +[GeneratedComInterface, Guid("…")] +public partial interface IFoo +{ + [MarshalUsing(typeof(MyMarshaller))] + MyType Item { get; set; } +} +``` + +Property-level marshalling info is propagated to **both** the getter (as return-value info) and the setter (as parameter info) when the generator builds the per-accessor stub. The property-level form is the recommended style because it expresses "marshal this property" in one place. + +The legacy per-accessor form using `[return: MarshalUsing(...)]` on the getter and `[param: MarshalUsing(...)]` on the setter remains supported. When both forms are present, the per-accessor form wins. + +`[MarshalAs]` is **not** valid on a property declaration; the BCL's `[AttributeUsage]` on `MarshalAsAttribute` does not include `AttributeTargets.Property`, so C# itself rejects the placement with **CS0592** before the generator runs. Apply it to individual accessor return values / parameters if needed. + +The shadow members the generator emits in derived interfaces strip `[MarshalUsing]` and `[MarshalAs]` from their copy of the property attributes; the shadow forwards the call to the base accessor by managed invocation, so re-running marshalling info on it would be redundant. + +## Indexers + +C# indexers (`T this[I0 i0, …, In in] { get; set; }`) are supported using the same per-accessor pipeline as ordinary properties. Each accessor becomes its own vtable slot, the indexer's getter slot precedes its setter slot, and indexer slots are appended in source-declaration order alongside any other members. + +**Slot signatures.** With a default `[IndexerName("Item")]`, the getter slot has the signature `HRESULT get_Item(I0 i0, …, In in, out T value)` and the setter slot has the signature `HRESULT set_Item(I0 i0, …, In in, T value)`. The index parameters precede the trailing value parameter on the setter, matching the order the C# language and the built-in CLR use when surfacing an indexer through COM. + +**Read-only and write-only.** `this[I i] { get; }` produces one getter slot; `this[I i] { set; }` produces one setter slot — the same as properties. + +**Overloading.** A `[GeneratedComInterface]` may declare multiple indexer overloads distinguished by index-parameter type; each overload's accessors get their own consecutive slot pair, in source order. The C# language requires that all indexers on the same type share a single effective `[IndexerName]` (the compiler enforces this with **CS0668**), so the IL method names on the slots are identical across overloads and overload disambiguation is left to the parameter list: + +```csharp +[GeneratedComInterface, Guid("…")] +public partial interface IFoo +{ + int this[int i] { get; set; } // slots 3, 4: get_Item(int, out int) / set_Item(int, int) + int this[int i, int j] { get; set; } // slots 5, 6: get_Item(int, int, out int) / set_Item(int, int, int) + int this[long l] { get; } // slot 7: get_Item(long, out int) + int this[short s] { set; } // slot 8: set_Item(short, int) +} +``` + +**`[IndexerName]`.** The `[IndexerName(...)]` attribute on an indexer renames the IL accessor methods (e.g. `get_Element` / `set_Element` instead of `get_Item` / `set_Item`). The generator honors this on both ABI slot generation and the auto-emitted forwarding shadows it places on derived interfaces. A derived `[GeneratedComInterface]` that re-declares an inherited indexer with the `new` modifier **must** repeat the base interface's `[IndexerName]` value (or, if the base uses the default, omit the attribute on the derived); the two values cannot diverge. + +The reason is that the generator emits the inherited indexer onto the derived interface as an explicit-interface implementation (`int IBase.this[…] => throw new UnreachableException();`), which counts as an indexer member on the derived type for CS0668's "all indexers on one type share one `[IndexerName]`" rule. If the user-declared `new` shadow's `[IndexerName]` disagrees with the base's, the C# compiler rejects the derived interface with **CS0668**, **CS0111**, and related diagnostics. Splitting a single interface into multiple `[IndexerName]`-distinguished shapes therefore requires declaring them on separate, unrelated interfaces. + +**Supported modifiers and marshalling.** The lists of allowed and disallowed modifiers under [Supported property surface](#supported-property-surface) apply unchanged to indexers, replacing `int Foo { … }` with `int this[I i] { … }`. `[MarshalUsing]` on the indexer is propagated to both accessor stubs as parameter info on the value parameter and as return-value info on the getter, in the same way it is for properties. + +**Default-implemented indexers.** As with properties, an indexer whose accessors all carry bodies is treated as a [default-implemented member](./DefaultImplementedMembers.md) and is not assigned a vtable slot. Mixed accessor bodies (`int this[int i] { get; set { … } }`) are rejected by the C# language itself with **CS0501** rather than by the generator — the equivalent property shape triggers **CS0525** instead, but either way the user-facing diagnostic surfaces before generator analysis runs. + +## Default-implemented members (DIM) + +Property accessors, indexer accessors, and methods on `[GeneratedComInterface]` may carry user-supplied bodies; the generator treats those as managed-only sugar that does **not** receive a vtable slot. The full contract — rules, diagnostics, the `get_X` / `set_X` name reservation pitfall — lives in [DefaultImplementedMembers.md](./DefaultImplementedMembers.md). + +## References + +* [Compatibility.md](./Compatibility.md) — Rolling per-release semantic compatibility notes. +* [DefaultImplementedMembers.md](./DefaultImplementedMembers.md) — DIM contract for properties, indexers, and methods on a `[GeneratedComInterface]`. +* [DerivedComInterfaces.md](./DerivedComInterfaces.md) — Inheritance and shadowing rules that property declarations inherit from. +* [VTableStubs.md](./VTableStubs.md) — The underlying `VirtualMethodIndexAttribute` building block that ABI accessor stubs ultimately consume. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/MarshalUsingAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/MarshalUsingAttribute.cs index c6f159fa1a6670..eca86502531411 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/MarshalUsingAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/MarshalUsingAttribute.cs @@ -12,7 +12,7 @@ namespace System.Runtime.InteropServices.Marshalling /// /// /// - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true)] + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true)] public sealed class MarshalUsingAttribute : Attribute { /// diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs index 797d8c22e47147..bfcd255a5ff04d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/gen/JSImportGenerator/JSExportGenerator.cs @@ -400,7 +400,7 @@ private static LocalFunctionStatementSyntax GenerateInnerLocalFunction(Increment return LocalFunctionStatement( returnType, innerFunctionName) - .WithBody(stubGenerator.GenerateStubBody(IdentifierName(TypeNames.GlobalAlias + context.SignatureContext.MethodName))) + .WithBody(stubGenerator.GenerateStubBodyForMethod(IdentifierName(TypeNames.GlobalAlias + context.SignatureContext.MethodName))) .WithParameterList(parameters) .WithAttributeLists(SingletonList(AttributeList(SingletonSeparatedList( Attribute(IdentifierName(Constants.DebuggerNonUserCodeAttribute)))))); diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Analyzers/ComInterfaceGeneratorDiagnosticsAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Analyzers/ComInterfaceGeneratorDiagnosticsAnalyzer.cs index ba2f234da4da42..6c64623666e844 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Analyzers/ComInterfaceGeneratorDiagnosticsAnalyzer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Analyzers/ComInterfaceGeneratorDiagnosticsAnalyzer.cs @@ -36,8 +36,10 @@ public class ComInterfaceGeneratorDiagnosticsAnalyzer : DiagnosticAnalyzer GeneratorDiagnostics.BaseInterfaceDefinedInOtherAssembly, // Method-level diagnostics GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, - GeneratorDiagnostics.InstancePropertyDeclaredInInterface, GeneratorDiagnostics.InstanceEventDeclaredInInterface, + GeneratorDiagnostics.InvalidAttributedMethodSignature, + GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface, + GeneratorDiagnostics.PropertyAccessorsMustBeAllOrNothing, GeneratorDiagnostics.CannotAnalyzeMethodPattern, GeneratorDiagnostics.CannotAnalyzeInterfacePattern, // Stub-level diagnostics @@ -60,6 +62,8 @@ public class ComInterfaceGeneratorDiagnosticsAnalyzer : DiagnosticAnalyzer GeneratorDiagnostics.SizeOfInCollectionMustBeDefinedAtCallOutParam, GeneratorDiagnostics.SizeOfInCollectionMustBeDefinedAtCallReturnValue, GeneratorDiagnostics.InvalidExceptionMarshallingConfiguration, + GeneratorDiagnostics.MarshalUsingOnPropertyAccessorMustSpecifyType, + GeneratorDiagnostics.MarshalAttributeOnDefaultImplementedComInterfaceMember, GeneratorDiagnostics.GeneratedComInterfaceUsageDoesNotFollowBestPractices); public override void Initialize(AnalysisContext context) diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs index 3b110111667f92..3c209670718ef4 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs @@ -5,6 +5,7 @@ using System.CodeDom.Compiler; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -341,14 +342,29 @@ private static IncrementalMethodStubGenerationContext CalculateSharedStubInforma owningInterfaceInfo.Type, declaringType, generatorDiagnostics.Diagnostics.ToSequenceEqualImmutableArray(), - ComInterfaceDispatchMarshallingInfo.Instance); + ComInterfaceDispatchMarshallingInfo.Instance, + ClassifyMemberKind(symbol)); } - internal static IncrementalMethodStubGenerationContext CalculateStubInformation(MethodDeclarationSyntax? syntax, IMethodSymbol symbol, int index, StubEnvironment environment, ComInterfaceInfo owningInterface, CancellationToken ct) + private static StubMemberKind ClassifyMemberKind(IMethodSymbol symbol) => (symbol.MethodKind, symbol.AssociatedSymbol) switch { - ISignatureDiagnosticLocations locations = syntax is null - ? NoneSignatureDiagnosticLocations.Instance - : new MethodSignatureDiagnosticLocations(syntax); + (MethodKind.PropertyGet, IPropertySymbol { IsIndexer: true }) => StubMemberKind.IndexerGetter, + (MethodKind.PropertySet, IPropertySymbol { IsIndexer: true }) => StubMemberKind.IndexerSetter, + (MethodKind.PropertyGet, _) => StubMemberKind.PropertyGetter, + (MethodKind.PropertySet, _) => StubMemberKind.PropertySetter, + _ => StubMemberKind.Method, + }; + + internal static IncrementalMethodStubGenerationContext CalculateStubInformation(MemberDeclarationSyntax? syntax, IMethodSymbol symbol, int index, StubEnvironment environment, ComInterfaceInfo owningInterface, CancellationToken ct) + { + ISignatureDiagnosticLocations locations = syntax switch + { + null => NoneSignatureDiagnosticLocations.Instance, + MethodDeclarationSyntax methodSyntax => new MethodSignatureDiagnosticLocations(methodSyntax), + PropertyDeclarationSyntax propertySyntax => CreatePropertyAccessorDiagnosticLocations(propertySyntax, symbol), + IndexerDeclarationSyntax indexerSyntax => CreateIndexerAccessorDiagnosticLocations(indexerSyntax, symbol), + _ => throw new UnreachableException(), + }; var sourcelessStubInformation = CalculateSharedStubInformation( symbol, @@ -362,11 +378,24 @@ internal static IncrementalMethodStubGenerationContext CalculateStubInformation( return sourcelessStubInformation; var containingSyntaxContext = new ContainingSyntaxContext(syntax); - var methodSyntaxTemplate = new ContainingSyntax( - new SyntaxTokenList(syntax.Modifiers.Where(static m => !m.IsKind(SyntaxKind.NewKeyword) && !m.IsKind(SyntaxKind.PartialKeyword) && !m.IsKind(SyntaxKind.VirtualKeyword))).StripAccessibilityModifiers(), - SyntaxKind.MethodDeclaration, - syntax.Identifier, - syntax.TypeParameterList); + ContainingSyntax methodSyntaxTemplate = syntax switch + { + MethodDeclarationSyntax methodSyntax => new ContainingSyntax( + new SyntaxTokenList(methodSyntax.Modifiers.Where(static m => !m.IsKind(SyntaxKind.NewKeyword) && !m.IsKind(SyntaxKind.PartialKeyword) && !m.IsKind(SyntaxKind.VirtualKeyword))).StripAccessibilityModifiers(), + SyntaxKind.MethodDeclaration, + methodSyntax.Identifier, + methodSyntax.TypeParameterList), + // Property / indexer accessors are emitted as plain methods named e.g. 'get_Foo' / 'set_Foo' + // ('get_Item' / 'set_Item' for indexers, or the [IndexerName]-renamed value). + PropertyDeclarationSyntax or IndexerDeclarationSyntax => new ContainingSyntax( + TokenList(), + SyntaxKind.MethodDeclaration, + Identifier(symbol.Name), + typeParameters: null), + _ => throw new UnreachableException(), + }; + + StubMemberKind memberKind = ClassifyMemberKind(symbol); return new SourceAvailableIncrementalMethodStubGenerationContext( sourcelessStubInformation.SignatureContext, @@ -380,7 +409,43 @@ internal static IncrementalMethodStubGenerationContext CalculateStubInformation( sourcelessStubInformation.TypeKeyOwner, sourcelessStubInformation.DeclaringType, sourcelessStubInformation.Diagnostics, - ComInterfaceDispatchMarshallingInfo.Instance); + ComInterfaceDispatchMarshallingInfo.Instance, + memberKind); + } + + // For a property accessor, the user-visible source location is the property's identifier. + // The getter has no managed parameters; the setter has the implicit 'value' parameter which we report at + // the property identifier (it has no source location of its own). + private static MethodSignatureDiagnosticLocations CreatePropertyAccessorDiagnosticLocations(PropertyDeclarationSyntax propertySyntax, IMethodSymbol accessor) + { + Location identifierLocation = propertySyntax.Identifier.GetLocation(); + ImmutableArray parameterLocations = accessor.MethodKind is MethodKind.PropertySet + ? ImmutableArray.Create(identifierLocation) + : ImmutableArray.Empty; + return new MethodSignatureDiagnosticLocations(accessor.Name, parameterLocations, identifierLocation); + } + + // For an indexer accessor, the user-visible source location is the 'this' keyword (indexers have no + // identifier token). Diagnostics that index into ManagedParameterLocations must see one entry per + // index parameter, plus the implicit 'value' parameter for the setter, with 'value' falling back to + // the 'this' location since it has no syntactic representation. + private static MethodSignatureDiagnosticLocations CreateIndexerAccessorDiagnosticLocations(IndexerDeclarationSyntax indexerSyntax, IMethodSymbol accessor) + { + Location thisLocation = indexerSyntax.ThisKeyword.GetLocation(); + var indexParameters = indexerSyntax.ParameterList.Parameters; + int parameterCount = accessor.MethodKind is MethodKind.PropertySet + ? indexParameters.Count + 1 + : indexParameters.Count; + var builder = ImmutableArray.CreateBuilder(parameterCount); + foreach (var parameter in indexParameters) + { + builder.Add(parameter.GetLocation()); + } + if (accessor.MethodKind is MethodKind.PropertySet) + { + builder.Add(thisLocation); + } + return new MethodSignatureDiagnosticLocations(accessor.Name, builder.MoveToImmutable(), thisLocation); } private static MarshalDirection GetDirectionFromOptions(ComInterfaceOptions options) @@ -561,12 +626,12 @@ private static void WriteInterfaceImplementation(IndentedTextWriter writer, ComI writer.WriteLine('}'); } + BasePropertyDeclarationSyntax? bufferedDeclaredGetter = null; foreach (ComMethodContext declaredMethod in data.DeclaredMethods) { if (declaredMethod.ManagedToUnmanagedStub is GeneratedStubCodeContext managedToUnmanagedContext) { - writer.InnerWriter.WriteLine(); - writer.WriteMultilineNode(managedToUnmanagedContext.Stub.Node.NormalizeWhitespace()); + EmitMemberHonoringPropertyMerge(writer, managedToUnmanagedContext.Stub.Node, ref bufferedDeclaredGetter); } if (declaredMethod.UnmanagedToManagedStub is GeneratedStubCodeContext unmanagedToManagedContext && @@ -576,7 +641,10 @@ private static void WriteInterfaceImplementation(IndentedTextWriter writer, ComI writer.WriteMultilineNode(unmanagedToManagedContext.Stub.Node.NormalizeWhitespace()); } } + FlushBufferedPropertyGetter(writer, ref bufferedDeclaredGetter); + BasePropertyDeclarationSyntax? bufferedShadowGetter = null; + string derivedInterfaceName = data.Interface.Info.Type.FullTypeName; foreach (ComMethodContext inheritedStub in data.InheritedMethods) { if (inheritedStub is not { IsExternallyDefined: false, ManagedToUnmanagedStub: GeneratedStubCodeContext shadowImplementationContextContext }) @@ -584,15 +652,30 @@ private static void WriteInterfaceImplementation(IndentedTextWriter writer, ComI continue; } - MethodDeclarationSyntax preparedNode = shadowImplementationContextContext.Stub.Node - .WithExplicitInterfaceSpecifier( - ExplicitInterfaceSpecifier(ParseName(data.Interface.Info.Type.FullTypeName))) - .NormalizeWhitespace(); - - writer.InnerWriter.WriteLine(); - writer.WriteMultilineNode(preparedNode); + MemberDeclarationSyntax stubNode = shadowImplementationContextContext.Stub.Node; + if (stubNode is BasePropertyDeclarationSyntax basePropertyNode) + { + // The accessor stub was generated for the base interface; rewrite its explicit-interface + // specifier to point at the derived interface before emitting/merging. Both property and + // indexer declarations expose a WithExplicitInterfaceSpecifier on the base type. + basePropertyNode = basePropertyNode.WithExplicitInterfaceSpecifier( + ExplicitInterfaceSpecifier(ParseName(derivedInterfaceName))); + EmitMemberHonoringPropertyMerge(writer, basePropertyNode, ref bufferedShadowGetter); + } + else if (stubNode is MethodDeclarationSyntax methodNode) + { + FlushBufferedPropertyGetter(writer, ref bufferedShadowGetter); + MethodDeclarationSyntax preparedNode = methodNode + .WithExplicitInterfaceSpecifier( + ExplicitInterfaceSpecifier(ParseName(derivedInterfaceName))) + .NormalizeWhitespace(); + writer.InnerWriter.WriteLine(); + writer.WriteMultilineNode(preparedNode); + } } + FlushBufferedPropertyGetter(writer, ref bufferedShadowGetter); + BasePropertyDeclarationSyntax? bufferedUnreachableGetter = null; foreach (ComMethodContext inheritedStub in data.InheritedMethods) { if (inheritedStub.IsExternallyDefined) @@ -600,16 +683,206 @@ private static void WriteInterfaceImplementation(IndentedTextWriter writer, ComI continue; } + if (inheritedStub.GenerationContext is { MemberKind: var kind } && kind.IsPropertyOrIndexerAccessor()) + { + // Property/indexer accessors must be emitted as one explicit-interface declaration per + // get/set pair. Synthesize a single-accessor declaration here and let the merge helper + // collapse a getter+setter pair into one declaration. + BasePropertyDeclarationSyntax synthesized = SynthesizeUnreachableInheritedPropertyAccessor(inheritedStub); + EmitMemberHonoringPropertyMerge(writer, synthesized, ref bufferedUnreachableGetter); + continue; + } + + FlushBufferedPropertyGetter(writer, ref bufferedUnreachableGetter); writer.InnerWriter.WriteLine(); writer.Write($"{inheritedStub.GenerationContext.SignatureContext.StubReturnType} {inheritedStub.OriginalDeclaringInterface.Info.Type.FullTypeName}.{inheritedStub.MethodInfo.MethodName}"); writer.Write($"({string.Join(", ", inheritedStub.GenerationContext.SignatureContext.StubParameters.Select(p => p.NormalizeWhitespace().ToString()))})"); writer.WriteLine(" => throw new global::System.Diagnostics.UnreachableException();"); } + FlushBufferedPropertyGetter(writer, ref bufferedUnreachableGetter); writer.Indent--; writer.WriteLine('}'); } + private static void EmitMemberHonoringPropertyMerge( + IndentedTextWriter writer, + MemberDeclarationSyntax node, + ref BasePropertyDeclarationSyntax? bufferedGetter) + { + // Property and indexer accessor stubs arrive one per accessor (get then set when both exist). + // Merge consecutive get+set halves of the same property/indexer into a single declaration + // before emitting, so the resulting code is a valid explicit interface implementation. + // + // Three cases below: + // (1) Incoming node is a getter accessor — flush any prior buffered getter + // (an orphan with no matching setter) and stash this one to wait for a paired setter. + // (2) Incoming node is a setter accessor that pairs with the buffered getter (same + // target property/indexer) — merge them into a single declaration and emit. + // (3) Anything else (orphan setter, setter targeting a different property/indexer than + // the buffered getter, or a non-property syntax node) — flush any buffered getter + // and emit the incoming node as-is. + if (node is BasePropertyDeclarationSyntax basePropertyDecl) + { + bool isGetter = basePropertyDecl.AccessorList!.Accessors[0].Kind() is SyntaxKind.GetAccessorDeclaration; + if (isGetter) + { + // Case (1): buffer the getter; wait for a possible paired setter. + FlushBufferedPropertyGetter(writer, ref bufferedGetter); + bufferedGetter = basePropertyDecl; + return; + } + if (bufferedGetter is not null + && IsSameAccessorTarget(bufferedGetter, basePropertyDecl)) + { + // Case (2): setter pairs with the buffered getter — merge and emit one declaration. + BasePropertyDeclarationSyntax merged = MergePropertyAccessors(bufferedGetter, basePropertyDecl); + writer.InnerWriter.WriteLine(); + writer.WriteMultilineNode(merged.NormalizeWhitespace()); + bufferedGetter = null; + return; + } + } + // Case (3): flush any buffered getter and emit the incoming node as-is. + FlushBufferedPropertyGetter(writer, ref bufferedGetter); + writer.InnerWriter.WriteLine(); + writer.WriteMultilineNode(node.NormalizeWhitespace()); + } + + // The buffer holds either a property getter or an indexer getter. Two consecutive accessor stubs + // merge into one declaration iff they target the SAME underlying property/indexer: + // - same explicit-interface specifier (or both unqualified), + // - same syntactic shape (both PropertyDeclaration or both IndexerDeclaration), + // - for properties: same identifier text (the property name). + // - for indexers: same index-parameter type signature (overloads must NOT cross-pair). + private static bool IsSameAccessorTarget(BasePropertyDeclarationSyntax getter, BasePropertyDeclarationSyntax setter) + { + string getterExplicit = getter.ExplicitInterfaceSpecifier?.Name.ToString() ?? string.Empty; + string setterExplicit = setter.ExplicitInterfaceSpecifier?.Name.ToString() ?? string.Empty; + if (getterExplicit != setterExplicit) + { + return false; + } + return (getter, setter) switch + { + (PropertyDeclarationSyntax g, PropertyDeclarationSyntax s) => g.Identifier.Text == s.Identifier.Text, + (IndexerDeclarationSyntax g, IndexerDeclarationSyntax s) => HaveSameParameterSignatures(g.ParameterList, s.ParameterList), + _ => false, + }; + } + + // Indexer overloads are distinguished not just by parameter types but also by parameter + // modifiers (`ref`, `in`, `out`, `ref readonly`, ...). Two overloads that differ only by + // modifier occupy separate vtable slots, so a getter from one and a setter from the other + // must not be merged into a single indexer declaration in the generated source. + private static bool HaveSameParameterSignatures(BaseParameterListSyntax a, BaseParameterListSyntax b) + { + var aParams = a.Parameters; + var bParams = b.Parameters; + if (aParams.Count != bParams.Count) + { + return false; + } + for (int i = 0; i < aParams.Count; i++) + { + if (aParams[i].Type!.NormalizeWhitespace().ToString() != bParams[i].Type!.NormalizeWhitespace().ToString()) + { + return false; + } + if (!ParameterModifiersEqual(aParams[i].Modifiers, bParams[i].Modifiers)) + { + return false; + } + } + return true; + } + + private static bool ParameterModifiersEqual(SyntaxTokenList a, SyntaxTokenList b) + { + if (a.Count != b.Count) + { + return false; + } + for (int i = 0; i < a.Count; i++) + { + if (!a[i].IsKind(b[i].Kind())) + { + return false; + } + } + return true; + } + + private static BasePropertyDeclarationSyntax SynthesizeUnreachableInheritedPropertyAccessor(ComMethodContext inheritedStub) + { + IncrementalMethodStubGenerationContext genCtx = inheritedStub.GenerationContext; + Debug.Assert(genCtx.MemberKind.IsPropertyOrIndexerAccessor()); + + bool isSetter = genCtx.MemberKind.IsAccessorSetter(); + bool isIndexer = genCtx.MemberKind.IsIndexerAccessor(); + + ImmutableArray stubParameters = genCtx.SignatureContext.StubParameters.ToImmutableArray(); + TypeSyntax valueType; + ImmutableArray indexParameters; + if (isSetter) + { + valueType = stubParameters[stubParameters.Length - 1].Type!; + indexParameters = stubParameters.RemoveAt(stubParameters.Length - 1); + } + else + { + valueType = genCtx.SignatureContext.StubReturnType; + indexParameters = stubParameters; + } + + AccessorDeclarationSyntax accessor = AccessorDeclaration( + isSetter ? SyntaxKind.SetAccessorDeclaration : SyntaxKind.GetAccessorDeclaration) + .WithExpressionBody(ArrowExpressionClause( + ThrowExpression( + ObjectCreationExpression(ParseTypeName("global::System.Diagnostics.UnreachableException")) + .WithArgumentList(ArgumentList())))) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); + + ExplicitInterfaceSpecifierSyntax explicitSpecifier = ExplicitInterfaceSpecifier( + ParseName(inheritedStub.OriginalDeclaringInterface.Info.Type.FullTypeName)); + + if (isIndexer) + { + return IndexerDeclaration(valueType) + .WithExplicitInterfaceSpecifier(explicitSpecifier) + .WithParameterList(BracketedParameterList(SeparatedList(indexParameters))) + .WithAccessorList(AccessorList(SingletonList(accessor))); + } + + string accessorName = inheritedStub.MethodInfo.MethodName; + string propertyName = IncrementalMethodStubGenerationContext.GetPropertyNameFromAccessor(accessorName); + + return PropertyDeclaration(valueType, Identifier(propertyName)) + .WithExplicitInterfaceSpecifier(explicitSpecifier) + .WithAccessorList(AccessorList(SingletonList(accessor))); + } + + private static void FlushBufferedPropertyGetter(IndentedTextWriter writer, ref BasePropertyDeclarationSyntax? bufferedGetter) + { + if (bufferedGetter is null) + { + return; + } + writer.InnerWriter.WriteLine(); + writer.WriteMultilineNode(bufferedGetter.NormalizeWhitespace()); + bufferedGetter = null; + } + + private static BasePropertyDeclarationSyntax MergePropertyAccessors( + BasePropertyDeclarationSyntax getter, + BasePropertyDeclarationSyntax setter) + { + var combined = new List(2); + combined.AddRange(getter.AccessorList!.Accessors); + combined.AddRange(setter.AccessorList!.Accessors); + return getter.WithAccessorList(AccessorList(List(combined))); + } + private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter writer, ComInterfaceAndMethodsContext data) { data.Interface.Info.TypeDefinitionContext.WriteToWithUnsafeModifier(writer, (data.Interface.Info.ContainingSyntax, data.ShadowingMethods), static (writer, data) => @@ -621,11 +894,98 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter writer.WriteLine('{'); writer.Indent++; + // Buffered getter state for merging consecutive get+set pairs into one declaration. + // For ordinary properties IndexParamList / IndexArgList are null; for indexers they hold + // the formatted parameter list (e.g. "int i, string s") and the argument-forwarding list + // (e.g. "i, s") respectively. The parameter list also serves as part of the merge identity + // so that overloaded indexers do not accidentally cross-pair. + (string? PropName, string? DeclaringType, string? PropType, + SequenceEqualImmutableArray PropAttrs, + string? IndexParamList, string? IndexArgList) pendingGetter = default; + foreach (ComMethodContext shadow in shadowingMethods) { IncrementalMethodStubGenerationContext generationContext = shadow.GenerationContext; SignatureContext sigContext = generationContext.SignatureContext; + if (generationContext.MemberKind.IsPropertyOrIndexerAccessor()) + { + bool isSetter = generationContext.MemberKind.IsAccessorSetter(); + bool isIndexer = generationContext.MemberKind.IsIndexerAccessor(); + string accessorName = shadow.MethodInfo.MethodName; + string propName = IncrementalMethodStubGenerationContext.GetPropertyNameFromAccessor(accessorName); + string declaringType = shadow.OriginalDeclaringInterface.Info.Type.FullTypeName; + + // Materialize the parameter sequences once — StubParameters / ManagedParameters + // are IEnumerable, not lists. + ImmutableArray stubParams = sigContext.StubParameters.ToImmutableArray(); + ImmutableArray managedParams = sigContext.ManagedParameters.ToImmutableArray(); + + // The value type for an accessor is the StubReturnType for a getter and the LAST + // managed parameter type for a setter (the implicit 'value'). For both ordinary + // properties (one managed parameter) and indexer setters (index params + value), + // the value entry is always last. + TypeSyntax valueTypeSyntax = isSetter + ? stubParams[stubParams.Length - 1].Type! + : sigContext.StubReturnType; + string propType = valueTypeSyntax.NormalizeWhitespace().ToString(); + SequenceEqualImmutableArray propAttrs = shadow.MethodInfo.AssociatedAttributes; + + string? indexParamList = null; + string? indexArgList = null; + if (isIndexer) + { + // For indexers the index parameter list is the StubParameters minus the + // implicit value entry (for setters). For getters, all StubParameters are + // index parameters. + int indexCount = isSetter ? stubParams.Length - 1 : stubParams.Length; + indexParamList = string.Join(", ", stubParams.Take(indexCount).Select(p => p.NormalizeWhitespace().ToString())); + indexArgList = string.Join(", ", managedParams.Take(indexCount).Select(mp => $"{(mp.IsByRef ? $"{MarshallerHelpers.GetManagedArgumentRefKindKeyword(mp)} " : "")}{mp.InstanceIdentifier}")); + } + + if (!isSetter) + { + FlushPendingGetter(writer, ref pendingGetter); + pendingGetter = (propName, declaringType, propType, propAttrs, indexParamList, indexArgList); + continue; + } + + // Setter: try to pair with a buffered getter. Identity includes the index parameter + // list so overloaded indexers (same name, different param types) stay separate. + if (pendingGetter.PropName == propName + && pendingGetter.DeclaringType == declaringType + && pendingGetter.IndexParamList == indexParamList) + { + EmitPropertyAttributes(writer, pendingGetter.PropAttrs); + EmitDeclarationHead(writer, pendingGetter.PropType!, pendingGetter.PropName!, pendingGetter.IndexParamList); + writer.WriteLine('{'); + writer.Indent++; + EmitAccessor(writer, isSetter: false, pendingGetter.DeclaringType!, pendingGetter.PropName!, pendingGetter.IndexArgList); + EmitAccessor(writer, isSetter: true, pendingGetter.DeclaringType!, pendingGetter.PropName!, pendingGetter.IndexArgList); + writer.Indent--; + writer.WriteLine('}'); + pendingGetter = default; + continue; + } + + FlushPendingGetter(writer, ref pendingGetter); + EmitPropertyAttributes(writer, propAttrs); + EmitDeclarationHead(writer, propType, propName, indexParamList); + writer.WriteLine('{'); + writer.Indent++; + EmitAccessor(writer, isSetter: true, declaringType, propName, indexArgList); + writer.Indent--; + writer.WriteLine('}'); + continue; + } + + FlushPendingGetter(writer, ref pendingGetter); + + // AssociatedAttributes is currently populated only for property/indexer accessors; + // ordinary method stubs must not carry any. If this fires, a new producer is feeding + // the field for a non-property member and the emitter needs to decide how to consume it. + Debug.Assert(shadow.MethodInfo.AssociatedAttributes.Array.IsEmpty); + foreach (AttributeListSyntax additionalAttr in sigContext.AdditionalAttributes) { writer.WriteLine(additionalAttr.NormalizeWhitespace().ToString()); @@ -642,8 +1002,75 @@ private static void WriteIUnknownDerivedOriginalInterfacePart(IndentedTextWriter writer.WriteLine($"({string.Join(", ", sigContext.ManagedParameters.Select(mp => $"{(mp.IsByRef ? $"{MarshallerHelpers.GetManagedArgumentRefKindKeyword(mp)} " : "")}{mp.InstanceIdentifier}"))});"); } + FlushPendingGetter(writer, ref pendingGetter); + writer.Indent--; writer.WriteLine('}'); + + static void FlushPendingGetter(IndentedTextWriter writer, ref (string? PropName, string? DeclaringType, string? PropType, SequenceEqualImmutableArray PropAttrs, string? IndexParamList, string? IndexArgList) pending) + { + if (pending.PropName is null) + { + return; + } + EmitPropertyAttributes(writer, pending.PropAttrs); + EmitDeclarationHead(writer, pending.PropType!, pending.PropName!, pending.IndexParamList); + writer.WriteLine('{'); + writer.Indent++; + EmitAccessor(writer, isSetter: false, pending.DeclaringType!, pending.PropName!, pending.IndexArgList); + writer.Indent--; + writer.WriteLine('}'); + pending = default; + } + + // Writes either `new T Name` (property) or `new T this[]` (indexer) on its own line. + static void EmitDeclarationHead(IndentedTextWriter writer, string propType, string propName, string? indexParamList) + { + if (indexParamList is null) + { + writer.WriteLine($"new {propType} {propName}"); + } + else + { + writer.WriteLine($"new {propType} this[{indexParamList}]"); + } + } + + // Writes either `get => ((Base)this).Name;` / `set => ((Base)this).Name = value;` for properties + // or `get => ((Base)this)[];` / `set => ((Base)this)[] = value;` for indexers. + // For indexers the propName isn't part of the access expression (the IL-level naming comes + // from `[IndexerName]` propagated via AssociatedAttributes). + static void EmitAccessor(IndentedTextWriter writer, bool isSetter, string declaringType, string propName, string? indexArgList) + { + string access = indexArgList is null + ? $"(({declaringType})this).{propName}" + : $"(({declaringType})this)[{indexArgList}]"; + writer.WriteLine(isSetter + ? $"set => {access} = value;" + : $"get => {access};"); + } + + static void EmitPropertyAttributes(IndentedTextWriter writer, SequenceEqualImmutableArray attrs) + { + // The derived-interface property shadow is a pure C# forwarder: its accessors are + // `get => ((Base)this).Prop;` / `set => ((Base)this).Prop = value;`, with no COM call + // and therefore no marshalling. Marshalling attributes declared on the source property + // are intentionally suppressed here so the shadow only carries semantically-meaningful + // user attributes (e.g. attributes used for documentation, tooling, or reflection). + // + // `[IndexerName("X")]` (for indexers) is NOT marshalling and must be propagated so the + // shadow's IL accessor names (get_X / set_X) match the source's — otherwise the runtime + // sees `get_Item` / `set_Item` and the shadow loses identity with the base indexer. + foreach (AttributeInfo attrInfo in attrs) + { + if (attrInfo.Type is "global::" + TypeNames.MarshalUsingAttribute + or "global::" + TypeNames.System_Runtime_InteropServices_MarshalAsAttribute) + { + continue; + } + writer.WriteLine($"[{attrInfo.Type}({string.Join(", ", attrInfo.Arguments)})]"); + } + } }); } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs index f58aa48301ff0d..c5a4de48776939 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComMethodInfo.cs @@ -13,25 +13,46 @@ namespace Microsoft.Interop { /// - /// Represents a method that has been determined to be a COM interface method. Only contains info immediately available from an IMethodSymbol and MethodDeclarationSyntax. + /// Represents a method that has been determined to be a COM interface method. Only contains info immediately available from an IMethodSymbol and the user-declared member syntax (a for ordinary methods, a for property accessors, or an for indexer accessors). /// internal sealed record ComMethodInfo { - public MethodDeclarationSyntax? Syntax { get; init; } + public MemberDeclarationSyntax? Syntax { get; init; } public string MethodName { get; init; } public SequenceEqualImmutableArray Attributes { get; init; } + /// + /// Attributes associated with this method that come from a related symbol rather than the method + /// itself (for example, attributes declared on the source when this + /// method is a property accessor). + /// + public SequenceEqualImmutableArray AssociatedAttributes { get; init; } public bool IsUserDefinedShadowingMethod { get; init; } + /// + /// Disambiguator for externally-defined accessors/methods where is + /// . The IL name alone (for example, get_Item, or simply + /// MyMethod) cannot distinguish either overloads inside a single cross-assembly base + /// (e.g. four get_Item accessors of different indexer signatures) or same-named + /// methods coming from two disjoint cross-assembly bases. Always + /// for user-declared records, whose field already provides + /// per-declaration uniqueness. + /// + public string ExternalSymbolId { get; init; } = string.Empty; + private ComMethodInfo( - MethodDeclarationSyntax syntax, + MemberDeclarationSyntax? syntax, string methodName, SequenceEqualImmutableArray attributes, - bool isUserDefinedShadowingMethod) + bool isUserDefinedShadowingMethod, + SequenceEqualImmutableArray associatedAttributes = default) { Syntax = syntax; MethodName = methodName; Attributes = attributes; IsUserDefinedShadowingMethod = isUserDefinedShadowingMethod; + AssociatedAttributes = associatedAttributes.Array.IsDefault + ? ImmutableArray.Empty.ToSequenceEqual() + : associatedAttributes; } /// @@ -49,53 +70,100 @@ private ComMethodInfo( switch (member) { - case { Kind: SymbolKind.Property }: - methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(member.CreateDiagnosticInfo(GeneratorDiagnostics.InstancePropertyDeclaredInInterface, member.Name, data.ifaceSymbol.ToDisplayString()))); + case IPropertySymbol property: + AddPropertyAccessorInfos(methods, data.ifaceContext, data.ifaceSymbol, property, ct); break; case { Kind: SymbolKind.Event }: methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(member.CreateDiagnosticInfo(GeneratorDiagnostics.InstanceEventDeclaredInInterface, member.Name, data.ifaceSymbol.ToDisplayString()))); break; - case IMethodSymbol { MethodKind: MethodKind.Ordinary }: - methods.Add(CalculateMethodInfo(data.ifaceContext, (IMethodSymbol)member, ct)); + case IMethodSymbol { MethodKind: MethodKind.Ordinary } method: + AddMethodInfo(methods, data.ifaceContext, data.ifaceSymbol, method, ct); break; } } return methods.ToImmutable().ToSequenceEqual(); } - private static DiagnosticInfo? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax comMethodDeclaringSyntax, IMethodSymbol method) + /// + /// Outcome of analyzing the user-declared shape of a method or property on a + /// [GeneratedComInterface]-attributed interface. + /// + private enum MemberShapeOutcome + { + /// The member is abstract and should be emitted as a COM ABI vtable slot. + ComAbi, + /// + /// The member has a default implementation (body) and must NOT be assigned a vtable slot. + /// The user-supplied body runs purely on the managed side; the member is invisible to the + /// COM marshalling pipeline. + /// + DefaultImplementation, + /// The member is malformed; emit the accompanying diagnostic and skip emission. + Error, + } + + private static MemberShapeOutcome AnalyzeMethodShape( + MethodDeclarationSyntax comMethodDeclaringSyntax, + IMethodSymbol method, + out DiagnosticInfo? diagnostic) { - // Verify the method has no generic types or defined implementation - // and is not marked static or sealed + diagnostic = null; + + // A method with any body (block or expression) is treated as a default implementation + // (DIM) and is intentionally NOT assigned a vtable slot. Generic and sealed DIMs are + // accepted as well because the C# language already enforces the valid combinations. + if (comMethodDeclaringSyntax.Body is not null || comMethodDeclaringSyntax.ExpressionBody is not null) + { + return MemberShapeOutcome.DefaultImplementation; + } + + // For non-DIM methods (the COM ABI path), generic methods and sealed methods are not supported. if (comMethodDeclaringSyntax.TypeParameterList is not null - || comMethodDeclaringSyntax.Body is not null || comMethodDeclaringSyntax.Modifiers.Any(SyntaxKind.SealedKeyword)) { - return DiagnosticInfo.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, comMethodDeclaringSyntax.Identifier.GetLocation(), method.Name); + diagnostic = DiagnosticInfo.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, comMethodDeclaringSyntax.Identifier.GetLocation(), method.Name); + return MemberShapeOutcome.Error; } // Verify the method does not have a ref return if (method.ReturnsByRef || method.ReturnsByRefReadonly) { - return DiagnosticInfo.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, comMethodDeclaringSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString()); + diagnostic = DiagnosticInfo.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, comMethodDeclaringSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString()); + return MemberShapeOutcome.Error; } - return null; + return MemberShapeOutcome.ComAbi; } - private static DiagnosticOr<(ComMethodInfo, IMethodSymbol)> CalculateMethodInfo(ComInterfaceInfo ifaceContext, IMethodSymbol method, CancellationToken ct) + private static void AddMethodInfo( + ImmutableArray>.Builder methods, + ComInterfaceInfo ifaceContext, + INamedTypeSymbol ifaceSymbol, + IMethodSymbol method, + CancellationToken ct) { ct.ThrowIfCancellationRequested(); Debug.Assert(method is { IsStatic: false, MethodKind: MethodKind.Ordinary }); // For externally-defined contexts, we only need minimal information about the method - // to ensure that we have the right offsets for inheriting vtable types. - // Skip all validation as that will be done when that type is compiled. + // to ensure that we have the right offsets for inheriting vtable types. Default-implemented + // members in another assembly do not occupy vtable slots; rely on IMethodSymbol.IsAbstract + // (which is false for DIMs in metadata) to distinguish. if (ifaceContext.IsExternallyDefined) { - return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(( - new ComMethodInfo(null, method.Name, method.GetAttributes().Select(AttributeInfo.From).ToImmutableArray().ToSequenceEqual(), false), - method)); + if (!method.IsAbstract) + { + return; + } + + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(( + new ComMethodInfo(null, method.Name, CreateAttributeInfoArray(method.GetAttributes()), false) + { + // Stable per-symbol identity is required here because Syntax is null. + ExternalSymbolId = BuildExternalSymbolId(method), + }, + method))); + return; } // We only support methods that are defined in the same partial interface definition as the @@ -116,7 +184,8 @@ private ComMethodInfo( if (methodLocationInAttributedInterfaceDeclaration is null) { - return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(DiagnosticInfo.Create(GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, method.Locations.FirstOrDefault(), method.ToDisplayString())); + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(DiagnosticInfo.Create(GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, method.Locations.FirstOrDefault(), method.ToDisplayString()))); + return; } // Find the matching declaration syntax @@ -132,25 +201,437 @@ private ComMethodInfo( } if (comMethodDeclaringSyntax is null) { - return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(DiagnosticInfo.Create(GeneratorDiagnostics.CannotAnalyzeMethodPattern, method.Locations.FirstOrDefault(), method.ToDisplayString())); + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(DiagnosticInfo.Create(GeneratorDiagnostics.CannotAnalyzeMethodPattern, method.Locations.FirstOrDefault(), method.ToDisplayString()))); + return; + } + + switch (AnalyzeMethodShape(comMethodDeclaringSyntax, method, out var diag)) + { + case MemberShapeOutcome.Error: + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(diag!)); + return; + case MemberShapeOutcome.DefaultImplementation: + EmitMarshalAttributeWarningsForMethod(methods, method, method.Name, ifaceSymbol); + return; + } + + var attributeInfos = CreateAttributeInfoArray(method.GetAttributes()); + + bool shadowsBaseMethod = comMethodDeclaringSyntax.Modifiers.Any(SyntaxKind.NewKeyword); + var comMethodInfo = new ComMethodInfo(comMethodDeclaringSyntax, method.Name, attributeInfos, shadowsBaseMethod); + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From((comMethodInfo, method))); + } + + /// + /// Adds one per accessor (get first, then set) for a property declared on a + /// [GeneratedComInterface]-attributed interface, or a single diagnostic if the property's + /// declaration shape is not supported by source-generated COM. + /// + /// + /// + /// An abstract property (no accessor bodies) is mapped to one or two consecutive vtable slots + /// (getter first, then setter) using the same rules built-in COM uses for [ComVisible(true)] + /// interfaces. + /// + /// + /// A property whose accessors all carry bodies is treated as a default implementation (DIM) + /// and is NOT assigned a vtable slot — the user-supplied body runs purely on the managed side + /// and is invisible to the COM marshalling pipeline. Mixing the two shapes within a single + /// property is reported as . + /// + /// + private static void AddPropertyAccessorInfos( + ImmutableArray>.Builder methods, + ComInterfaceInfo ifaceContext, + INamedTypeSymbol ifaceSymbol, + IPropertySymbol property, + CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + // For externally-defined contexts, mirror the ordinary-method fast path: emit ComMethodInfos + // only for abstract accessors (handled inside AddExternallyDefinedAccessor). + if (ifaceContext.IsExternallyDefined) + { + AddExternallyDefinedAccessor(methods, property.GetMethod); + AddExternallyDefinedAccessor(methods, property.SetMethod); + return; + } + + Location interfaceLocation = ifaceContext.Declaration.GetLocation(); + Location? propertyLocationInAttributedInterfaceDeclaration = null; + foreach (var propertyLocation in property.Locations) + { + if (propertyLocation.SourceTree == interfaceLocation.SourceTree + && interfaceLocation.SourceSpan.Contains(propertyLocation.SourceSpan)) + { + propertyLocationInAttributedInterfaceDeclaration = propertyLocation; + break; + } } - var diag = GetDiagnosticIfInvalidMethodForGeneration(comMethodDeclaringSyntax, method); - if (diag is not null) + if (propertyLocationInAttributedInterfaceDeclaration is null) { - return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(diag); + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From( + DiagnosticInfo.Create(GeneratorDiagnostics.MethodNotDeclaredInAttributedInterface, property.Locations.FirstOrDefault(), property.ToDisplayString()))); + return; } - var attributes = method.GetAttributes(); - var attributeInfos = ImmutableArray.CreateBuilder(attributes.Length); + BasePropertyDeclarationSyntax? propertyDeclaringSyntax = null; + foreach (var declaringSyntaxReference in property.DeclaringSyntaxReferences) + { + if (declaringSyntaxReference.GetSyntax(ct) is BasePropertyDeclarationSyntax candidate + && candidate.GetLocation().SourceSpan.Contains(propertyLocationInAttributedInterfaceDeclaration.SourceSpan)) + { + propertyDeclaringSyntax = candidate; + break; + } + } + if (propertyDeclaringSyntax is null) + { + // The syntax tree doesn't cover the located position. This is unexpected for any + // BasePropertyDeclarationSyntax shape (property or indexer); report the same + // analysis-failure diagnostic the ordinary-method path uses for the parallel case. + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From( + DiagnosticInfo.Create(GeneratorDiagnostics.CannotAnalyzeMethodPattern, property.Locations.FirstOrDefault(), property.ToDisplayString()))); + return; + } + + switch (AnalyzePropertyShape(propertyDeclaringSyntax, property, ifaceSymbol, out var shapeDiagnostic)) + { + case MemberShapeOutcome.Error: + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(shapeDiagnostic!)); + return; + case MemberShapeOutcome.DefaultImplementation: + EmitMarshalAttributeWarningsForProperty(methods, property, ifaceSymbol); + return; + } + + bool shadowsBaseProperty = propertyDeclaringSyntax.Modifiers.Any(SyntaxKind.NewKeyword); + + // Emit one ComMethodInfo per accessor, in vtable slot order (get first, then set), matching the + // CCW vtable layout produced by the built-in CLR for a [ComVisible] interface. + AddPropertyAccessor(methods, propertyDeclaringSyntax, property.GetMethod, shadowsBaseProperty); + AddPropertyAccessor(methods, propertyDeclaringSyntax, property.SetMethod, shadowsBaseProperty); + } + + private static void AddExternallyDefinedAccessor( + ImmutableArray>.Builder methods, + IMethodSymbol? accessor) + { + // Default-implemented accessors in another assembly do not occupy vtable slots, so we + // must not count them when computing inheritance offsets. Rely on IMethodSymbol.IsAbstract + // (which is false for DIMs) to distinguish — matching AddMethodInfo's + // externally-defined fast path for ordinary methods. + if (accessor is null || !accessor.IsAbstract) + { + return; + } + + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(( + new ComMethodInfo( + null, + accessor.Name, + CreateAttributeInfoArray(accessor.GetAttributes()), + isUserDefinedShadowingMethod: false, + GetAssociatedAttributesForPropertyAccessor(accessor)) + { + // Stable per-symbol identity is required here because Syntax is null. + ExternalSymbolId = BuildExternalSymbolId(accessor), + }, + accessor))); + } + + private static void AddPropertyAccessor( + ImmutableArray>.Builder methods, + BasePropertyDeclarationSyntax propertyDeclaringSyntax, + IMethodSymbol? accessor, + bool isUserDefinedShadowingMethod) + { + if (accessor is null) + { + return; + } + + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From(( + new ComMethodInfo( + propertyDeclaringSyntax, + accessor.Name, + CreateAttributeInfoArray(accessor.GetAttributes()), + isUserDefinedShadowingMethod, + GetAssociatedAttributesForPropertyAccessor(accessor)), + accessor))); + } + + private static SequenceEqualImmutableArray GetAssociatedAttributesForPropertyAccessor(IMethodSymbol accessor) + { + if (accessor.AssociatedSymbol is not IPropertySymbol property) + { + return ImmutableArray.Empty.ToSequenceEqual(); + } + + return CreateAttributeInfoArray(property.GetAttributes()); + } + + private static SequenceEqualImmutableArray CreateAttributeInfoArray(ImmutableArray attributes) + { + if (attributes.IsDefaultOrEmpty) + { + return ImmutableArray.Empty.ToSequenceEqual(); + } + + var builder = ImmutableArray.CreateBuilder(attributes.Length); foreach (var attr in attributes) { - attributeInfos.Add(AttributeInfo.From(attr)); + builder.Add(AttributeInfo.From(attr)); } + return builder.MoveToImmutable().ToSequenceEqual(); + } - bool shadowsBaseMethod = comMethodDeclaringSyntax.Modifiers.Any(SyntaxKind.NewKeyword); - var comMethodInfo = new ComMethodInfo(comMethodDeclaringSyntax, method.Name, attributeInfos.MoveToImmutable().ToSequenceEqual(), shadowsBaseMethod); - return DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From((comMethodInfo, method)); + /// + /// Builds a stable per-symbol identifier used as the value of + /// . Returns the symbol's ECMA-334 + /// documentation-comment ID; falls back to a fully-qualified display string for the rare + /// symbol shapes that have no doc-id form so the field is always non-null. + /// These strings are for convenience only, any unique identifier would work. + /// + private static string BuildExternalSymbolId(IMethodSymbol method) + => method.GetDocumentationCommentId() + ?? method.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + private static MemberShapeOutcome AnalyzePropertyShape( + BasePropertyDeclarationSyntax propertyDeclaringSyntax, + IPropertySymbol property, + INamedTypeSymbol ifaceSymbol, + out DiagnosticInfo? diagnostic) + { + diagnostic = null; + + foreach (var modifier in propertyDeclaringSyntax.Modifiers) + { + switch (modifier.Kind()) + { + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.InternalKeyword: + case SyntaxKind.UnsafeKeyword: + case SyntaxKind.NewKeyword: + continue; + case SyntaxKind.ExternKeyword: + case SyntaxKind.RequiredKeyword: + default: + // Any other modifier (e.g. `static`, `partial`, `abstract`, `virtual`, …) + // is rejected by the same unsupported-modifier diagnostic with the offending + // keyword text. C# already disallows most of these on interface instance + // properties, but reporting through our own diagnostic gives a clearer signal. + diagnostic = property.CreateDiagnosticInfo( + GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface, + property.Name, ifaceSymbol.ToDisplayString(), modifier.ValueText); + return MemberShapeOutcome.Error; + } + } + + // Auto-property initializers are not legal on interface instance properties at the C# + // level (CS8053), so we never reach AnalyzePropertyShape with one. Indexers cannot carry + // initializers at all. + Debug.Assert(propertyDeclaringSyntax is not PropertyDeclarationSyntax { Initializer: not null }, + "Interface instance properties cannot have auto-property initializers (CS8053)."); + + // Disallow `init` accessors before deciding DIM-vs-ABI: an `init` accessor is conceptually a + // setter, but `init`-vs-`set` has no meaningful representation in a COM vtable, so it isn't + // supported regardless of whether the accessor has a body. + if (propertyDeclaringSyntax.AccessorList is { } accessorList) + { + foreach (var accessor in accessorList.Accessors) + { + if (accessor.Keyword.IsKind(SyntaxKind.InitKeyword)) + { + diagnostic = property.CreateDiagnosticInfo( + GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface, + property.Name, ifaceSymbol.ToDisplayString(), "init"); + return MemberShapeOutcome.Error; + } + } + } + + // An expression-bodied property (`T Foo => …;`) is a single-getter default implementation + // and is treated as a DIM. Indexers can also be expression-bodied (`T this[int i] => …;`) + // with the same semantics. ExpressionBody lives on the concrete subclasses, not on + // BasePropertyDeclarationSyntax, so we pattern-match each shape. + ArrowExpressionClauseSyntax? expressionBody = propertyDeclaringSyntax switch + { + PropertyDeclarationSyntax propertyDecl => propertyDecl.ExpressionBody, + IndexerDeclarationSyntax indexerDecl => indexerDecl.ExpressionBody, + _ => null, + }; + if (expressionBody is not null) + { + return MemberShapeOutcome.DefaultImplementation; + } + + // Look at the per-accessor bodies. All-with-bodies → DIM, none-with-bodies → COM ABI, + // mixed → error. We require the user to commit to one shape per property to avoid the + // confusion of a property whose getter is in the vtable but whose setter isn't (or + // vice versa) — that would silently change marshalling semantics on a per-accessor basis. + if (propertyDeclaringSyntax.AccessorList is { } al) + { + int accessorCount = 0; + int accessorsWithBody = 0; + foreach (var accessor in al.Accessors) + { + accessorCount++; + if (accessor.Body is not null || accessor.ExpressionBody is not null) + { + accessorsWithBody++; + } + } + + if (accessorCount > 0 && accessorsWithBody > 0 && accessorsWithBody < accessorCount) + { + diagnostic = property.CreateDiagnosticInfo( + GeneratorDiagnostics.PropertyAccessorsMustBeAllOrNothing, + property.Name, ifaceSymbol.ToDisplayString()); + return MemberShapeOutcome.Error; + } + + if (accessorCount > 0 && accessorsWithBody == accessorCount) + { + return MemberShapeOutcome.DefaultImplementation; + } + } + + // For the COM ABI path (no bodies), reject ref / ref readonly returns the same way + // ordinary methods do — there is no representation for a managed ref-return in a COM + // vtable. DIM-shaped properties returning by ref are unaffected because the DIM + // branches above have already returned MemberShapeOutcome.DefaultImplementation. + if (property.ReturnsByRef || property.ReturnsByRefReadonly) + { + diagnostic = property.CreateDiagnosticInfo( + GeneratorDiagnostics.ReturnConfigurationNotSupported, + "ref return", property.ToDisplayString()); + return MemberShapeOutcome.Error; + } + + // [MarshalUsing] on an accessor's value surface (the getter's return or the setter's + // value parameter) must specify a marshaller type. A count-only or depth-only attribute + // on the accessor could partially conflict with a property-level [MarshalUsing] and + // silently shadow it in the property-to-accessor merge in SignatureContext. We require + // the user to combine the marshaller type and count on a single accessor attribute or + // attach the count-only attribute to the property declaration instead. + if (!HasCompleteAccessorMarshalUsing(property)) + { + diagnostic = property.CreateDiagnosticInfo( + GeneratorDiagnostics.MarshalUsingOnPropertyAccessorMustSpecifyType, + property.Name, ifaceSymbol.ToDisplayString()); + return MemberShapeOutcome.Error; + } + + return MemberShapeOutcome.ComAbi; + } + + private static bool HasCompleteAccessorMarshalUsing(IPropertySymbol property) + { + // Inspect the only two value surfaces that participate in the property-to-accessor + // attribute merge in SignatureContext.MergeAccessorAndPropertyAttributes: + // - The getter's return type attributes (e.g., `[return: MarshalUsing(...)] get`). + // - The setter's value parameter attributes (e.g., `[param: MarshalUsing(...)] set`). + // Index parameters on indexer accessors are deliberately excluded -- they don't merge + // with property-level attributes, so [MarshalUsing] there cannot create the ambiguity + // this diagnostic guards against. An accessor surface is "complete" when every + // [MarshalUsing] on it specifies a marshaller type; a surface with no [MarshalUsing] + // at all is trivially complete. + if (property.GetMethod is { } getter + && !IsMarshalUsingComplete(getter.GetReturnTypeAttributes())) + { + return false; + } + + if (property.SetMethod is { } setter && setter.Parameters.Length > 0 + && !IsMarshalUsingComplete(setter.Parameters[setter.Parameters.Length - 1].GetAttributes())) + { + return false; + } + + return true; + + static bool IsMarshalUsingComplete(ImmutableArray attributes) + { + foreach (AttributeData attr in attributes) + { + if (attr.AttributeClass?.ToDisplayString() == TypeNames.MarshalUsingAttribute + && attr.ConstructorArguments.Length == 0) + { + return false; + } + } + return true; + } + } + + private static void EmitMarshalAttributeWarningsForMethod( + ImmutableArray>.Builder methods, + IMethodSymbol method, + string memberName, + INamedTypeSymbol ifaceSymbol) + { + foreach (var attribute in method.GetAttributes()) + { + TryAddMarshalAttributeWarning(methods, attribute, memberName, ifaceSymbol); + } + + foreach (var attribute in method.GetReturnTypeAttributes()) + { + TryAddMarshalAttributeWarning(methods, attribute, memberName, ifaceSymbol); + } + + foreach (var parameter in method.Parameters) + { + foreach (var attribute in parameter.GetAttributes()) + { + TryAddMarshalAttributeWarning(methods, attribute, memberName, ifaceSymbol); + } + } + } + + private static void EmitMarshalAttributeWarningsForProperty( + ImmutableArray>.Builder methods, + IPropertySymbol property, + INamedTypeSymbol ifaceSymbol) + { + foreach (var attribute in property.GetAttributes()) + { + TryAddMarshalAttributeWarning(methods, attribute, property.Name, ifaceSymbol); + } + + if (property.GetMethod is { } getter) + { + EmitMarshalAttributeWarningsForMethod(methods, getter, property.Name, ifaceSymbol); + } + + if (property.SetMethod is { } setter) + { + EmitMarshalAttributeWarningsForMethod(methods, setter, property.Name, ifaceSymbol); + } + } + + private static void TryAddMarshalAttributeWarning( + ImmutableArray>.Builder methods, + AttributeData attribute, + string memberName, + INamedTypeSymbol ifaceSymbol) + { + string? attrName = attribute.AttributeClass?.ToDisplayString(); + if (attrName != TypeNames.MarshalUsingAttribute + && attrName != TypeNames.System_Runtime_InteropServices_MarshalAsAttribute) + { + return; + } + + methods.Add(DiagnosticOr<(ComMethodInfo, IMethodSymbol)>.From( + attribute.CreateDiagnosticInfo( + GeneratorDiagnostics.MarshalAttributeOnDefaultImplementedComInterfaceMember, + memberName, ifaceSymbol.ToDisplayString()))); } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratedStubCodeContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratedStubCodeContext.cs index 6f11382a5b4e4d..dfce95a2552ca3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratedStubCodeContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratedStubCodeContext.cs @@ -9,6 +9,6 @@ namespace Microsoft.Interop internal sealed record GeneratedStubCodeContext( ManagedTypeInfo OriginalDefiningType, ContainingSyntaxContext ContainingSyntaxContext, - SyntaxEquivalentNode Stub, + SyntaxEquivalentNode Stub, SequenceEqualImmutableArray Diagnostics) : GeneratedMethodContextBase(OriginalDefiningType, Diagnostics); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs index bc67f140812286..d0d921f5032fd4 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs @@ -304,16 +304,49 @@ public class Ids isEnabledByDefault: true, description: GetResourceString(nameof(SR.MethodNotDeclaredInAttributedInterfaceDescription))); - /// - public static readonly DiagnosticDescriptor InstancePropertyDeclaredInInterface = + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationOnGeneratedComInterface = DiagnosticDescriptorHelper.Create( Ids.MemberWillNotBeSourceGenerated, - GetResourceString(nameof(SR.InstancePropertyDeclaredInInterfaceTitle)), - GetResourceString(nameof(SR.InstancePropertyDeclaredInInterfaceMessage)), + GetResourceString(nameof(SR.InvalidPropertyDeclarationOnGeneratedComInterfaceTitle)), + GetResourceString(nameof(SR.InvalidPropertyDeclarationOnGeneratedComInterfaceMessage)), Category, DiagnosticSeverity.Error, isEnabledByDefault: true, - description: GetResourceString(nameof(SR.InstancePropertyDeclaredInInterfaceDescription))); + description: GetResourceString(nameof(SR.InvalidPropertyDeclarationOnGeneratedComInterfaceDescription))); + + /// + public static readonly DiagnosticDescriptor PropertyAccessorsMustBeAllOrNothing = + DiagnosticDescriptorHelper.Create( + Ids.MemberWillNotBeSourceGenerated, + GetResourceString(nameof(SR.PropertyAccessorsMustBeAllOrNothingTitle)), + GetResourceString(nameof(SR.PropertyAccessorsMustBeAllOrNothingMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(SR.PropertyAccessorsMustBeAllOrNothingDescription))); + + /// + public static readonly DiagnosticDescriptor MarshalAttributeOnDefaultImplementedComInterfaceMember = + DiagnosticDescriptorHelper.Create( + Ids.MemberWillNotBeSourceGenerated, + GetResourceString(nameof(SR.MarshalAttributeOnDefaultImplementedComInterfaceMemberTitle)), + GetResourceString(nameof(SR.MarshalAttributeOnDefaultImplementedComInterfaceMemberMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(SR.MarshalAttributeOnDefaultImplementedComInterfaceMemberDescription))); + + /// + public static readonly DiagnosticDescriptor MarshalUsingOnPropertyAccessorMustSpecifyType = + DiagnosticDescriptorHelper.Create( + Ids.InvalidGeneratedComInterfaceAttributeUsage, + GetResourceString(nameof(SR.MarshalUsingOnPropertyAccessorMustSpecifyTypeTitle)), + GetResourceString(nameof(SR.MarshalUsingOnPropertyAccessorMustSpecifyTypeMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(SR.MarshalUsingOnPropertyAccessorMustSpecifyTypeDescription))); /// public static readonly DiagnosticDescriptor InstanceEventDeclaredInInterface = diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs index d42b40e8d998b8..08783399124e5e 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/IncrementalMethodStubGenerationContext.cs @@ -1,12 +1,49 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.Interop { internal abstract record GeneratedMethodContextBase(ManagedTypeInfo OriginalDefiningType, SequenceEqualImmutableArray Diagnostics); + internal enum StubMemberKind + { + Method, + PropertyGetter, + PropertySetter, + IndexerGetter, + IndexerSetter, + } + + internal static class StubMemberKindExtensions + { + /// + /// Returns true if represents any property or indexer accessor + /// (, , + /// , or ). + /// + public static bool IsPropertyOrIndexerAccessor(this StubMemberKind kind) + => kind is StubMemberKind.PropertyGetter or StubMemberKind.PropertySetter + or StubMemberKind.IndexerGetter or StubMemberKind.IndexerSetter; + + /// + /// Returns true if represents the setter half of either a property + /// or an indexer. + /// + public static bool IsAccessorSetter(this StubMemberKind kind) + => kind is StubMemberKind.PropertySetter or StubMemberKind.IndexerSetter; + + /// + /// Returns true if represents either accessor of an indexer + /// (as opposed to an ordinary property). + /// + public static bool IsIndexerAccessor(this StubMemberKind kind) + => kind is StubMemberKind.IndexerGetter or StubMemberKind.IndexerSetter; + } + internal record IncrementalMethodStubGenerationContext( SignatureContext SignatureContext, ISignatureDiagnosticLocations DiagnosticLocation, @@ -17,7 +54,33 @@ internal record IncrementalMethodStubGenerationContext( ManagedTypeInfo TypeKeyOwner, ManagedTypeInfo DeclaringType, SequenceEqualImmutableArray Diagnostics, - MarshallingInfo ManagedThisMarshallingInfo) : GeneratedMethodContextBase(DeclaringType, Diagnostics); + MarshallingInfo ManagedThisMarshallingInfo, + StubMemberKind MemberKind) : GeneratedMethodContextBase(DeclaringType, Diagnostics) + { + private const string GetterPrefix = "get_"; + private const string SetterPrefix = "set_"; + + /// + /// Returns true if matches the Roslyn naming convention for a + /// property accessor method (get_X or set_X). Centralizes the convention so + /// callers do not embed the prefix literals directly. + /// + public static bool IsPropertyAccessorName(string name) + => name.StartsWith(GetterPrefix, StringComparison.Ordinal) + || name.StartsWith(SetterPrefix, StringComparison.Ordinal); + + /// + /// Strips the get_/set_ prefix from to recover + /// the underlying property name. Caller is responsible for ensuring the input is an + /// accessor-shaped name (validated via ). + /// + public static string GetPropertyNameFromAccessor(string accessorName) + { + Debug.Assert(IsPropertyAccessorName(accessorName)); + // GetterPrefix and SetterPrefix are the same length; either constant works here. + return accessorName.Substring(GetterPrefix.Length); + } + } internal sealed record SourceAvailableIncrementalMethodStubGenerationContext( SignatureContext SignatureContext, @@ -31,7 +94,8 @@ internal sealed record SourceAvailableIncrementalMethodStubGenerationContext( ManagedTypeInfo TypeKeyOwner, ManagedTypeInfo DeclaringType, SequenceEqualImmutableArray Diagnostics, - MarshallingInfo ManagedThisMarshallingInfo) : IncrementalMethodStubGenerationContext( + MarshallingInfo ManagedThisMarshallingInfo, + StubMemberKind MemberKind) : IncrementalMethodStubGenerationContext( SignatureContext, DiagnosticLocation, CallingConvention, @@ -41,5 +105,26 @@ internal sealed record SourceAvailableIncrementalMethodStubGenerationContext( TypeKeyOwner, DeclaringType, Diagnostics, - ManagedThisMarshallingInfo); + ManagedThisMarshallingInfo, + MemberKind) + { + /// + /// The user-visible name of the member this stub targets, suitable for use as an identifier in + /// generated source. For an ordinary method this is the method's name; for a property accessor + /// this is the property's name (e.g. Foo for accessor get_Foo, stripping the + /// Roslyn-internal get_ / set_ prefix). + /// + public string TemplateName + { + get + { + string templateName = StubMethodSyntaxTemplate.Identifier.Text; + if (MemberKind.IsPropertyOrIndexerAccessor()) + { + return GetPropertyNameFromAccessor(templateName); + } + return templateName; + } + } + } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs index cef4bef3426c6f..1d1e892fab2bb1 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodPointerStubGenerator.cs @@ -20,7 +20,7 @@ internal static class VirtualMethodPointerStubGenerator internal const string VirtualMethodTableIdentifier = "__vtable"; internal const string VirtualMethodTarget = "__target"; - public static (MethodDeclarationSyntax, ImmutableArray) GenerateManagedToNativeStub( + public static (MemberDeclarationSyntax, ImmutableArray) GenerateManagedToNativeStub( SourceAvailableIncrementalMethodStubGenerationContext methodStub, Func generatorResolverCreator) { @@ -85,12 +85,28 @@ public static (MethodDeclarationSyntax, ImmutableArray) Generate // with no additional decorators. Debug.Assert(methodStub.TypeKeyOwner.Syntax is NameSyntax); - return ( - PrintGeneratedSource( + MemberDeclarationSyntax stubDeclaration; + if (methodStub.MemberKind.IsPropertyOrIndexerAccessor()) + { + // Emit a property or indexer declaration containing only the relevant accessor (get or set) + // with the stub body inline. The writer is responsible for merging the get and set halves + // of a single accessor pair into one declaration before output. + stubDeclaration = PrintPropertyOrIndexerAccessorStub( + methodStub, + code) + .WithExplicitInterfaceSpecifier(ExplicitInterfaceSpecifier((NameSyntax)methodStub.TypeKeyOwner.Syntax)); + } + else + { + stubDeclaration = PrintMethodStub( methodStub.StubMethodSyntaxTemplate, methodStub.SignatureContext, code) - .WithExplicitInterfaceSpecifier(ExplicitInterfaceSpecifier((NameSyntax)methodStub.TypeKeyOwner.Syntax)), + .WithExplicitInterfaceSpecifier(ExplicitInterfaceSpecifier((NameSyntax)methodStub.TypeKeyOwner.Syntax)); + } + + return ( + stubDeclaration, methodStub.Diagnostics.Array.AddRange(diagnostics.Diagnostics)); } @@ -112,7 +128,7 @@ private static ParenthesizedExpressionSyntax CreateFunctionPointerExpression( untypedFunctionPointerExpression)); } - private static MethodDeclarationSyntax PrintGeneratedSource( + private static MethodDeclarationSyntax PrintMethodStub( ContainingSyntax stubMethodSyntax, SignatureContext stub, BlockSyntax stubCode) @@ -125,9 +141,59 @@ private static MethodDeclarationSyntax PrintGeneratedSource( .WithBody(stubCode); } + private static BasePropertyDeclarationSyntax PrintPropertyOrIndexerAccessorStub( + SourceAvailableIncrementalMethodStubGenerationContext methodStub, + BlockSyntax stubCode) + { + Debug.Assert(methodStub.MemberKind.IsPropertyOrIndexerAccessor()); + bool isSetter = methodStub.MemberKind.IsAccessorSetter(); + bool isIndexer = methodStub.MemberKind.IsIndexerAccessor(); + + // For a getter, the stub return type is the value type and the parameter list contains the + // index parameters only (empty for an ordinary property). + // For a setter, the stub return type is void and the parameter list is "index parameters + + // value". In C# property/indexer syntax the value parameter is implicit (its type is taken + // from the declared type), so we drop it from the parameter list and treat its type as the + // value type for the declaration. + ImmutableArray stubParameters = methodStub.SignatureContext.StubParameters.ToImmutableArray(); + TypeSyntax valueType; + ImmutableArray indexParameters; + if (isSetter) + { + // The value parameter is the LAST entry for both property setters (only entry) and + // indexer setters (after the index parameters). + valueType = stubParameters[stubParameters.Length - 1].Type!; + indexParameters = stubParameters.RemoveAt(stubParameters.Length - 1); + } + else + { + valueType = methodStub.SignatureContext.StubReturnType; + indexParameters = stubParameters; + } + + SyntaxKind accessorKind = isSetter + ? SyntaxKind.SetAccessorDeclaration + : SyntaxKind.GetAccessorDeclaration; + + AccessorDeclarationSyntax accessor = AccessorDeclaration(accessorKind) + .AddAttributeLists(methodStub.SignatureContext.AdditionalAttributes.ToArray()) + .WithBody(stubCode); + + if (isIndexer) + { + return IndexerDeclaration(valueType) + .WithParameterList(BracketedParameterList(SeparatedList(indexParameters))) + .WithAccessorList(AccessorList(SingletonList(accessor))); + } + + return PropertyDeclaration(valueType, Identifier(methodStub.TemplateName)) + .WithAccessorList( + AccessorList(SingletonList(accessor))); + } + private const string ManagedThisParameterIdentifier = "@this"; - public static (MethodDeclarationSyntax, ImmutableArray) GenerateNativeToManagedStub( + public static (MemberDeclarationSyntax, ImmutableArray) GenerateNativeToManagedStub( SourceAvailableIncrementalMethodStubGenerationContext methodStub, Func generatorResolverCreator) { @@ -141,10 +207,37 @@ public static (MethodDeclarationSyntax, ImmutableArray) Generate diagnostics, generatorResolverCreator(methodStub.EnvironmentFlags, MarshalDirection.UnmanagedToManaged)); - BlockSyntax code = stubGenerator.GenerateStubBody( - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - IdentifierName(ManagedThisParameterIdentifier), - IdentifierName(methodStub.StubMethodSyntaxTemplate.Identifier))); + BlockSyntax code; + if (methodStub.MemberKind.IsPropertyOrIndexerAccessor()) + { + bool isSetter = methodStub.MemberKind.IsAccessorSetter(); + if (methodStub.MemberKind.IsIndexerAccessor()) + { + // For an indexer accessor the managed-side access is element access on @this; the + // helper assembles the bracketed index-argument list from the marshalled identifiers. + code = stubGenerator.GenerateStubBodyForIndexer( + IdentifierName(ManagedThisParameterIdentifier), + isSetter); + } + else + { + // For an ordinary property accessor the managed-side access is member access: + // @this.Foo + ExpressionSyntax propertyAccess = MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(ManagedThisParameterIdentifier), + IdentifierName(methodStub.TemplateName)); + code = stubGenerator.GenerateStubBodyForProperty(propertyAccess, isSetter); + } + } + else + { + Debug.Assert(methodStub.MemberKind is StubMemberKind.Method); + code = stubGenerator.GenerateStubBodyForMethod( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(ManagedThisParameterIdentifier), + IdentifierName(methodStub.StubMethodSyntaxTemplate.Identifier))); + } (ParameterListSyntax unmanagedParameterList, TypeSyntax returnType, _) = stubGenerator.GenerateAbiMethodSignatureData(); diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs index 26cd9b48bf747e..d42e24dfd87fb8 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs @@ -379,7 +379,8 @@ internal static SourceAvailableIncrementalMethodStubGenerationContext CalculateS interfaceType, interfaceType, new SequenceEqualImmutableArray(generatorDiagnostics.Diagnostics.ToImmutableArray()), - new ObjectUnwrapperInfo(unwrapperSyntax)); + new ObjectUnwrapperInfo(unwrapperSyntax), + MemberKind: StubMemberKind.Method); } private static MarshallingInfo CreateExceptionMarshallingInfo(AttributeData virtualMethodIndexAttr, ISymbol symbol, Compilation compilation, GeneratorDiagnosticsBag diagnostics, VirtualMethodIndexCompilationData virtualMethodIndexData) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx index ad5e04846222c1..9f63be478045cf 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/Strings.resx @@ -426,14 +426,41 @@ Declaring an instance event in a type with the 'GeneratedComInterfaceAttribute' is not supported - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported + + Property uses an unsupported modifier or accessor for a source-generated COM interface + + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf index 269d8f7b7da069..d03e4dc8b5d68d 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.cs.xlf @@ -532,21 +532,6 @@ Deklarace události instance v typu s GeneratedComInterfaceAttribute se nepodporuje. - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Vlastnosti nejsou konceptem COM, takže pro vlastnosti instance na rozhraních COM generovaných zdrojem nebude vygenerován žádný kód spolupráce. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - Vlastnost instance {0} je deklarována v rozhraní {1}, ve kterém je použit atribut GeneratedComInterfaceAttribute. - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - Deklarace vlastnosti instance v typu s GeneratedComInterfaceAttribute se nepodporuje. - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. Použití GeneratedComInterfaceAttribute a InterfaceTypeAttribute se nepodporuje s hodnotou ComInterfaceType {0}. @@ -707,6 +692,21 @@ Zadaný parametr ComInterfaceOptions pro {0} není platný. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures Zařazovací typ má nekompatibilní signatury metod @@ -847,11 +847,41 @@ Zadaná konfigurace atributu MarshalAsAttribute pro návratovou hodnotu metody {1} není podporovaná voláními P/Invoke generovanými zdrojem. Pokud je zadaná konfigurace povinná, použijte místo toho normální DllImport. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. Argument marshalMode atributu „CustomMarshaattribute“ musí být platná hodnota výčtu MarshalMode. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. Typ vstupního bodu řadiče „{0}“ pro spravovaný typ „{1}“ musí mít aritu o jednu větší než spravovaný typ. @@ -982,6 +1012,21 @@ U parametrů pole se doporučuje použít explicitní atributy „[In]“ a „[Out]“. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. GeneratedComInterfaceAttribute a GeneratedComClassAttribute vyžadují nebezpečný kód. Projekt se musí aktualizovat na <AllowUnsafeBlocks>true</AllowUnsafeBlocks>. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf index c701271eb01e04..40468a8da8992c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.de.xlf @@ -532,21 +532,6 @@ Das Deklarieren eines Instanzereignisses in einem Typ mit dem "GeneratedComInterfaceAttribute" wird nicht unterstützt. - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Eigenschaften sind kein Konzept in COM, daher wird kein Interopcode für Instanzeigenschaften auf von der Quelle generierten COM-Schnittstellen generiert. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - Die Instanzeigenschaft "{0}" wird in der Schnittstelle "{1}" deklariert, auf die das "GeneratedComInterfaceAttribute" angewendet wurde. - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - Das Deklarieren einer Instanzeigenschaft in einem Typ mit dem "GeneratedComInterfaceAttribute" wird nicht unterstützt. - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. Die Verwendung von „GeneratedComInterfaceAttribute“ und „InterfaceTypeAttribute“ wird mit dem ComInterfaceType-Wert „{0}“ nicht unterstützt. @@ -707,6 +692,21 @@ Die angegebenen 'ComInterfaceOptions' auf '{0}' sind ungültig. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures Der Marshaller-Typ weist inkompatible Methodensignaturen auf @@ -847,11 +847,41 @@ Die angegebene Konfiguration „MarshalAsAttribute“ für den Rückgabewert der Methode „{1}“ wird von quellengenerierten P/Invokes nicht unterstützt. Wenn die angegebene Konfiguration erforderlich ist, verwenden Sie stattdessen einen regulären „DllImport“. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. Das "marshalMode"-Argument von "CustomMarshallerAttribute" muss ein gültiger Enumerationswert von "MarshalMode" sein. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. Der Marshaller-Einstiegspunkttyp "{0}" für den verwalteten Typ "{1}" muss eine Stelligkeit aufweisen, die größer als der verwaltete Typ ist. @@ -982,6 +1012,21 @@ Es wird empfohlen, explizite [In]- und [Out]-Attribute für Arrayparameter zu verwenden. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. 'GeneratedComInterfaceAttribute' und 'GeneratedComClassAttribute' erfordern unsicheren Code. Das Projekt muss mit '<AllowUnsafeBlocks>wahr</AllowUnsafeBlocks>' aktualisiert werden. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf index cdc12113617c22..85c1f42bb4ed6f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.es.xlf @@ -532,21 +532,6 @@ No se admite la declaración de un evento de instancia en un tipo con "GeneratedComInterfaceAttribute". - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Las propiedades no son un concepto en COM, por lo que no se generará código de interoperabilidad para las propiedades de instancia en interfaces COM generadas por el origen. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - La propiedad de instancia ''{0}'' se declara en la interfaz ''{1}'', que tiene aplicado ''GeneratedComInterfaceAttribute'' - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - No se admite la declaración de una propiedad de instancia en un tipo con "GeneratedComInterfaceAttribute". - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. No se admite el uso de "GeneratedComInterfaceAttribute" e "InterfaceTypeAttribute" con el valor "ComInterfaceType" '{0}'. @@ -707,6 +692,21 @@ El elemento ''ComInterfaceOptions'' especificado en ''{0}'' no es válido. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures El tipo de serializador tiene firmas de método incompatibles @@ -847,11 +847,41 @@ La configuración "MarshalAsAttribute" para el valor devuelto del método "{1}" no se admite en P/Invokes generado por código fuente. Si se requiere la configuración especificada, use un "DllImport" normal en su lugar. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. El argumento 'marshalMode' de 'CustomMarshallerAttribute' debe ser un valor de enumeración válido de 'MarshalMode'. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. El tipo de punto de entrada del serializador "{0}" para el tipo administrado "{1}" debe tener una aridad mayor que el tipo administrado. @@ -982,6 +1012,21 @@ Se recomienda usar los atributos explícitos "[In]" y "[Out]" en los parámetros de matriz. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. "GeneratedComInterfaceAttribute" y "GeneratedComClassAttribute" requieren código no seguro. El proyecto debe actualizarse con "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>". diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf index ad71c691b050c8..8f30a6438a3f15 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.fr.xlf @@ -532,21 +532,6 @@ La déclaration d’un événement d’instance dans un type avec « GeneratedComInterfaceAttribute » n’est pas prise en charge - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Les propriétés ne sont pas un concept dans COM. Par conséquent, aucun code d’interopérabilité n’est généré à la source pour les propriétés d’instance sur les interfaces COM générées par la source. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - La propriété d’instance « {0} » est déclarée dans l’interface « {1} », à laquelle « GeneratedComInterfaceAttribute » est appliqué - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - La déclaration d’une propriété d’instance dans un type avec « GeneratedComInterfaceAttribute » n’est pas prise en charge - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. L’utilisation de 'GeneratedComInterfaceAttribute' et 'InterfaceTypeAttribute' n’est pas prise en charge avec la valeur 'ComInterfaceType' '{0}'. @@ -707,6 +692,21 @@ Les 'ComInterfaceOptions' spécifiées sur « {0} » ne sont pas valides. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures Le type Marshaller a des signatures de méthode incompatibles @@ -847,11 +847,41 @@ La configuration « MarshalAsAttribute » spécifiée pour la valeur de retour de la méthode «{1}» n’est pas prise en charge par les P/Invokes générés par la source. Si la configuration spécifiée est requise, utilisez plutôt un « DllImport » standard. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. L’argument 'marshalMode' de 'CustomMarshallerAttribute' doit être une valeur enum valide de 'MarshalMode'. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. Le type de point d’entrée marshaleur '{0}' pour le type managé '{1}' doit avoir une arité supérieure au type managé. @@ -982,6 +1012,21 @@ Il est recommandé d'utiliser les attributs explicites '[In]' et '[Out]' sur les paramètres du tableau. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. « GeneratedComInterfaceAttribute » et « GeneratedComClassAttribute » nécessitent du code non sécurisé. Le projet doit être mis à jour avec « <AllowUnsafeBlocks>true</AllowUnsafeBlocks> ». diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf index 88d8a5ed94dc31..792ce69256b893 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.it.xlf @@ -532,21 +532,6 @@ La dichiarazione di un evento dell’istanza in un tipo con 'GeneratedComInterfaceAttribute' non è supportata - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Le proprietà non sono concetti in COM quindi non verrà generato codice di interoperabilità per le proprietà dell'istanza nelle interfacce COM generate dall'origine. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - La proprietà dell’istanza '{0}' è dichiarata nell'interfaccia '{1}' a cui è applicato 'GeneratedComInterfaceAttribute' - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - La dichiarazione di una proprietà dell’istanza in un tipo con 'GeneratedComInterfaceAttribute' non è supportata - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. L'uso di 'GeneratedComInterfaceAttribute' e 'InterfaceTypeAttribute' non è supportato con il valore '{0}' di 'ComInterfaceType'. @@ -707,6 +692,21 @@ L’elemento 'ComInterfaceOptions' specificato in '{0}' non è valido. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures Il tipo marshaller ha firme di metodo incompatibili @@ -847,11 +847,41 @@ La configurazione 'MarshalAsAttribute' specificata per il valore restituito del metodo '{1}' non è supportata dai P/Invoke generati dall'origine. Se la configurazione specificata è obbligatoria, usare un attributo 'DllImport' normale. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. L'argomento 'marshalMode' di 'CustomMarshallerAttribute' deve essere un valore di enumerazione valido di 'MarshalMode'. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. Il tipo di punto di ingresso del marshalling '{0}' per il tipo gestito '{1}' deve avere un grado maggiore rispetto a quello del tipo gestito. @@ -982,6 +1012,21 @@ È consigliabile usare attributi '[In]' e '[Out]' espliciti nei parametri di matrice. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. GeneratedComInterfaceAttribute e 'GeneratedComClassAttribute' richiedono codice non gestito. Il progetto deve essere aggiornato con '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf index 2dc1736593aa7d..4b41f895ae5ac5 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ja.xlf @@ -532,21 +532,6 @@ 'GeneratedComInterfaceAttribute' を含む型のインスタンス イベントの宣言はサポートされていません - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - プロパティは COM の概念ではないため、ソース生成 COM インターフェイス上のインスタンス プロパティに対して相互運用コードはソース生成されません。 - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - インスタンス プロパティ '{0}' は、'GeneratedComInterfaceAttribute' が適用されているインターフェイス '{1}' で宣言されています - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - 'GeneratedComInterfaceAttribute' を含む型のインスタンス プロパティの宣言はサポートされていません - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. 'GeneratedComInterfaceAttribute' および 'InterfaceTypeAttribute' は、'ComInterfaceType' の値 '{0}' ではサポートされていません。 @@ -707,6 +692,21 @@ '' で指定された '{0}' の 'ComInterfaceOptions' が無効です。{1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures マーシャラー型に互換性のないメソッド シグネチャがあります @@ -847,11 +847,41 @@ メソッド '{1}' の戻り値に指定された 'MarshalAsAttribute' 構成は、ソース生成 P/Invokes ではサポートされていません。指定した構成が必要な場合は、代わりに通常の 'DllImport' を使用してください。 + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. 'CustomMarshallerAttribute' の 'marshalMode' 引数は、'MarshalMode' の有効な列挙値である必要があります。 + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. マネージド型 '{1}' のマーシャラー エントリ ポイント型 '{0}' には、マネージド型より 1 大きい引数が必要です。 @@ -982,6 +1012,21 @@ 配列パラメーターに明示的な '[In]' および '[Out]' 属性を使用することをお勧めします。 + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. 'GeneratedComInterfaceAttribute' および 'GeneratedComClassAttribute' にはアンセーフ コードが必要です。プロジェクトは '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>' で更新する必要があります。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf index 930bf13d94e5a4..8f9c5b999b8df0 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ko.xlf @@ -532,21 +532,6 @@ 'GeneratedComInterfaceAttribute'를 사용하는 형식의 인스턴스 이벤트 선언은 지원되지 않습니다. - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - 속성은 COM의 개념이 아니므로 소스 생성 COM 인터페이스의 인스턴스 속성에 대해 interop 코드가 생성되지 않습니다. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - 'GeneratedComInterfaceAttribute'가 적용된 '{1}' 인터페이스에서 '{0}' 인스턴스 속성이 선언되었습니다. - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - 'GeneratedComInterfaceAttribute'를 사용하는 형식의 인스턴스 속성 선언은 지원되지 않습니다. - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. 'GeneratedComInterfaceAttribute' 및 'InterfaceTypeAttribute'는 'ComInterfaceType' 값 '{0}'에서 지원되지 않습니다. @@ -707,6 +692,21 @@ '{0}'에 지정한 'ComInterfaceOptions'가 잘못되었습니다. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures 마샬러 형식에 호환되지 않는 메서드 시그니처가 있습니다. @@ -847,11 +847,41 @@ 메서드 '{1}'의 반환 값에 대해 지정된 'MarshalAsAttribute' 구성은 소스에서 생성된 P/Invokes에서 지원되지 않습니다. 지정된 구성이 필요한 경우 대신 일반 'DllImport'를 사용합니다. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. 'CustomMarshallerAttribute'의 'marshalMode' 인수는 'MarshalMode'의 유효한 열거형 값이어야 합니다. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. 관리 유형 '{1}'에 대한 마샬러 진입점 유형 '{0}'에는 관리 유형보다 1이 더 커야 합니다. @@ -982,6 +1012,21 @@ 배열 매개 변수에는 명시적인 '[In]' 및 '[Out]' 특성을 사용하는 것이 좋습니다. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. 'GeneratedComInterfaceAttribute' 및 'GeneratedComClassAttribute'에는 안전하지 않은 코드가 필요합니다. 프로젝트를 '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'로 업데이트해야 합니다. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf index c09c9755c28a65..c263205d897efe 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pl.xlf @@ -532,21 +532,6 @@ Deklarowanie zdarzenia wystąpienia w typie z atrybutem „GeneratedComInterfaceAttribute” nie jest obsługiwane - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Te właściwości nie są koncepcją w modelu COM, dlatego nie zostanie wygenerowany kod międzyoperacyjny dla właściwości wystąpienia w interfejsach COM generowanych źródłowo. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - Właściwość wystąpienia „{0}” jest zadeklarowana w interfejsie „{1}”, do którego zastosowano atrybut „GeneratedComInterfaceAttribute” - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - Deklarowanie właściwości wystąpienia w typie z atrybutem „GeneratedComInterfaceAttribute” nie jest obsługiwane - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. Użycie atrybutów „GeneratedComInterfaceAttribute” i „InterfaceTypeAttribute” nie jest obsługiwane w przypadku wartości „ComInterfaceType” „{0}”. @@ -707,6 +692,21 @@ Określone opcje „ComInterfaceOptions” w „{0}” są nieprawidłowe. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures Typ marshallera ma niezgodne sygnatury metody @@ -847,11 +847,41 @@ Określona konfiguracja atrybutu „MarshalAsAttribute” dla wartości zwracanej metody „{1}” nie jest obsługiwana przez generowane źródłowo P/Invokes. Jeśli określona konfiguracja jest wymagana, należy zamiast niej użyć zwykłego elementu „DllImport”. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. Argument „marshalMode” atrybutu „CustomMarshaellerAttribute” musi być prawidłową wartością wyliczenia argumentu „MarshalMode”. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. Typ punktu wejścia marshallera „{0}” dla typu zarządzanego „{1}” musi mieć liczbę argumentów o jeden większą niż typ zarządzany. @@ -982,6 +1012,21 @@ Zaleca się używanie jawnych atrybutów „[In]” i „[Out]” w parametrach tablicy. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. Atrybut „GeneratedComInterfaceAttribute” i „GeneratedComClassAttribute” wymagają niebezpiecznego kodu. Projekt musi zostać zaktualizowany za pomocą polecenia „<AllowUnsafeBlocks>true</AllowUnsafeBlocks>”. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf index fcd4b14f2f3375..19313d63bd4f6f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.pt-BR.xlf @@ -532,21 +532,6 @@ Não há suporte para declarar um evento de instância em um tipo com "GeneratedComInterfaceAttribute" - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - As propriedades não são um conceito em COM, portanto, nenhum código de interoperabilidade será gerado para propriedades de instância em interfaces COM geradas pela origem. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - A propriedade de instância "{0}" é declarada na interface "{1}", que tem "GeneratedComInterfaceAttribute" aplicado - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - Não há suporte para declarar uma propriedade de instância em um tipo com "GeneratedComInterfaceAttribute" - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. Não há suporte para o uso de 'GeneratedComInterfaceAttribute' e 'InterfaceTypeAttribute' com o valor 'ComInterfaceType' '{0}'. @@ -707,6 +692,21 @@ As 'ComInterfaceOptions' especificadas em '{0}' são inválidas. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures O tipo de marshaller tem assinaturas de método incompatíveis @@ -847,11 +847,41 @@ A configuração especificada "MarshalAsAttribute" para o valor retornado do método "{1}" não tem suporte do P/Invokes gerado pela origem. Se a configuração especificada for necessária, use "DllImport" no lugar. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. O argumento 'marshalMode' de 'CustomMarshallerAttribute' deve ser um valor de enumeração válido de 'MarshalMode'. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. O tipo de ponto de entrada para realizar marshaling '{0}' para o tipo gerenciado '{1}' deve ter uma aridade maior do que o tipo gerenciado. @@ -982,6 +1012,21 @@ É recomendável usar atributos '[In]' e '[Out]' explícitos nos parâmetros de matriz. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. "GeneratedComInterfaceAttribute" e "GeneratedComClassAttribute" exigem código não seguro. O projeto deve ser atualizado com "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>". diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf index 40b82d86126721..8172725cd0f7ad 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.ru.xlf @@ -532,21 +532,6 @@ Объявление события экземпляра в типе с атрибутом GeneratedComInterfaceAttribute не поддерживается - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Свойства не являются концепцией в COM, поэтому источник не будет генерировать код взаимодействия для свойств экземпляра в COM-интерфейсах, создаваемых источником. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - Свойство экземпляра "{0}" объявлено в интерфейсе "{1}", к которому применен атрибут GeneratedComInterfaceAttribute - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - Объявление свойства экземпляра в типе с атрибутом GeneratedComInterfaceAttribute не поддерживается - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. Использование GeneratedComInterfaceAttribute и InterfaceTypeAttribute не поддерживается со значением ComInterfaceType "{0}". @@ -707,6 +692,21 @@ Указанный параметр "ComInterfaceOptions" в "{0}" недопустим. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures Тип маршалер имеет несовместимые сигнатуры методов @@ -847,11 +847,41 @@ Указанная конфигурация "MarshalAsAttribute" для возвращаемого значения метода "{1}" не поддерживается сгенерированными источником P/Invokes. Если указанная конфигурация обязательна, используйте обычный метод "DllImport". + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. Аргумент "marshalMode" атрибута "CustomMarshallerAttribute" должен быть допустимым перечисляемым значением "MarshalMode". + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. Тип точки входа маршаллера "{0}" для управляемого типа "{1}" должен иметь более высокую арность, чем управляемый тип. @@ -982,6 +1012,21 @@ Рекомендуется использовать явные атрибуты "[In]" и "[Out]" для параметров массива. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. Для "GeneratedComInterfaceAttribute" и "GeneratedComClassAttribute" требуется небезопасный код. Проект необходимо обновить с использованием значения "<AllowUnsafeBlocks>true</AllowUnsafeBlocks>". diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf index 9967cd1fd59bf1..926ea614094eb5 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.tr.xlf @@ -532,21 +532,6 @@ 'GeneratedComInterfaceAttribute' içeren bir tür içinde bir örnek olay bildirilmesi desteklenmiyor - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - Özellikler COM'da kavram olarak değerlendirilmez, bu nedenle kaynak tarafından oluşturulan COM arabirimleri üzerinde örnek özellikler için birlikte çalışma kodu oluşturulmaz. - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - '{0}' örnek özelliği '{1}' arabiriminde bildirildi ve bu arabirimde 'GeneratedComInterfaceAttribute' uygulanmış - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - 'GeneratedComInterfaceAttribute' içeren bir tür içinde bir örnek özellik bildirilmesi desteklenmiyor - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. 'GeneratedComInterfaceAttribute' ve 'InterfaceTypeAttribute' kullanımı, 'ComInterfaceType' değeri '{0}' ile desteklenmiyor. @@ -707,6 +692,21 @@ '{0}' üzerinde 'ComInterfaceOptions' geçersiz. {1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures Sıralayıcı türü uyumsuz metot imzaları içeriyor @@ -847,11 +847,41 @@ '{1}' yönteminin dönüş değeri için belirtilen 'MarshalAsAttribute' yapılandırması, kaynak tarafından oluşturulan P/Invokes tarafından desteklenmiyor. Belirtilen yapılandırma gerekiyorsa, bunun yerine normal bir 'DllImport' kullanın. + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. 'CustomMarshallerAttribute' için 'marshalMode' bağımsız değişkeni 'MarshalMode' için geçerli bir sabit listesi değeri olmalıdır. + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. Yönetilen tür '{1}' için sıralayıcı giriş noktası '{0}', yönetilen türden büyük bir parametre sayısına sahip olmalıdır. @@ -982,6 +1012,21 @@ Dizi parametrelerinde açık '[In]' ve '[Out]' özniteliklerinin kullanılması önerilir. + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. 'GeneratedComInterfaceAttribute' ve 'GeneratedComClassAttribute' güvenli olmayan kod gerektiriyor. Projenin '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>' ile güncelleştirilmiş olması gerekiyor. diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf index ba3a9435e4ceae..76ff339b47a54c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hans.xlf @@ -532,21 +532,6 @@ 不支持在具有“GeneratedComInterfaceAttribute”的类型中声明实例事件 - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - 属性在 COM 中不是概念,因此不会为源生成的 COM 接口上的实例属性生成互操作代码。 - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - 实例属性“{0}”在接口“{1}”中声明,该接口应用了“GeneratedComInterfaceAttribute” - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - 不支持在具有“GeneratedComInterfaceAttribute”的类型中声明实例属性 - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. “ComInterfaceType”值“{0}”不支持使用“GeneratedComInterfaceAttribute”和“InterfaceTypeAttribute”。 @@ -707,6 +692,21 @@ “{0}”上指定的 "ComInterfaceOptions" 无效。{1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures 封送程序类型具有不兼容的方法签名 @@ -847,11 +847,41 @@ 源生成的 P/Invoke 不支持方法“{1}”返回值的指定“MarshalAsAttribute”配置。如果需要指定配置,请改用常规的“DllImport”。 + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. “CustomMarshallerAttribute”的“marshalMode”参数必须是“MarshalMode”的有效枚举值。 + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. 托管类型“{1}”的封送程序入口点类型“{0}”必须具有大于托管类型的 arity。 @@ -982,6 +1012,21 @@ 建议对数组参数使用显式“[In]”和“[Out]”属性。 + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. “GeneratedComInterfaceAttribute”和“GeneratedComClassAttribute”需要不安全代码。必须将项目更新为“<AllowUnsafeBlocks>true</AllowUnsafeBlocks>”。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf index 21f9ebc93240e2..445948fae86b61 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/Resources/xlf/Strings.zh-Hant.xlf @@ -532,21 +532,6 @@ 不支援在具有 'GeneratedComInterfaceAttribute' 的類型中宣告執行個體事件 - - Properties are not a concept in COM, so no interop code will be source generated for instance properties on source-generated COM interfaces. - 屬性不是 COM 中的概念,因此不會為來源產生的 COM 介面上的執行個體屬性來源產生 Interop 程式碼。 - - - - The instance property '{0}' is declared in the interface '{1}', which has the 'GeneratedComInterfaceAttribute' applied - 執行個體屬性 '{0}' 是在套用了 'GeneratedComInterfaceAttribute' 的介面 '{1}' 中宣告的 - - - - Declaring an instance property in a type with the 'GeneratedComInterfaceAttribute' is not supported - 不支援在具有 'GeneratedComInterfaceAttribute' 的類型中宣告執行個體屬性 - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. 'ComInterfaceType' 值 '{0}' 不支援使用 'GeneratedComInterfaceAttribute' 和 'InterfaceTypeAttribute'。 @@ -707,6 +692,21 @@ '{0}' 上指定的 'ComInterfaceOptions' 無效。{1} + + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + Source-generated COM interface properties do not support the 'static', 'extern', 'required', or 'init' modifiers/accessors. + + + + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + The property '{0}' on interface '{1}' uses '{2}', which is not supported on a source-generated COM interface property + + + + Property uses an unsupported modifier or accessor for a source-generated COM interface + Property uses an unsupported modifier or accessor for a source-generated COM interface + + Marshaller type has incompatible method signatures 封送處理器類型有不相容的方法簽章 @@ -847,11 +847,41 @@ 來源產生的 P/Invokes 不支援為方法 '{1}' 的傳回值指定 'MarshalAsAttribute' 設定。如果需要指定的設定,請改用一般 'DllImport'。 + + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + Marshalling attributes such as 'MarshalUsing' and 'MarshalAs' on a default-implemented (body-bearing) member of a source-generated COM interface have no effect. Default implementations are pure managed code that bypasses the COM marshalling pipeline and does not get a generated stub. + + + + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + The marshalling attribute on the default-implemented member '{0}' on interface '{1}' will be ignored because default implementations do not generate COM marshalling code + + + + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + Marshalling attribute on a default-implemented member of a source-generated COM interface has no effect + + The 'marshalMode' argument of 'CustomMarshallerAttribute' must be a valid enum value of 'MarshalMode'. 'CustomMarshallerAttribute' 的 'marshalMode' 引數必須是有效的列舉值 'MarshalMode'。 + + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + When 'MarshalUsing' is applied to a source-generated COM interface property accessor (via '[return: MarshalUsing(...)]' on a getter or '[param: MarshalUsing(...)]' on a setter), it must supply a marshaller type via the constructor argument. Count-only or depth-only forms ('MarshalUsing(ConstantElementCount = ...)', 'MarshalUsing(CountElementName = ...)', or 'MarshalUsing(ElementIndirectionDepth = ...)' on an accessor) would silently override or partially conflict with a property-level 'MarshalUsing'. Combine the marshaller type and count information on a single 'MarshalUsing' attribute on the accessor. 'ConstantElementCount' and 'ElementIndirectionDepth' may also be supplied via a property-level 'MarshalUsing'; 'CountElementName' is not supported on property or indexer accessors because peer-parameter lookups are not available in that context. + + + + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + The 'MarshalUsing' attribute on an accessor of property '{0}' on interface '{1}' must specify a marshaller type. Combine the marshaller type and count on a single 'MarshalUsing' on the accessor, or specify 'ConstantElementCount' or 'ElementIndirectionDepth' on the property declaration. + + + + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + 'MarshalUsing' applied to a source-generated COM interface property accessor must specify a marshaller type + + The marshaller entry point type '{0}' for managed type '{1}' must have an arity of one greater than the managed type. 受控類型 '{1}' 的封送處理器進入點類型 '{0}' 必須具有大於受控類型的 arity。 @@ -982,6 +1012,21 @@ 建議在陣列參數上使用明確的 '[In]' 和 '[Out]' 屬性。 + + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + On a source-generated COM interface, a property is either fully abstract (no accessor bodies, treated as a COM property occupying vtable slots) or fully default-implemented (every accessor has a body, treated as managed-only code that bypasses COM marshalling). Mixing the two shapes within a single property is not supported. + + + + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + The property '{0}' on interface '{1}' mixes abstract and default-implemented accessors. Either all accessors must have bodies or none must have bodies. + + + + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + Property accessors on a source-generated COM interface must either all have bodies or none have bodies + + 'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>'. 'GeneratedComInterfaceAttribute' 和 'GeneratedComClassAttribute' 需要不安全的程式碼。專案必須以 '<AllowUnsafeBlocks>true</AllowUnsafeBlocks>' 更新。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/DefaultMarshallingInfoParser.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/DefaultMarshallingInfoParser.cs index a0a51229597c82..5776640d55dee3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/DefaultMarshallingInfoParser.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/DefaultMarshallingInfoParser.cs @@ -54,9 +54,18 @@ public static MarshallingInfoParser Create(StubEnvironment env, GeneratorDiagnos new MarshalAsAttributeParser(diagnostics, defaultInfo), new MarshalUsingAttributeParser(env.Compilation, diagnostics)); + // Property accessors cannot legitimately use peer-element lookups + // (the getter has no parameters; the setter has only its own value parameter, so + // self-reference is nonsensical), so route them to an empty provider that always + // fails such lookups. This also lets MethodSignatureElementInfoProvider assert it + // never sees a property accessor. + IElementInfoProvider elementInfoProvider = method.AssociatedSymbol is IPropertySymbol + ? EmptyElementInfoProvider.Instance + : new MethodSignatureElementInfoProvider(env.Compilation, diagnostics, method, useSiteAttributeParsers); + return new MarshallingInfoParser( diagnostics, - new MethodSignatureElementInfoProvider(env.Compilation, diagnostics, method, useSiteAttributeParsers), + elementInfoProvider, useSiteAttributeParsers, ImmutableArray.Create( new MarshalAsWithCustomMarshallersParser(env.Compilation, diagnostics, new MarshalAsAttributeParser(diagnostics, defaultInfo)), diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/EmptyElementInfoProvider.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/EmptyElementInfoProvider.cs new file mode 100644 index 00000000000000..17cb093147beac --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/EmptyElementInfoProvider.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.Interop +{ + /// + /// An that exposes no peer elements. + /// Used for signatures whose elements cannot legitimately reference one another + /// (e.g. property accessors, where the getter has no parameters and the setter + /// has only its own value parameter), so that peer-element lookups always fail. + /// + internal sealed class EmptyElementInfoProvider : IElementInfoProvider + { + public static EmptyElementInfoProvider Instance { get; } = new EmptyElementInfoProvider(); + + private EmptyElementInfoProvider() { } + + public string FindNameForParamIndex(int paramIndex) => string.Empty; + + public bool TryGetInfoForElementName(AttributeData attrData, string elementName, GetMarshallingInfoCallback marshallingInfoCallback, IElementInfoProvider rootProvider, out TypePositionInfo info) + { + info = null; + return false; + } + + public bool TryGetInfoForParamIndex(AttributeData attrData, int paramIndex, GetMarshallingInfoCallback marshallingInfoCallback, IElementInfoProvider rootProvider, out TypePositionInfo info) + { + info = null; + return false; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/GeneratedStatements.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/GeneratedStatements.cs index 3beed9f31c2ece..acdf733b4d11f5 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/GeneratedStatements.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/GeneratedStatements.cs @@ -69,6 +69,27 @@ public static GeneratedStatements Create(BoundGenerators marshallers, StubCodeCo } } + /// + /// Create the standard set of generated statements for an unmanaged-to-managed stub whose + /// managed-side invocation is a property or indexer accessor. For a getter the body assigns the + /// read expression into the return identifier (__retVal = propertyAccess); for a setter + /// it assigns the value parameter into the access expression (propertyAccess = value). + /// + /// + /// For indexers the caller is expected to bake the index arguments into + /// (as an ElementAccessExpression) before passing it in; + /// the value parameter — which is the LAST entry in ManagedParameterMarshallers for both + /// property and indexer setters — is consumed here. + /// + public static GeneratedStatements CreateForProperty(BoundGenerators marshallers, StubIdentifierContext context, ExpressionSyntax propertyAccess, bool isSetter) + { + GeneratedStatements statements = Create(marshallers, context); + return statements with + { + InvokeStatement = GenerateStatementForProperty(marshallers, context with { CurrentStage = StubIdentifierContext.Stage.Invoke }, propertyAccess, isSetter) + }; + } + private static ImmutableArray GenerateStatementsForStubContext(BoundGenerators marshallers, StubIdentifierContext context) { ImmutableArray.Builder statementsToUpdate = ImmutableArray.CreateBuilder(); @@ -149,6 +170,39 @@ private static ExpressionStatementSyntax GenerateStatementForManagedInvoke(Bound invoke)); } + private static ExpressionStatementSyntax GenerateStatementForProperty(BoundGenerators marshallers, StubIdentifierContext context, ExpressionSyntax propertyAccess, bool isSetter) + { + if (context.CurrentStage != StubIdentifierContext.Stage.Invoke) + { + throw new ArgumentException("CurrentStage must be Invoke"); + } + + if (isSetter) + { + // Setter: assign the value parameter into the access expression. + // propertyAccess = ; + // + // For property setters the only managed parameter IS the value parameter, so .Last() is + // identical to .Single(). For indexer setters the value parameter is appended LAST after + // the index parameters (Roslyn convention), so .Last() correctly picks it out. + IBoundMarshallingGenerator valueParameterMarshaller = marshallers.ManagedParameterMarshallers.Last(); + ExpressionSyntax valueExpression = valueParameterMarshaller.AsManagedArgument(context).Expression; + return ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + propertyAccess, + valueExpression)); + } + + // Getter: assign the property read into the managed return identifier. + // = propertyAccess; + return ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + IdentifierName(context.GetIdentifiers(marshallers.ManagedReturnMarshaller.TypeInfo).managed), + propertyAccess)); + } + private static ImmutableArray GenerateCatchClauseForManagedException(BoundGenerators marshallers, StubIdentifierContext context) { if (!marshallers.HasManagedExceptionMarshaller) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MethodSignatureElementInfoProvider.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MethodSignatureElementInfoProvider.cs index a7446788b80000..a1b9514d8c6474 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MethodSignatureElementInfoProvider.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/MethodSignatureElementInfoProvider.cs @@ -1,10 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Text; +using System.Diagnostics; using Microsoft.CodeAnalysis; namespace Microsoft.Interop @@ -18,6 +16,9 @@ public sealed class MethodSignatureElementInfoProvider : IElementInfoProvider public MethodSignatureElementInfoProvider(Compilation compilation, GeneratorDiagnosticsBag generatorDiagnostics, IMethodSymbol method, ImmutableArray useSiteAttributeParsers) { + Debug.Assert(method.AssociatedSymbol is not IPropertySymbol, + "Property accessors are not valid arguments to MethodSignatureElementInfoProvider."); + _compilation = compilation; _generatorDiagnostics = generatorDiagnostics; _method = method; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs index f36857d379e632..96932bf8569eb4 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/SignatureContext.cs @@ -103,13 +103,33 @@ private static ImmutableArray GenerateTypeInformation( MarshallingInfoParser marshallingInfoParser, StubEnvironment env) { + // When the underlying method is a property accessor, bare attributes on the property declaration + // (e.g. `[MarshalUsing(typeof(X))] string Prop { get; set; }`) land on the property symbol and + // are not otherwise visible to the marshalling pipeline. Fall them through to the accessor's + // value surface only -- the getter's return, or the setter's value parameter (the last + // parameter, after any indexer index parameters). Index parameters on indexer accessors and the + // setter's `void` return are not value surfaces and do not inherit property-level attributes. + // Accessor-level attributes win over property-level ones on a per-type basis. Target-scoped + // attributes (`[return:]`, `[param:]`, `[get:]`, `[set:]`) are routed by Roslyn onto the + // accessor directly and so are already in the accessor's attribute set. + ImmutableArray associatedPropertyAttributes = method.AssociatedSymbol is IPropertySymbol property + ? property.GetAttributes() + : ImmutableArray.Empty; + + // The value parameter on a setter is the last parameter (index parameters precede it on + // indexer setters). Getters have no value parameter -- their value surface is the return. + int valueParameterIndex = method.MethodKind == MethodKind.PropertySet + ? method.Parameters.Length - 1 + : -1; - // Determine parameter and return types ImmutableArray.Builder typeInfos = ImmutableArray.CreateBuilder(); for (int i = 0; i < method.Parameters.Length; i++) { IParameterSymbol param = method.Parameters[i]; - MarshallingInfo marshallingInfo = marshallingInfoParser.ParseMarshallingInfo(param.Type, param.GetAttributes()); + ImmutableArray paramAttributes = i == valueParameterIndex + ? MergeAccessorAndPropertyAttributes(param.GetAttributes(), associatedPropertyAttributes) + : param.GetAttributes(); + MarshallingInfo marshallingInfo = marshallingInfoParser.ParseMarshallingInfo(param.Type, paramAttributes); var typeInfo = TypePositionInfo.CreateForParameter(param, marshallingInfo, env.Compilation); typeInfo = typeInfo with { @@ -119,7 +139,10 @@ private static ImmutableArray GenerateTypeInformation( typeInfos.Add(typeInfo); } - TypePositionInfo retTypeInfo = new(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(method.ReturnType), marshallingInfoParser.ParseMarshallingInfo(method.ReturnType, method.GetReturnTypeAttributes())); + ImmutableArray returnAttributes = method.MethodKind == MethodKind.PropertyGet + ? MergeAccessorAndPropertyAttributes(method.GetReturnTypeAttributes(), associatedPropertyAttributes) + : method.GetReturnTypeAttributes(); + TypePositionInfo retTypeInfo = new(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(method.ReturnType), marshallingInfoParser.ParseMarshallingInfo(method.ReturnType, returnAttributes)); retTypeInfo = retTypeInfo with { ManagedIndex = TypePositionInfo.ReturnIndex, @@ -131,6 +154,69 @@ private static ImmutableArray GenerateTypeInformation( return typeInfos.ToImmutable(); } + private static ImmutableArray MergeAccessorAndPropertyAttributes( + ImmutableArray accessorAttributes, + ImmutableArray associatedPropertyAttributes) + { + if (associatedPropertyAttributes.IsEmpty) + { + return accessorAttributes; + } + + // Accessor-level attributes win over property-level ones at the same dedup key + // (attribute type + ElementIndirectionDepth for [MarshalUsing], attribute type alone + // otherwise). [MarshalUsing] is the only AllowMultiple = true attribute that flows + // through this merge: it can repeat on a single value surface with distinct + // ElementIndirectionDepth values to describe marshalling at successive levels of + // indirection (the value itself at depth 0, its elements at depth 1, and so on). The + // public contract on MarshalUsingAttribute.ElementIndirectionDepth states only one + // [MarshalUsing] with a given depth may be provided on a given parameter or return + // value, so dedup keys for [MarshalUsing] include the depth -- an accessor-level + // [MarshalUsing] overrides only the property-level [MarshalUsing] at the matching + // depth, and property-level [MarshalUsing]s at other depths flow through. + // + // To keep this dedup unambiguous, the COM generator additionally rejects accessor-level + // [MarshalUsing] attributes that omit the marshaller type (see + // MarshalUsingOnPropertyAccessorMustSpecifyType in GeneratorDiagnostics). That keeps the + // partial-split case (e.g., marshaller type on the property and count-only on the + // accessor) from silently dropping one side; the user combines the information on a + // single attribute or attaches the count-only [MarshalUsing] to the property. + HashSet<(string?, int)> accessorAttributeKeys = new(); + foreach (AttributeData attr in accessorAttributes) + { + accessorAttributeKeys.Add(GetMergeKey(attr)); + } + + ImmutableArray.Builder merged = ImmutableArray.CreateBuilder(accessorAttributes.Length + associatedPropertyAttributes.Length); + merged.AddRange(accessorAttributes); + foreach (AttributeData attr in associatedPropertyAttributes) + { + if (!accessorAttributeKeys.Contains(GetMergeKey(attr))) + { + merged.Add(attr); + } + } + return merged.ToImmutable(); + + static (string?, int) GetMergeKey(AttributeData attr) + { + string? attributeName = attr.AttributeClass?.ToDisplayString(); + int depth = 0; + if (attributeName == TypeNames.MarshalUsingAttribute) + { + foreach (KeyValuePair named in attr.NamedArguments) + { + if (named.Key == ManualTypeMarshallingHelper.MarshalUsingProperties.ElementIndirectionDepth) + { + depth = (int)named.Value.Value!; + break; + } + } + } + return (attributeName, depth); + } + } + public bool Equals(SignatureContext other) { // We don't check if the generator factories are equal since diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/UnmanagedToManagedStubGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/UnmanagedToManagedStubGenerator.cs index bdc27c6555ea19..dc36427c139953 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/UnmanagedToManagedStubGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/UnmanagedToManagedStubGenerator.cs @@ -47,12 +47,65 @@ public UnmanagedToManagedStubGenerator( /// /// The generated code assumes it will be in an unsafe context. /// - public BlockSyntax GenerateStubBody(ExpressionSyntax methodToInvoke) + public BlockSyntax GenerateStubBodyForMethod(ExpressionSyntax methodToInvoke) { GeneratedStatements statements = GeneratedStatements.Create( _marshallers, StubCodeContext.DefaultNativeToManagedStub, _context, methodToInvoke); + return BuildBodyFromStatements(statements); + } + + /// + /// Generate the method body of the unmanaged-to-managed ComWrappers-based stub for a property + /// accessor. The natural methodToInvoke(args) shape doesn't apply — for a getter the + /// body assigns the property read into the return slot (retVal = propertyAccess) and + /// for a setter it assigns the value parameter into the property (propertyAccess = value). + /// + /// Member-access expression naming the managed-side property + /// (e.g., @this.Foo). + /// True if this stub is the property setter; false for the getter. + public BlockSyntax GenerateStubBodyForProperty(ExpressionSyntax propertyAccess, bool isSetter) + { + GeneratedStatements statements = GeneratedStatements.CreateForProperty( + _marshallers, + _context, + propertyAccess, + isSetter); + return BuildBodyFromStatements(statements); + } + + /// + /// Generate the method body of the unmanaged-to-managed ComWrappers-based stub for an indexer + /// accessor. Wraps the supplied in an + /// built from the marshalled identifiers for the + /// index parameters (which are all managed parameters for an indexer getter, and all-but-the-last + /// managed parameter for an indexer setter — the trailing entry is the implicit + /// value). The resulting access expression is then handled by the same property-stub + /// pipeline. + /// + /// Expression naming the managed-side target instance (e.g., + /// @this); the bracketed argument list is appended to this expression. + /// True if this stub is the indexer setter; false for the getter. + public BlockSyntax GenerateStubBodyForIndexer(ExpressionSyntax instance, bool isSetter) + { + var managedParameterMarshallers = _marshallers.ManagedParameterMarshallers; + int indexCount = isSetter ? managedParameterMarshallers.Length - 1 : managedParameterMarshallers.Length; + var argBuilder = ImmutableArray.CreateBuilder(indexCount); + for (int i = 0; i < indexCount; i++) + { + argBuilder.Add(managedParameterMarshallers[i].AsManagedArgument(_context)); + } + + ExpressionSyntax elementAccess = ElementAccessExpression( + instance, + BracketedArgumentList(SeparatedList(argBuilder.MoveToImmutable()))); + + return GenerateStubBodyForProperty(elementAccess, isSetter); + } + + private BlockSyntax BuildBodyFromStatements(GeneratedStatements statements) + { Debug.Assert(statements.CleanupCalleeAllocated.IsEmpty); bool shouldInitializeVariables = diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 661599e51405f2..c1660a7ca592ef 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -2540,7 +2540,7 @@ public ref struct ManagedToUnmanagedIn public void Free() { throw null; } } } - [System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.ReturnValue, AllowMultiple = true)] + [System.AttributeUsageAttribute(System.AttributeTargets.Parameter | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue, AllowMultiple = true)] public sealed partial class MarshalUsingAttribute : System.Attribute { public MarshalUsingAttribute() { } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/CrossAssemblyInheritanceTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/CrossAssemblyInheritanceTests.cs index 14f916718fcd9d..d36b72cde4567d 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/CrossAssemblyInheritanceTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/CrossAssemblyInheritanceTests.cs @@ -252,5 +252,183 @@ public unsafe void IDerivedFromDerivedExternalDerived_VTableLayoutIsCorrect() Assert.True(derivedFromExternalDerivedVTable.SequenceEqual(deepDerivedVTable), "IDerivedFromDerivedExternalDerived should have consistent IDerivedFromExternalDerived vtable layout"); } + + [GeneratedComClass] + [Guid("e0c6b35f-1234-4567-8901-123456789ac0")] + internal partial class DerivedExternalIndexersAndPropertiesImpl : IDerivedExternalIndexersAndProperties + { + private int _setSentinel; + private int _value = 11; + private int _writeOnlyCounter; + + public int this[int i] + { + get => i * 10; + set => _setSentinel = value; + } + + public int this[int i, int j] + { + get => (i * 100) + j; + set => _setSentinel = value; + } + + public int this[long l] => unchecked((int)(l + 1)); + + public int this[short s] + { + set => _setSentinel = value; + } + + public int Value + { + get => _value; + set => _value = value; + } + + public string Name => "DerivedExternalIndexersAndProperties"; + + public int WriteOnlyCounter + { + set => _writeOnlyCounter = value; + } + + public int Marker => 7; + + internal int LastSetSentinel => _setSentinel; + internal int LastWriteOnlyCounter => _writeOnlyCounter; + } + + [Fact] + public void IDerivedExternalIndexersAndProperties_AccessorsDispatchAcrossBothSurfaces() + { + var implementation = new DerivedExternalIndexersAndPropertiesImpl(); + var comWrappers = new StrategyBasedComWrappers(); + var nativeObj = comWrappers.GetOrCreateComInterfaceForObject(implementation, CreateComInterfaceFlags.None); + var managedObj = comWrappers.GetOrCreateObjectForComInstance(nativeObj, CreateObjectFlags.None); + + var externalSurface = (IExternalIndexersAndProperties)managedObj; + + // Overloaded indexers dispatch by parameter signature even though every accessor + // shares the get_Item / set_Item IL name. + Assert.Equal(50, externalSurface[5]); + Assert.Equal(305, externalSurface[3, 5]); + Assert.Equal(101, externalSurface[100L]); + + externalSurface[5] = 1; + externalSurface[3, 5] = 2; + externalSurface[(short)7] = 3; + + // Regular property accessors on the same external interface are reachable through + // the same RCW dispatch and round-trip values across the COM boundary. + Assert.Equal(11, externalSurface.Value); + externalSurface.Value = 22; + Assert.Equal(22, externalSurface.Value); + + Assert.Equal("DerivedExternalIndexersAndProperties", externalSurface.Name); + + externalSurface.WriteOnlyCounter = 99; + + // The derived interface still sees both the inherited surfaces and its own Marker. + var derived = (IDerivedExternalIndexersAndProperties)managedObj; + Assert.Equal(7, derived.Marker); + Assert.Equal(50, derived[5]); + Assert.Equal(305, derived[3, 5]); + Assert.Equal(101, derived[100L]); + Assert.Equal(22, derived.Value); + Assert.Equal("DerivedExternalIndexersAndProperties", derived.Name); + } + + [Fact] + public unsafe void IDerivedExternalIndexersAndProperties_VTableLayoutExtendsBase() + { + IIUnknownDerivedDetails baseDetails = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy + .GetIUnknownDerivedDetails(typeof(IExternalIndexersAndProperties).TypeHandle); + IIUnknownDerivedDetails derivedDetails = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy + .GetIUnknownDerivedDetails(typeof(IDerivedExternalIndexersAndProperties).TypeHandle); + + // IExternalIndexersAndProperties contributes one slot per accessor in declaration order: + // indexer get/set [int] (2) + // indexer get/set [int, int] (2) + // indexer get [long] (1) + // indexer set [short] (1) + // property get/set Value (2) + // property get Name (1) + // property set WriteOnlyCounter (1) = 10 slots + // plus IUnknown's 3. + const int BaseVTableSize = 3 + 10; + + var baseVTable = new ReadOnlySpan(baseDetails.ManagedVirtualMethodTable, BaseVTableSize); + var derivedBaseVTable = new ReadOnlySpan(derivedDetails.ManagedVirtualMethodTable, BaseVTableSize); + + Assert.True(baseVTable.SequenceEqual(derivedBaseVTable), + "IDerivedExternalIndexersAndProperties should preserve the IExternalIndexersAndProperties vtable prefix even though all indexer accessors share the get_Item/set_Item name."); + } + + [GeneratedComClass] + [Guid("e0c6b35f-1234-4567-8901-123456789ac1")] + internal partial class DerivedFromExternalSameNameImpl + : IDerivedFromExternalSameNameA, IDerivedFromExternalSameNameB + { + double IExternalSameNameA.MyMethod() => 1.5; + int IExternalSameNameB.MyMethod() => 33; + } + + [Fact] + public void IDerivedFromExternalSameName_CanCallBothDisjointBases() + { + var implementation = new DerivedFromExternalSameNameImpl(); + var comWrappers = new StrategyBasedComWrappers(); + var nativeObj = comWrappers.GetOrCreateComInterfaceForObject(implementation, CreateComInterfaceFlags.None); + var managedObj = comWrappers.GetOrCreateObjectForComInstance(nativeObj, CreateObjectFlags.None); + + var a = (IExternalSameNameA)managedObj; + var b = (IExternalSameNameB)managedObj; + + Assert.Equal(1.5, a.MyMethod()); + Assert.Equal(33, b.MyMethod()); + } + + [GeneratedComClass] + [Guid("e0c6b35f-1234-4567-8901-123456789ac2")] + internal partial class DerivedFromExternalSameNameAOnlyImpl + : IDerivedFromExternalSameNameA + { + public double MyMethod() => 2.5; + } + + [GeneratedComClass] + [Guid("e0c6b35f-1234-4567-8901-123456789ac3")] + internal partial class DerivedFromExternalSameNameBOnlyImpl + : IDerivedFromExternalSameNameB + { + public int MyMethod() => 77; + } + + [Fact] + public void IDerivedFromExternalSameNameA_CanCallStandalone() + { + var implementation = new DerivedFromExternalSameNameAOnlyImpl(); + var comWrappers = new StrategyBasedComWrappers(); + var nativeObj = comWrappers.GetOrCreateComInterfaceForObject(implementation, CreateComInterfaceFlags.None); + var managedObj = comWrappers.GetOrCreateObjectForComInstance(nativeObj, CreateObjectFlags.None); + + var a = (IExternalSameNameA)managedObj; + + Assert.Equal(2.5, a.MyMethod()); + } + + [Fact] + public void IDerivedFromExternalSameNameB_CanCallStandalone() + { + var implementation = new DerivedFromExternalSameNameBOnlyImpl(); + var comWrappers = new StrategyBasedComWrappers(); + var nativeObj = comWrappers.GetOrCreateComInterfaceForObject(implementation, CreateComInterfaceFlags.None); + var managedObj = comWrappers.GetOrCreateObjectForComInstance(nativeObj, CreateObjectFlags.None); + + var b = (IExternalSameNameB)managedObj; + + Assert.Equal(77, b.MyMethod()); + } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedPropertiesTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedPropertiesTests.cs new file mode 100644 index 00000000000000..718b73d7d96876 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedPropertiesTests.cs @@ -0,0 +1,415 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; +using Xunit; + +namespace ComInterfaceGenerator.Tests +{ + public unsafe partial class IDerivedPropertiesTests + { + [LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_derived_properties")] + public static partial void* NewDerivedProperties(); + + private static (DerivedProperties Impl, IDerivedProperties Rcw) CreateRcwAroundCcw() + { + var impl = new DerivedProperties(); + var cw = new StrategyBasedComWrappers(); + var comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + try + { + var comObject = cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + return (impl, (IDerivedProperties)comObject); + } + finally + { + Marshal.Release(comPtr); + } + } + + // ------------------------------------------------------------------------- + // Inherited properties — exercised via the derived RCW. Proves that + // accessor slots inherited from IProperties are emitted on the derived + // interface and dispatch into the correct underlying implementation. + // ------------------------------------------------------------------------- + + [Fact] + public void Inherited_IntProperty_RoundTrips() + { + (DerivedProperties impl, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + rcw.IntProperty = 321; + Assert.Equal(321, rcw.IntProperty); + Assert.Equal(321, impl.IntProperty); + } + + [Fact] + public void Inherited_ReadOnlyInt_ReturnsImplValue() + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + Assert.Equal(111, rcw.ReadOnlyInt); + } + + [Fact] + public void Inherited_WriteOnlyInt_PropagatesToImpl() + { + (DerivedProperties impl, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + rcw.WriteOnlyInt = 77; + Assert.Equal(77, impl.WriteOnlyIntSink); + } + + [Fact] + public void Inherited_GuidProperty_RoundTrips() + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + Guid value = Guid.NewGuid(); + rcw.GuidProperty = value; + Assert.Equal(value, rcw.GuidProperty); + } + + [Theory] + [InlineData("")] + [InlineData("Hello, World!")] + [InlineData("Unicode \u4F60\u597D \uD83D\uDE00")] + public void Inherited_StringProperty_RoundTrips(string value) + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + rcw.StringProperty = value; + Assert.Equal(value, rcw.StringProperty); + } + + [Fact] + public void Inherited_Self_NullByDefault() + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + Assert.Null(rcw.Self); + } + + [Fact] + public void Inherited_Self_RoundTripsInterfaceReference() + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + var other = new Properties(); + other.IntProperty = 88; + rcw.Self = other; + + IProperties? selfRcw = rcw.Self; + Assert.NotNull(selfRcw); + Assert.Equal(88, selfRcw!.IntProperty); + } + + // ------------------------------------------------------------------------- + // Derived-only properties — declared on IDerivedProperties. + // ------------------------------------------------------------------------- + + [Fact] + public void Derived_DerivedIntProperty_RoundTrips() + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + rcw.DerivedIntProperty = 555; + Assert.Equal(555, rcw.DerivedIntProperty); + } + + [Theory] + [InlineData("")] + [InlineData("Derived value")] + [InlineData("Unicode \u4F60\u597D \uD83D\uDE00")] + public void Derived_DerivedStringProperty_RoundTrips(string value) + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + rcw.DerivedStringProperty = value; + Assert.Equal(value, rcw.DerivedStringProperty); + } + + [Fact] + public void Derived_DerivedReadOnlyInt_ReturnsImplValue() + { + (_, IDerivedProperties rcw) = CreateRcwAroundCcw(); + + Assert.Equal(2222, rcw.DerivedReadOnlyInt); + } + + // ------------------------------------------------------------------------- + // QI smoke test: the derived RCW can be cast back to the base interface + // and inherited properties remain functional through the base RCW. + // ------------------------------------------------------------------------- + + [Fact] + public void QI_DerivedToBase_AccessesInheritedProperties() + { + (DerivedProperties impl, IDerivedProperties derivedRcw) = CreateRcwAroundCcw(); + IProperties baseRcw = (IProperties)derivedRcw; + + baseRcw.IntProperty = 1234; + Assert.Equal(1234, baseRcw.IntProperty); + Assert.Equal(1234, impl.IntProperty); + + derivedRcw.DerivedIntProperty = 9999; + Assert.Equal(9999, derivedRcw.DerivedIntProperty); + } + + // ------------------------------------------------------------------------- + // VTable layout: the derived interface vtable must begin with the base + // interface vtable verbatim. Mirrors IDerivedTests' equivalent check + // and is the strongest proof that inherited-property codegen emits the + // accessor slots in the correct order. + // ------------------------------------------------------------------------- + + [Fact] + public unsafe void DerivedVtable_StartsWithBaseInterfaceLayout() + { + IIUnknownDerivedDetails baseInterfaceDetails = + StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IProperties).TypeHandle); + IIUnknownDerivedDetails derivedInterfaceDetails = + StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IDerivedProperties).TypeHandle); + + int numBaseMethods = typeof(IProperties).GetMethods().Length; + int numPointersToCompare = 3 + numBaseMethods; + + var expected = new ReadOnlySpan(baseInterfaceDetails.ManagedVirtualMethodTable, numPointersToCompare); + var actual = new ReadOnlySpan(derivedInterfaceDetails.ManagedVirtualMethodTable, numPointersToCompare); + + Assert.True(expected.SequenceEqual(actual)); + } + + // ------------------------------------------------------------------------- + // RCW path only, driven by a hand-rolled native vtable (`new_derived_properties` + // from NativeExports/ComInterfaceGenerator/DerivedProperties.cs). The native + // shim registers two ComInterfaceEntry items (IProperties + IDerivedProperties); + // the derived vtable replicates the base layout in its first 13 slots and + // appends 5 derived-only slots. These tests prove the generated derived RCW + // marshals correctly against external native code, including dispatch through + // inherited accessor slots. + // ------------------------------------------------------------------------- + + private static IDerivedProperties CreateRcwOverNativeShim() + { + var cw = new StrategyBasedComWrappers(); + nint nativePtr = (nint)NewDerivedProperties(); + try + { + return (IDerivedProperties)cw.GetOrCreateObjectForComInstance(nativePtr, CreateObjectFlags.None); + } + finally + { + Marshal.Release(nativePtr); + } + } + + [Fact] + public void NativeShim_Inherited_IntProperty_RoundTrips() + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + rcw.IntProperty = 11; + Assert.Equal(11, rcw.IntProperty); + } + + [Fact] + public void NativeShim_Inherited_ReadOnlyInt_ReturnsShimValue() + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + Assert.Equal(111, rcw.ReadOnlyInt); + } + + [Fact] + public void NativeShim_Inherited_WriteOnlyInt_DoesNotThrow() + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + rcw.WriteOnlyInt = 17; + } + + [Fact] + public void NativeShim_Inherited_GuidProperty_RoundTrips() + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + Guid value = Guid.NewGuid(); + rcw.GuidProperty = value; + Assert.Equal(value, rcw.GuidProperty); + } + + [Theory] + [InlineData("")] + [InlineData("Hello, World!")] + [InlineData("Unicode \u4F60\u597D \uD83D\uDE00")] + public void NativeShim_Inherited_StringProperty_RoundTrips(string value) + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + rcw.StringProperty = value; + Assert.Equal(value, rcw.StringProperty); + } + + [Fact] + public void NativeShim_Derived_DerivedIntProperty_RoundTrips() + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + rcw.DerivedIntProperty = 222; + Assert.Equal(222, rcw.DerivedIntProperty); + } + + [Theory] + [InlineData("")] + [InlineData("Derived value")] + [InlineData("Unicode \u4F60\u597D \uD83D\uDE00")] + public void NativeShim_Derived_DerivedStringProperty_RoundTrips(string value) + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + rcw.DerivedStringProperty = value; + Assert.Equal(value, rcw.DerivedStringProperty); + } + + [Fact] + public void NativeShim_Derived_DerivedReadOnlyInt_ReturnsShimValue() + { + IDerivedProperties rcw = CreateRcwOverNativeShim(); + + Assert.Equal(2222, rcw.DerivedReadOnlyInt); + } + + [Fact] + public void NativeShim_QI_DerivedToBase_AccessesInheritedProperties() + { + IDerivedProperties derivedRcw = CreateRcwOverNativeShim(); + IProperties baseRcw = (IProperties)derivedRcw; + + baseRcw.IntProperty = 4321; + // Reads through the base interface go via the base vtable slot, + // which the shim shares state with the derived vtable's inherited slot. + Assert.Equal(4321, baseRcw.IntProperty); + Assert.Equal(4321, derivedRcw.IntProperty); + } + + // ------------------------------------------------------------------------- + // User-defined `new`-keyword property shadowing — mirrors the IHide method + // pattern for properties. A derived [GeneratedComInterface] that declares + // `new int Shadowed { get; set; }` should get its own fresh vtable slots + // for the get/set accessors, independent of the inherited base slots. + // The CCW class differentiates the two via explicit interface implementations + // (IPropertyShadowingBase.Shadowed vs IPropertyShadowingDerived.Shadowed), + // so reads/writes through each interface route to a distinct backing field. + // ------------------------------------------------------------------------- + + private static (PropertyShadowingImpl Impl, IPropertyShadowingDerived Rcw) CreatePropertyShadowingRcwAroundCcw() + { + var impl = new PropertyShadowingImpl(); + var cw = new StrategyBasedComWrappers(); + var comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + try + { + var comObject = cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + return (impl, (IPropertyShadowingDerived)comObject); + } + finally + { + Marshal.Release(comPtr); + } + } + + [Fact] + public void NewShadow_DerivedAccessor_RoutesToDerivedSlot() + { + (PropertyShadowingImpl impl, IPropertyShadowingDerived rcw) = CreatePropertyShadowingRcwAroundCcw(); + + rcw.Shadowed = 42; + + Assert.Equal(42, rcw.Shadowed); + Assert.Equal(42, impl.DerivedShadowedSink); + Assert.Equal(0, impl.BaseShadowedSink); + } + + [Fact] + public void NewShadow_BaseAccessor_RoutesToBaseSlot() + { + (PropertyShadowingImpl impl, IPropertyShadowingDerived rcw) = CreatePropertyShadowingRcwAroundCcw(); + IPropertyShadowingBase baseRcw = (IPropertyShadowingBase)rcw; + + baseRcw.Shadowed = 99; + + Assert.Equal(99, baseRcw.Shadowed); + Assert.Equal(99, impl.BaseShadowedSink); + Assert.Equal(0, impl.DerivedShadowedSink); + } + + [Fact] + public void NewShadow_SlotsAreIndependent() + { + (PropertyShadowingImpl impl, IPropertyShadowingDerived rcw) = CreatePropertyShadowingRcwAroundCcw(); + IPropertyShadowingBase baseRcw = (IPropertyShadowingBase)rcw; + + baseRcw.Shadowed = 7; + rcw.Shadowed = 13; + + Assert.Equal(7, baseRcw.Shadowed); + Assert.Equal(13, rcw.Shadowed); + Assert.Equal(7, impl.BaseShadowedSink); + Assert.Equal(13, impl.DerivedShadowedSink); + } + + [Fact] + public void NewShadow_InheritedNonShadowedProperty_SharesSingleSlot() + { + (PropertyShadowingImpl impl, IPropertyShadowingDerived rcw) = CreatePropertyShadowingRcwAroundCcw(); + IPropertyShadowingBase baseRcw = (IPropertyShadowingBase)rcw; + + baseRcw.NotShadowed = 100; + Assert.Equal(100, baseRcw.NotShadowed); + Assert.Equal(100, rcw.NotShadowed); + + rcw.NotShadowed = 200; + Assert.Equal(200, baseRcw.NotShadowed); + Assert.Equal(200, rcw.NotShadowed); + Assert.Equal(200, impl.NotShadowed); + } + + [Fact] + public unsafe void NewShadow_DerivedVtable_AppendsFreshSlotsForShadowedProperty() + { + IIUnknownDerivedDetails baseInterfaceDetails = + StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IPropertyShadowingBase).TypeHandle); + IIUnknownDerivedDetails derivedInterfaceDetails = + StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IPropertyShadowingDerived).TypeHandle); + + int numBaseAccessors = typeof(IPropertyShadowingBase).GetMethods().Length; + int numBaseSlots = 3 + numBaseAccessors; + + var baseVtable = new ReadOnlySpan(baseInterfaceDetails.ManagedVirtualMethodTable, numBaseSlots); + var derivedInheritedRange = new ReadOnlySpan(derivedInterfaceDetails.ManagedVirtualMethodTable, numBaseSlots); + + Assert.True(baseVtable.SequenceEqual(derivedInheritedRange)); + + (PropertyShadowingImpl impl, IPropertyShadowingDerived rcw) = CreatePropertyShadowingRcwAroundCcw(); + var (thisPtr, derivedVtable) = ((IUnmanagedVirtualMethodTableProvider)rcw).GetVirtualMethodTableInfoForKey(typeof(IPropertyShadowingDerived)); + + int derivedSetSlot = numBaseSlots + 1; + int derivedGetSlot = numBaseSlots; + + int sentinel = 12345; + int hr = ((delegate* unmanaged[MemberFunction])derivedVtable[derivedSetSlot])(thisPtr, sentinel); + Assert.Equal(0, hr); + Assert.Equal(sentinel, impl.DerivedShadowedSink); + Assert.Equal(0, impl.BaseShadowedSink); + + int readBack; + hr = ((delegate* unmanaged[MemberFunction])derivedVtable[derivedGetSlot])(thisPtr, &readBack); + Assert.Equal(0, hr); + Assert.Equal(sentinel, readBack); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDimMembersTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDimMembersTests.cs new file mode 100644 index 00000000000000..626e9ed5f95637 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDimMembersTests.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; +using Xunit; + +namespace ComInterfaceGenerator.Tests +{ + public unsafe partial class IDimMembersTests + { + // ------------------------------------------------------------------------- + // Round-trip an in-process [GeneratedComClass] through CCW + RCW. Each test + // verifies that the user-defined default-implemented (DIM) property or + // method routes through its associated COM ABI methods rather than being + // assigned its own vtable slot. + // ------------------------------------------------------------------------- + + private static (DimMembers Impl, IDimMembers Rcw) CreateRcwAroundCcw() + { + var impl = new DimMembers(); + var cw = new StrategyBasedComWrappers(); + var comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + try + { + var comObject = cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + return (impl, (IDimMembers)comObject); + } + finally + { + Marshal.Release(comPtr); + } + } + + [Fact] + public void DimProperty_RoundTrips() + { + (DimMembers impl, IDimMembers rcw) = CreateRcwAroundCcw(); + + rcw.Value = 3.14; + Assert.Equal(3.14, rcw.Value); + Assert.Equal(3.14, impl.ReadValue()); + } + + [Fact] + public void DimReadOnlyProperty_ReturnsImplValue() + { + (_, IDimMembers rcw) = CreateRcwAroundCcw(); + + Assert.Equal(111, rcw.CountProperty); + } + + [Fact] + public void DimWriteOnlyProperty_PropagatesToImpl() + { + (DimMembers impl, IDimMembers rcw) = CreateRcwAroundCcw(); + + rcw.SinkProperty = 42; + Assert.Equal(42, impl.WriteOnlyIntSink); + } + + [Fact] + public void DimMethod_ExecutesLocally() + { + (_, IDimMembers rcw) = CreateRcwAroundCcw(); + + Assert.Equal(20, rcw.DoubleIt(10)); + } + + [Fact] + public void DimProperty_AbiAccessorsRemainCallable() + { + (_, IDimMembers rcw) = CreateRcwAroundCcw(); + + rcw.WriteValue(2.5); + Assert.Equal(2.5, rcw.ReadValue()); + Assert.Equal(2.5, rcw.Value); + } + + // ------------------------------------------------------------------------- + // Raw vtable inspection: confirm that DIM members do NOT consume vtable + // slots. The vtable should only contain IUnknown (3) + the 4 ABI methods + // (ReadValue, WriteValue, ReadCount, WriteSink) -- 7 slots total. + // ------------------------------------------------------------------------- + + [Fact] + public void DimMembers_VtableContainsOnlyAbiSlots() + { + IIUnknownDerivedDetails details = + StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IDimMembers).TypeHandle); + + // Reflection-derived count: only abstract members (the ABI methods) get + // vtable slots; DIM properties/methods do not. + int abstractAccessorCount = 0; + foreach (MethodInfo method in typeof(IDimMembers).GetMethods()) + { + if (method.IsAbstract) + { + abstractAccessorCount++; + } + } + + Assert.Equal(4, abstractAccessorCount); + + // Confirm via the raw vtable: read 7 slots and ensure they're non-null. + // (If DIM members had been assigned slots, the count would be higher.) + int expectedSlots = 3 + abstractAccessorCount; + var vtable = new ReadOnlySpan(details.ManagedVirtualMethodTable, expectedSlots); + for (int i = 0; i < expectedSlots; i++) + { + Assert.NotEqual(0, vtable[i]); + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IIndexersTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IIndexersTests.cs new file mode 100644 index 00000000000000..8ee4b5b80d5ce3 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IIndexersTests.cs @@ -0,0 +1,331 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; +using Xunit; + +namespace ComInterfaceGenerator.Tests +{ + [Collection(nameof(TrackedIntMarshaller))] + public unsafe partial class IIndexersTests + { + [LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_indexers")] + public static partial void* NewIndexers(); + + // ------------------------------------------------------------------------- + // Round-trip an in-process [GeneratedComClass] through CCW + RCW so each + // indexer accessor is invoked through the generator's marshalling pipeline + // in both directions. + // ------------------------------------------------------------------------- + + private static (Indexers Impl, IIndexers Rcw) CreateRcwAroundCcw() + { + var impl = new Indexers(); + var cw = new StrategyBasedComWrappers(); + var comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + try + { + var comObject = cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + return (impl, (IIndexers)comObject); + } + finally + { + Marshal.Release(comPtr); + } + } + + [Theory] + [InlineData(0, 100)] + [InlineData(5, -3)] + [InlineData(-1, int.MaxValue / 2)] + public void SingleParamIndexer_RoundTrips(int index, int value) + { + (Indexers impl, IIndexers rcw) = CreateRcwAroundCcw(); + + rcw[index] = value; + int observed = rcw[index]; + + Assert.Equal(value, observed); + // The impl computes value-(i), stores value-(i), then the getter returns value-(i)+i = value. + Assert.Equal(value, impl[index]); + } + + [Theory] + [InlineData(0, 0, 11)] + [InlineData(2, 7, 999)] + [InlineData(-3, 4, -123)] + public void TwoParamIndexer_RoundTrips(int i, int j, int value) + { + (Indexers impl, IIndexers rcw) = CreateRcwAroundCcw(); + + rcw[i, j] = value; + int observed = rcw[i, j]; + + Assert.Equal(value, observed); + Assert.Equal(value, impl[i, j]); + } + + [Theory] + [InlineData(0L)] + [InlineData(11L)] + [InlineData(-3L)] + public void ReadOnlyIndexer_ReturnsComputedValue(long l) + { + (_, IIndexers rcw) = CreateRcwAroundCcw(); + + Assert.Equal(unchecked((int)(l * 7)), rcw[l]); + } + + [Theory] + [InlineData((short)0, 0)] + [InlineData((short)5, 13)] + [InlineData((short)-7, -200)] + public void WriteOnlyIndexer_PropagatesToImpl(short s, int value) + { + (Indexers impl, IIndexers rcw) = CreateRcwAroundCcw(); + + rcw[s] = value; + + Assert.Equal(value + s, impl.WriteOnlyShortSink); + } + + [Theory] + [InlineData("alpha", "first")] + [InlineData("", "empty-key")] + [InlineData("non-ascii-\u00e9\u00f1\u4e2d", "non-ascii-value-\u00fc\u30a2\u00df")] + [InlineData("key", "")] + public void StringKeyedIndexer_RoundTrips(string key, string value) + { + (Indexers impl, IIndexers rcw) = CreateRcwAroundCcw(); + + rcw[key] = value; + string observed = rcw[key]; + + Assert.Equal(value, observed); + Assert.Equal(value, impl[key]); + } + + // ------------------------------------------------------------------------- + // RCW path only, driven by a hand-rolled native vtable (`new_indexers` from + // NativeExports/ComInterfaceGenerator/Indexers.cs). This proves the generated + // RCW marshals correctly against external native code, not just against our + // own [GeneratedComClass] CCW. + // ------------------------------------------------------------------------- + + private static IIndexers CreateRcwOverNativeShim() + { + var cw = new StrategyBasedComWrappers(); + nint nativePtr = (nint)NewIndexers(); + try + { + return (IIndexers)cw.GetOrCreateObjectForComInstance(nativePtr, CreateObjectFlags.None); + } + finally + { + Marshal.Release(nativePtr); + } + } + + [Theory] + [InlineData(0, 100)] + [InlineData(5, -3)] + [InlineData(-1, int.MaxValue / 2)] + public void NativeShim_SingleParamIndexer_RoundTrips(int index, int value) + { + IIndexers rcw = CreateRcwOverNativeShim(); + + rcw[index] = value; + Assert.Equal(value, rcw[index]); + } + + [Theory] + [InlineData(0, 0, 11)] + [InlineData(2, 7, 999)] + [InlineData(-3, 4, -123)] + public void NativeShim_TwoParamIndexer_RoundTrips(int i, int j, int value) + { + IIndexers rcw = CreateRcwOverNativeShim(); + + rcw[i, j] = value; + Assert.Equal(value, rcw[i, j]); + } + + [Theory] + [InlineData(0L)] + [InlineData(11L)] + [InlineData(-3L)] + public void NativeShim_ReadOnlyIndexer_ReturnsShimValue(long l) + { + IIndexers rcw = CreateRcwOverNativeShim(); + + Assert.Equal(unchecked((int)(l * 7)), rcw[l]); + } + + [Theory] + [InlineData((short)0, 0)] + [InlineData((short)5, 13)] + [InlineData((short)-7, -200)] + public void NativeShim_WriteOnlyIndexer_DoesNotThrow(short s, int value) + { + IIndexers rcw = CreateRcwOverNativeShim(); + + rcw[s] = value; + } + + [Theory] + [InlineData("alpha", "first")] + [InlineData("", "empty-key")] + [InlineData("non-ascii-\u00e9\u00f1\u4e2d", "non-ascii-value-\u00fc\u30a2\u00df")] + [InlineData("key", "")] + public void NativeShim_StringKeyedIndexer_RoundTrips(string key, string value) + { + IIndexers rcw = CreateRcwOverNativeShim(); + + rcw[key] = value; + Assert.Equal(value, rcw[key]); + } + + // ------------------------------------------------------------------------- + // [IndexerName] propagation. Verifies the indexer accessor names baked into + // the metadata are get_Element / set_Element (not the default get_Item / set_Item), + // both on the user's interface and on the RCW returned from CCW marshalling. + // ------------------------------------------------------------------------- + + [Fact] + public void RenamedIndexer_ILAccessorNames_MatchIndexerNameAttribute() + { + MethodInfo[] methods = typeof(IRenamedIndexer).GetMethods( + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + Assert.Contains(methods, m => m.Name == "get_Element"); + Assert.Contains(methods, m => m.Name == "set_Element"); + Assert.DoesNotContain(methods, m => m.Name == "get_Item"); + Assert.DoesNotContain(methods, m => m.Name == "set_Item"); + } + + [Theory] + [InlineData(0, 100)] + [InlineData(3, -50)] + public void RenamedIndexer_RoundTrips(int index, int value) + { + var impl = new RenamedIndexer(); + var cw = new StrategyBasedComWrappers(); + nint comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + IRenamedIndexer rcw; + try + { + rcw = (IRenamedIndexer)cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + } + finally + { + Marshal.Release(comPtr); + } + + rcw[index] = value; + Assert.Equal(value, rcw[index]); + } + + // ------------------------------------------------------------------------- + // Property-level [MarshalUsing] on an indexer. Verifies that an attribute + // placed on the indexer declaration applies only to the value surface + // (getter return / setter value parameter) and is NOT propagated onto + // index parameters or the setter's `void` return. The test interface + // declares `[MarshalUsing(typeof(TrackedIntMarshaller))] int this[int]` + // -- the index and value share the same `int` type, so a buggy generator + // that propagates the attribute to every parameter would invoke the + // marshaller on each index pass as well. Exact-count assertions catch + // that regression. + // ------------------------------------------------------------------------- + + private static (IndexerMarshalling Impl, IIndexerMarshalling Rcw) CreateIndexerMarshallingRcwAroundCcw() + { + var impl = new IndexerMarshalling(); + var cw = new StrategyBasedComWrappers(); + nint comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + try + { + var comObject = cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + return (impl, (IIndexerMarshalling)comObject); + } + finally + { + Marshal.Release(comPtr); + } + } + + [Fact] + public void IndexerLevelMarshalling_Bare_InvokesMarshallerOnValueSurfaceOnly() + { + TrackedIntMarshaller.Reset(); + + (_, IIndexerMarshalling rcw) = CreateIndexerMarshallingRcwAroundCcw(); + + rcw[3] = 100; + int observed = rcw[3]; + + Assert.Equal(100, observed); + + // Each accessor traverses RCW (managed-to-unmanaged on the call out) and CCW + // (unmanaged-to-managed on the call in) for inputs, and the reverse for outputs. + // Value surface invocations across one set + one get: + // setter value: 1 RCW m2u + 1 CCW u2m + // getter return: 1 CCW m2u + 1 RCW u2m + // Total: m2u == 2, u2m == 2. + // The index parameter must NOT receive the marshaller. If it did, each accessor + // would add an extra m2u (RCW outbound for the index) and u2m (CCW inbound for the + // index), yielding 4 / 4 instead of 2 / 2. + Assert.Equal(2, TrackedIntMarshaller.ManagedToUnmanagedCount); + Assert.Equal(2, TrackedIntMarshaller.UnmanagedToManagedCount); + } + + // ------------------------------------------------------------------------- + // Derived-interface shadow propagation. A derived [GeneratedComInterface] + // that re-declares an inherited indexer with `new` emits a shadow indexer + // forwarder on the derived interface. We assert that the shadow exists with + // the correct shape via reflection. + // ------------------------------------------------------------------------- + + [Fact] + public void DerivedShadowIndexer_DeclaresDefaultNamedIndexer() + { + PropertyInfo? shadow = typeof(IDerivedIndexers).GetProperty( + "Item", + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + binder: null, + returnType: null, + types: [typeof(int)], + modifiers: null); + Assert.NotNull(shadow); + + Assert.NotNull(shadow!.GetMethod); + Assert.NotNull(shadow.SetMethod); + // Default IndexerName means the IL accessors are get_Item/set_Item. + Assert.Equal("get_Item", shadow.GetMethod!.Name); + Assert.Equal("set_Item", shadow.SetMethod!.Name); + } + + [Fact] + public void DerivedShadowIndexer_PropagatesIndexerNameAttribute() + { + PropertyInfo? shadow = typeof(IRenamedDerivedIndexers).GetProperty( + "Foo", + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + binder: null, + returnType: null, + types: [typeof(long)], + modifiers: null); + Assert.NotNull(shadow); + + Assert.NotNull(shadow!.GetMethod); + Assert.NotNull(shadow.SetMethod); + // The base interface declares [IndexerName("Foo")]; the shadow must propagate it so + // that the IL accessor names remain get_Foo / set_Foo on the derived interface. + Assert.Equal("get_Foo", shadow.GetMethod!.Name); + Assert.Equal("set_Foo", shadow.SetMethod!.Name); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IPropertiesTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IPropertiesTests.cs new file mode 100644 index 00000000000000..662f684c63a7d9 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IPropertiesTests.cs @@ -0,0 +1,397 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; +using Xunit; + +namespace ComInterfaceGenerator.Tests +{ + [Collection(nameof(TrackedIntMarshaller))] + public unsafe partial class IPropertiesTests + { + [LibraryImport(NativeExportsNE.NativeExportsNE_Binary, EntryPoint = "new_properties")] + public static partial void* NewProperties(); + + // ------------------------------------------------------------------------- + // Round-trip an in-process [GeneratedComClass] through CCW + RCW. Each test + // exercises one property to keep failure modes precisely localized. + // ------------------------------------------------------------------------- + + private static (Properties Impl, IProperties Rcw) CreateRcwAroundCcw() + { + var impl = new Properties(); + var cw = new StrategyBasedComWrappers(); + var comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + try + { + var comObject = cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + return (impl, (IProperties)comObject); + } + finally + { + Marshal.Release(comPtr); + } + } + + [Fact] + public void RcwAroundCcw_IntProperty_RoundTrips() + { + (Properties impl, IProperties rcw) = CreateRcwAroundCcw(); + + rcw.IntProperty = 123; + Assert.Equal(123, rcw.IntProperty); + Assert.Equal(123, impl.IntProperty); + } + + [Fact] + public void RcwAroundCcw_ReadOnlyInt_ReturnsImplValue() + { + (_, IProperties rcw) = CreateRcwAroundCcw(); + + Assert.Equal(111, rcw.ReadOnlyInt); + } + + [Fact] + public void RcwAroundCcw_WriteOnlyInt_PropagatesToImpl() + { + (Properties impl, IProperties rcw) = CreateRcwAroundCcw(); + + rcw.WriteOnlyInt = 99; + Assert.Equal(99, impl.WriteOnlyIntSink); + } + + [Fact] + public void RcwAroundCcw_GuidProperty_RoundTrips() + { + (_, IProperties rcw) = CreateRcwAroundCcw(); + + Guid value = Guid.NewGuid(); + rcw.GuidProperty = value; + Assert.Equal(value, rcw.GuidProperty); + } + + [Theory] + [InlineData("")] + [InlineData("Hello, World!")] + [InlineData("Unicode \u4F60\u597D \uD83D\uDE00")] + public void RcwAroundCcw_StringProperty_RoundTrips(string value) + { + (_, IProperties rcw) = CreateRcwAroundCcw(); + + rcw.StringProperty = value; + Assert.Equal(value, rcw.StringProperty); + } + + [Fact] + public void RcwAroundCcw_Self_NullByDefault() + { + (_, IProperties rcw) = CreateRcwAroundCcw(); + + Assert.Null(rcw.Self); + } + + [Fact] + public void RcwAroundCcw_Self_RoundTripsInterfaceReference() + { + (_, IProperties rcw) = CreateRcwAroundCcw(); + + var other = new Properties(); + other.IntProperty = 88; + rcw.Self = other; + + IProperties? selfRcw = rcw.Self; + Assert.NotNull(selfRcw); + Assert.Equal(88, selfRcw!.IntProperty); + } + + // ------------------------------------------------------------------------- + // RCW path only, driven by a hand-rolled native vtable (`new_properties` + // from NativeExports/ComInterfaceGenerator/Properties.cs). This proves the + // generated RCW marshals correctly against external native code, not just + // against our own [GeneratedComClass] CCW. + // ------------------------------------------------------------------------- + + private static IProperties CreateRcwOverNativeShim() + { + var cw = new StrategyBasedComWrappers(); + nint nativePtr = (nint)NewProperties(); + try + { + return (IProperties)cw.GetOrCreateObjectForComInstance(nativePtr, CreateObjectFlags.None); + } + finally + { + Marshal.Release(nativePtr); + } + } + + [Fact] + public void NativeShim_IntProperty_RoundTrips() + { + IProperties rcw = CreateRcwOverNativeShim(); + + rcw.IntProperty = 7; + Assert.Equal(7, rcw.IntProperty); + } + + [Fact] + public void NativeShim_ReadOnlyInt_ReturnsShimValue() + { + IProperties rcw = CreateRcwOverNativeShim(); + + Assert.Equal(111, rcw.ReadOnlyInt); + } + + [Fact] + public void NativeShim_WriteOnlyInt_DoesNotThrow() + { + IProperties rcw = CreateRcwOverNativeShim(); + + rcw.WriteOnlyInt = 13; + } + + [Fact] + public void NativeShim_GuidProperty_RoundTrips() + { + IProperties rcw = CreateRcwOverNativeShim(); + + Guid value = Guid.NewGuid(); + rcw.GuidProperty = value; + Assert.Equal(value, rcw.GuidProperty); + } + + [Theory] + [InlineData("")] + [InlineData("Hello, World!")] + [InlineData("Unicode \u4F60\u597D \uD83D\uDE00")] + public void NativeShim_StringProperty_RoundTrips(string value) + { + IProperties rcw = CreateRcwOverNativeShim(); + + rcw.StringProperty = value; + Assert.Equal(value, rcw.StringProperty); + } + + // ------------------------------------------------------------------------- + // Property-accessor marshalling-attribute coverage. Verifies that + // `[return: MarshalUsing(...)]` on a get accessor and `[param: MarshalUsing(...)]` + // on a set accessor are honored end-to-end through both the RCW and CCW + // marshalling pipelines. + // ------------------------------------------------------------------------- + + private static (PropertyMarshalling Impl, IPropertyMarshalling Rcw) CreatePropertyMarshallingRcwAroundCcw() + { + var impl = new PropertyMarshalling(); + var cw = new StrategyBasedComWrappers(); + var comPtr = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + try + { + var comObject = cw.GetOrCreateObjectForComInstance(comPtr, CreateObjectFlags.None); + return (impl, (IPropertyMarshalling)comObject); + } + finally + { + Marshal.Release(comPtr); + } + } + + [Fact] + public void PropertyAccessorMarshalling_ReadWrite_InvokesMarshallerOnBothAccessors() + { + TrackedIntMarshaller.Reset(); + + (_, IPropertyMarshalling rcw) = CreatePropertyMarshallingRcwAroundCcw(); + + rcw.TargetScoped = 7; + int observed = rcw.TargetScoped; + + Assert.Equal(7, observed); + + // CCW + RCW round-trip causes each marshaller direction to fire at least once + // per accessor invocation. + Assert.True(TrackedIntMarshaller.ManagedToUnmanagedCount > 0, "ConvertToUnmanaged was never called."); + Assert.True(TrackedIntMarshaller.UnmanagedToManagedCount > 0, "ConvertToManaged was never called."); + } + + [Fact] + public void PropertyAccessorMarshalling_ReadOnly_InvokesMarshallerOnGetterOnly() + { + TrackedIntMarshaller.Reset(); + + (_, IPropertyMarshalling rcw) = CreatePropertyMarshallingRcwAroundCcw(); + + int observed = rcw.ReadOnlyMarshalled; + + Assert.Equal(99, observed); + + Assert.True(TrackedIntMarshaller.ManagedToUnmanagedCount > 0, "ConvertToUnmanaged was never called for the getter return."); + Assert.True(TrackedIntMarshaller.UnmanagedToManagedCount > 0, "ConvertToManaged was never called for the getter return."); + } + + [Fact] + public void PropertyAccessorMarshalling_WriteOnly_InvokesMarshallerOnSetterOnly() + { + TrackedIntMarshaller.Reset(); + + (PropertyMarshalling impl, IPropertyMarshalling rcw) = CreatePropertyMarshallingRcwAroundCcw(); + + rcw.WriteOnlyMarshalled = 23; + + Assert.Equal(23, impl.WriteOnlySink); + + Assert.True(TrackedIntMarshaller.ManagedToUnmanagedCount > 0, "ConvertToUnmanaged was never called for the setter value."); + Assert.True(TrackedIntMarshaller.UnmanagedToManagedCount > 0, "ConvertToManaged was never called for the setter value."); + } + + // ------------------------------------------------------------------------- + // Property-level marshalling-attribute coverage. Verifies that a bare + // [MarshalUsing(...)] placed on the property declaration is propagated + // to both accessors, and that per-accessor [return:]/[param:] attributes + // take precedence over the property-level fallback. + // ------------------------------------------------------------------------- + + [Fact] + public void PropertyLevelMarshalling_Bare_InvokesMarshallerOnBothAccessors() + { + TrackedIntMarshaller.Reset(); + AlternateIntMarshaller.Reset(); + + (_, IPropertyMarshalling rcw) = CreatePropertyMarshallingRcwAroundCcw(); + + rcw.BareMarshalled = 5; + int observed = rcw.BareMarshalled; + + Assert.Equal(5, observed); + + Assert.True(TrackedIntMarshaller.ManagedToUnmanagedCount > 0, "Property-level [MarshalUsing] was not propagated to an accessor (ConvertToUnmanaged never invoked)."); + Assert.True(TrackedIntMarshaller.UnmanagedToManagedCount > 0, "Property-level [MarshalUsing] was not propagated to an accessor (ConvertToManaged never invoked)."); + Assert.Equal(0, AlternateIntMarshaller.ManagedToUnmanagedCount); + Assert.Equal(0, AlternateIntMarshaller.UnmanagedToManagedCount); + } + + [Fact] + public void PropertyLevelMarshalling_AccessorOverride_AccessorMarshallerWinsOnBothSides() + { + TrackedIntMarshaller.Reset(); + AlternateIntMarshaller.Reset(); + + (_, IPropertyMarshalling rcw) = CreatePropertyMarshallingRcwAroundCcw(); + + rcw.AccessorOverridesProperty = 11; + int observed = rcw.AccessorOverridesProperty; + + Assert.Equal(11, observed); + + Assert.True(TrackedIntMarshaller.ManagedToUnmanagedCount > 0, "Accessor-level [MarshalUsing] was overridden by the property-level fallback (ConvertToUnmanaged)."); + Assert.True(TrackedIntMarshaller.UnmanagedToManagedCount > 0, "Accessor-level [MarshalUsing] was overridden by the property-level fallback (ConvertToManaged)."); + Assert.Equal(0, AlternateIntMarshaller.ManagedToUnmanagedCount); + Assert.Equal(0, AlternateIntMarshaller.UnmanagedToManagedCount); + } + + [Fact] + public void PropertyLevelMarshalling_MixedOverride_AccessorWinsOnGetterPropertyFallbackOnSetter() + { + TrackedIntMarshaller.Reset(); + AlternateIntMarshaller.Reset(); + + (_, IPropertyMarshalling rcw) = CreatePropertyMarshallingRcwAroundCcw(); + + rcw.MixedPropertyAndAccessor = 13; + int observed = rcw.MixedPropertyAndAccessor; + + Assert.Equal(13, observed); + + // Getter has [return: MarshalUsing(typeof(TrackedIntMarshaller))]; setter has no override + // and falls back to the property-level [MarshalUsing(typeof(AlternateIntMarshaller))]. + Assert.True(TrackedIntMarshaller.ManagedToUnmanagedCount > 0, "Tracked marshaller was not invoked on the getter return path."); + Assert.True(TrackedIntMarshaller.UnmanagedToManagedCount > 0, "Tracked marshaller was not invoked on the getter return path."); + Assert.True(AlternateIntMarshaller.ManagedToUnmanagedCount > 0, "Property-level fallback marshaller was not invoked on the setter value path."); + Assert.True(AlternateIntMarshaller.UnmanagedToManagedCount > 0, "Property-level fallback marshaller was not invoked on the setter value path."); + } + + [Fact] + public void PropertyLevelMarshalling_ElementIndirection_PropertyDepth1AndAccessorDepth0Coexist() + { + TrackedIntMarshaller.Reset(); + + (_, IPropertyMarshalling rcw) = CreatePropertyMarshallingRcwAroundCcw(); + + int[] payload = new int[IPropertyMarshalling.ElementIndirectionArrayLength]; + for (int i = 0; i < payload.Length; i++) + { + payload[i] = i + 1; + } + + rcw.ElementIndirectionArray = payload; + int[] observed = rcw.ElementIndirectionArray; + + Assert.Equal(payload, observed); + + // The per-element marshaller runs once per element on each direction of each accessor: + // setter writes N elements managed-to-unmanaged (CCW receives) and reads them back + // unmanaged-to-managed (CCW stores); getter reverses. So we expect at least N invocations + // of each direction. + Assert.True( + TrackedIntMarshaller.ManagedToUnmanagedCount >= IPropertyMarshalling.ElementIndirectionArrayLength, + $"Per-element marshaller (depth 1) was dropped on the managed-to-unmanaged path: only {TrackedIntMarshaller.ManagedToUnmanagedCount} call(s) observed."); + Assert.True( + TrackedIntMarshaller.UnmanagedToManagedCount >= IPropertyMarshalling.ElementIndirectionArrayLength, + $"Per-element marshaller (depth 1) was dropped on the unmanaged-to-managed path: only {TrackedIntMarshaller.UnmanagedToManagedCount} call(s) observed."); + } + + // ------------------------------------------------------------------------- + // Shadow-property attribute propagation. A derived [GeneratedComInterface] + // emits `new T Prop { get => ((Base)this).Prop; set => ((Base)this).Prop = value; }` + // for each property inherited from a base [GeneratedComInterface]. User-defined + // property-level attributes are propagated onto the shadow header so they remain + // visible via reflection on the derived interface type. Marshalling attributes + // ([MarshalUsing], [MarshalAs]) are intentionally stripped from the shadow because + // it is a pure forwarder and the underlying base accessor already carries the + // marshalling semantics. + // ------------------------------------------------------------------------- + + private static PropertyInfo GetDerivedShadowProperty(string propertyName) + { + PropertyInfo? shadow = typeof(IShadowAttributePropagationDerived).GetProperty( + propertyName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + Assert.NotNull(shadow); + return shadow!; + } + + [Fact] + public void DerivedShadowProperty_PropagatesUserPropertyAttribute() + { + PropertyInfo shadow = GetDerivedShadowProperty(nameof(IShadowAttributePropagationBase.MarkedValue)); + + ShadowAttributeMarkerAttribute? marker = shadow.GetCustomAttribute(inherit: false); + + Assert.NotNull(marker); + Assert.Equal(7, marker!.Tag); + } + + [Fact] + public void DerivedShadowProperty_OmitsPropertyLevelMarshalUsingAttribute() + { + PropertyInfo shadow = GetDerivedShadowProperty(nameof(IShadowAttributePropagationBase.MarshalledValue)); + + Assert.Empty(shadow.GetCustomAttributes(typeof(MarshalUsingAttribute), inherit: false)); + } + + [Fact] + public void DerivedShadowProperty_KeepsUserAttributesAndStripsMarshallingAttributes() + { + PropertyInfo shadow = GetDerivedShadowProperty(nameof(IShadowAttributePropagationBase.MarkedAndMarshalledValue)); + + ShadowAttributeMarkerAttribute? marker = shadow.GetCustomAttribute(inherit: false); + Assert.NotNull(marker); + Assert.Equal(13, marker!.Tag); + + Assert.Empty(shadow.GetCustomAttributes(typeof(MarshalUsingAttribute), inherit: false)); + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/Interfaces/IDerivedExternalIndexersAndProperties.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/Interfaces/IDerivedExternalIndexersAndProperties.cs new file mode 100644 index 00000000000000..a603cece5a1a97 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/Interfaces/IDerivedExternalIndexersAndProperties.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; + +[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] +[Guid("9C13EAA7-AC8B-4B72-9E91-B3F2C0C3F8A3")] +#pragma warning disable SYSLIB1230 // Specifying 'GeneratedComInterfaceAttribute' on an interface that has a base interface defined in another assembly is not supported +internal partial interface IDerivedExternalIndexersAndProperties : IExternalIndexersAndProperties +#pragma warning restore SYSLIB1230 +{ + // Marker member appended after the inherited indexer and property slots so we can verify + // the derived vtable layout extends rather than displaces the base layout. + int Marker { get; } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/Interfaces/IDerivedFromExternalSameName.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/Interfaces/IDerivedFromExternalSameName.cs new file mode 100644 index 00000000000000..a41ad88dc39573 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/Interfaces/IDerivedFromExternalSameName.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; + +[GeneratedComInterface] +[Guid("b7c27add-cbc3-41c6-9e15-ddaf167c21a9")] +#pragma warning disable SYSLIB1230 // Specifying 'GeneratedComInterfaceAttribute' on an interface that has a base interface defined in another assembly is not supported +internal partial interface IDerivedFromExternalSameNameA : IExternalSameNameA +#pragma warning restore SYSLIB1230 +{ +} + +[GeneratedComInterface] +[Guid("5193a610-67dd-46b4-9f8c-238047c97ffb")] +#pragma warning disable SYSLIB1230 // Specifying 'GeneratedComInterfaceAttribute' on an interface that has a base interface defined in another assembly is not supported +internal partial interface IDerivedFromExternalSameNameB : IExternalSameNameB +#pragma warning restore SYSLIB1230 +{ +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs index 6d00dee71f5a9f..6c2b17a59aba1c 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CodeSnippets.cs @@ -638,7 +638,7 @@ partial interface IComInterface2 } """; - public string InterfaceWithPropertiesAndEvents => $$""" + public string InterfaceWithEvents => $$""" using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -650,14 +650,47 @@ partial interface IComInterface2 {{GeneratedComInterface()}} partial interface INativeAPI { - int {|#0:Property|} { get; set; } + event EventHandler {|#0:Event|}; - public static int StaticProperty { get; set; } + public static event EventHandler StaticEvent; + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} - event EventHandler {|#1:Event|}; + interface IOtherInterface + { + event EventHandler Event; public static event EventHandler StaticEvent; } + """; + + public string InterfaceWithProperties => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int ReadWrite { get; set; } + + int ReadOnly { get; } + + int WriteOnly { set; } + + public int Public { get; set; } + + internal int Internal { get; set; } + + unsafe int* Pointer { get; set; } + + public static int StaticProperty { get; set; } + } {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} @@ -666,11 +699,521 @@ interface IOtherInterface int Property { get; set; } public static int StaticProperty { get; set; } + } + """; - event EventHandler Event; + public string DerivedInterfaceWithNewProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; - public static event EventHandler StaticEvent; + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface IBase + { + int Shadowed { get; set; } + } + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface IDerived : IBase + { + new int Shadowed { get; set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("IBase")}} + {{_attributeProvider.AdditionalUserRequiredInterfaces("IDerived")}} + """; + + public string InterfaceWithIndexers => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int this[int i] { get; set; } + + int this[int i, int j] { get; set; } + + int this[long l] { get; } + + int this[short s] { set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithRenamedIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + [IndexerName("Foo")] + int this[int i] { get; set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string DerivedInterfaceWithNewIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface IBase + { + int this[int i] { get; set; } + } + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface IDerived : IBase + { + new int this[int i] { get; set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("IBase")}} + {{_attributeProvider.AdditionalUserRequiredInterfaces("IDerived")}} + """; + + public string DerivedInterfaceWithNewRenamedIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface IBase + { + [IndexerName("Foo")] + int this[int i] { get; set; } + } + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface IDerived : IBase + { + [IndexerName("Foo")] + new int this[int i] { get; set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("IBase")}} + {{_attributeProvider.AdditionalUserRequiredInterfaces("IDerived")}} + """; + + public string InterfaceWithDefaultImplementedIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int this[int i] + { + get => 7; + set { } + } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithExternProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + extern int {|#0:Extern|} { get; set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithRequiredProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + required int {|#0:Required|} { get; set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithInitProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int {|#0:InitOnly|} { get; init; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithDefaultImplementedMethod => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int Plain(); + + int Defaulted() { return 42; } + + int Expression() => 7; + + sealed int SealedDefaulted() => 11; + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithDefaultImplementedProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int Plain { get; set; } + + int ExpressionBodied => 42; + + int Wrapper { get { return 1; } set { _ = value; } } + + int GetterOnlyWrapper { get { return 7; } } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithPropertyMixedAccessorBodies => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int {|#0:Mixed|} { get; set { _ = value; } } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithMarshalAttributeOnDefaultImplementedMethod => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + [return: {|#0:MarshalUsing(ConstantElementCount = 1)|}] + int[] DefaultedMethod([{|#1:MarshalAs(UnmanagedType.I4)|}] int value) => new int[] { value }; + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithMarshalAttributeOnDefaultImplementedProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + [{|#0:MarshalUsing(typeof(IntMarshaller))|}] + int Defaulted { get => 1; set { _ = value; } } + } + + internal static class IntMarshaller + { + public static int ConvertToManaged(int value) => value; + public static int ConvertToUnmanaged(int value) => value; + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithExternIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + extern int {|#0:this|}[int i] { get; set; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithInitIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int {|#0:this|}[int i] { get; init; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithRefProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + ref int {|#0:RefProp|} { get; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithRefReadonlyProperty => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + ref readonly int {|#0:RefReadonlyProp|} { get; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithRefIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + ref int {|#0:this|}[int i] { get; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithRefReadonlyIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + ref readonly int {|#0:this|}[int i] { get; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithMarshalUsingCountOnlyOnPropertyGetter => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int[] {|#0:Prop|} { [return: MarshalUsing(ConstantElementCount = 10)] get; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithMarshalUsingCountOnlyOnPropertySetter => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int[] {|#0:Prop|} { [param: MarshalUsing(ConstantElementCount = 10)] set; } } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithMarshalUsingDepthOnlyOnPropertyAccessor => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int[][] {|#0:Prop|} { [return: MarshalUsing(ElementIndirectionDepth = 1)] get; } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithIndexerMixedAccessorBodies => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + int {|#0:this|}[int i] { get; set { _ = value; } } + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} + """; + + public string InterfaceWithMarshalAttributeOnDefaultImplementedIndexer => $$""" + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + {{UnmanagedObjectUnwrapper(typeof(UnmanagedObjectUnwrapper.TestUnwrapper))}} + {{GeneratedComInterface()}} + partial interface INativeAPI + { + [{|#0:MarshalUsing(typeof(IntIndexerMarshaller))|}] + int this[int i] { get => i; set { _ = value; } } + } + + internal static class IntIndexerMarshaller + { + public static int ConvertToManaged(int value) => value; + public static int ConvertToUnmanaged(int value) => value; + } + + {{_attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI")}} """; public string ForwarderWithPreserveSigAndRefKind(string refKind) => $$""" diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs index 9ab81347a93dcb..aacebfe155ad18 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs @@ -275,6 +275,116 @@ static void VerifyCompilation(Compilation comp) } } + [Fact] + public async Task IndexerOverloadsDifferingByParameterModifierEmitDistinctDeclarations() + { + var source = $$""" + using System; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + namespace Test + { + [GeneratedComInterface] + [Guid("D5C9B7D9-2A05-4F92-9C3A-7B1C5E2D8F41")] + partial interface IFoo + { + int this[int x] { get; } + int this[in int x] { set; } + } + } + """; + + var test = new VerifyCompilationTest(false) + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck | TestBehaviors.SkipGeneratedCodeCheck, + CompilationVerifier = VerifyCompilation + }; + await test.RunAsync(); + + static void VerifyCompilation(Compilation comp) + { + IndexerDeclarationSyntax[] generatedIndexers = comp.SyntaxTrees + .SelectMany(t => t.GetRoot().DescendantNodes().OfType()) + .Where(i => i.ExplicitInterfaceSpecifier is not null) + .ToArray(); + + Assert.Equal(2, generatedIndexers.Length); + + bool unmodifiedFound = false; + bool inFound = false; + foreach (IndexerDeclarationSyntax indexer in generatedIndexers) + { + SyntaxTokenList modifiers = indexer.ParameterList.Parameters[0].Modifiers; + if (modifiers.Count == 0) + { + unmodifiedFound = true; + } + else if (modifiers.Any(m => m.IsKind(SyntaxKind.InKeyword))) + { + inFound = true; + } + } + + Assert.True(unmodifiedFound, "Expected a generated indexer with no parameter modifier."); + Assert.True(inFound, "Expected a generated indexer with an 'in' parameter modifier."); + } + } + + [Fact] + public async Task ValidatePropertyAndIndexerAccessorStubsHaveAdditionalAttributes() + { + var source = $$""" + using System; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + namespace Test + { + [GeneratedComInterface] + [Guid("D5C9B7D9-2A05-4F92-9C3A-7B1C5E2D8F40")] + partial interface IFoo + { + int Value { get; set; } + int this[int i] { get; set; } + } + } + """; + + var test = new VerifyCompilationTest(false) + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck | TestBehaviors.SkipGeneratedCodeCheck, + CompilationVerifier = VerifyCompilation + }; + await test.RunAsync(); + + static void VerifyCompilation(Compilation comp) + { + string generatedCodeAttributeName = typeof(System.CodeDom.Compiler.GeneratedCodeAttribute).FullName!; + string skipLocalsInitAttributeName = typeof(System.Runtime.CompilerServices.SkipLocalsInitAttribute).FullName!; + + var accessors = comp.SyntaxTrees + .SelectMany(t => t.GetRoot().DescendantNodes().OfType()) + .Where(a => a.Kind() is SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration + && a.Body is not null) + .ToList(); + + Assert.Equal(4, accessors.Count); + + foreach (AccessorDeclarationSyntax accessor in accessors) + { + IEnumerable attributeNames = accessor.AttributeLists + .SelectMany(al => al.Attributes) + .Select(a => a.Name.ToString()); + + Assert.Contains(attributeNames, n => n.Contains(generatedCodeAttributeName)); + Assert.Contains(attributeNames, n => n.Contains(skipLocalsInitAttributeName)); + } + } + } + private static async Task VerifyGeneratedTypeShapes(string source, params string[] typeNames) { GeneratedShapeTest test = new(typeNames) diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs index 6f387d4b22e909..6308100acd6efc 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs @@ -31,16 +31,138 @@ public static IEnumerable ComInterfaceGeneratorSnippetsToCompile() .WithLocation(0) .WithArguments("IComInterface2") } }; - yield return new object[] { ID(), codeSnippets.InterfaceWithPropertiesAndEvents, new[] + yield return new object[] { ID(), codeSnippets.InterfaceWithEvents, new[] { - VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InstancePropertyDeclaredInInterface) - .WithLocation(0) - .WithArguments("Property", "INativeAPI"), VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InstanceEventDeclaredInInterface) - .WithLocation(1) + .WithLocation(0) .WithArguments("Event", "INativeAPI"), } }; + yield return new object[] { ID(), codeSnippets.InterfaceWithExternProperty, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface) + .WithLocation(0) + .WithArguments("Extern", "INativeAPI", "extern"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithRequiredProperty, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface) + .WithLocation(0) + .WithArguments("Required", "INativeAPI", "required"), + DiagnosticResult.CompilerError("CS0106").WithSpan(12, 18, 12, 26).WithArguments("required"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithInitProperty, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface) + .WithLocation(0) + .WithArguments("InitOnly", "INativeAPI", "init"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithPropertyMixedAccessorBodies, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.PropertyAccessorsMustBeAllOrNothing) + .WithLocation(0) + .WithArguments("Mixed", "INativeAPI"), + DiagnosticResult.CompilerError("CS0525").WithSpan(12, 9, 12, 14), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithMarshalAttributeOnDefaultImplementedMethod, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.MarshalAttributeOnDefaultImplementedComInterfaceMember) + .WithLocation(0) + .WithArguments("DefaultedMethod", "INativeAPI"), + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.MarshalAttributeOnDefaultImplementedComInterfaceMember) + .WithLocation(1) + .WithArguments("DefaultedMethod", "INativeAPI"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithMarshalAttributeOnDefaultImplementedProperty, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.MarshalAttributeOnDefaultImplementedComInterfaceMember) + .WithLocation(0) + .WithArguments("Defaulted", "INativeAPI"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithExternIndexer, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface) + .WithLocation(0) + .WithArguments("this[]", "INativeAPI", "extern"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithInitIndexer, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InvalidPropertyDeclarationOnGeneratedComInterface) + .WithLocation(0) + .WithArguments("this[]", "INativeAPI", "init"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithIndexerMixedAccessorBodies, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.PropertyAccessorsMustBeAllOrNothing) + .WithLocation(0) + .WithArguments("this[]", "INativeAPI"), + DiagnosticResult.CompilerError("CS0501").WithSpan(12, 23, 12, 26).WithArguments("INativeAPI.this[int].get"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithRefProperty, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.ReturnConfigurationNotSupported) + .WithLocation(0) + .WithArguments("ref return", "INativeAPI.RefProp"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithRefReadonlyProperty, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.ReturnConfigurationNotSupported) + .WithLocation(0) + .WithArguments("ref return", "INativeAPI.RefReadonlyProp"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithRefIndexer, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.ReturnConfigurationNotSupported) + .WithLocation(0) + .WithArguments("ref return", "INativeAPI.this[int]"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithRefReadonlyIndexer, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.ReturnConfigurationNotSupported) + .WithLocation(0) + .WithArguments("ref return", "INativeAPI.this[int]"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithMarshalAttributeOnDefaultImplementedIndexer, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.MarshalAttributeOnDefaultImplementedComInterfaceMember) + .WithLocation(0) + .WithArguments("this[]", "INativeAPI"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithMarshalUsingCountOnlyOnPropertyGetter, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.MarshalUsingOnPropertyAccessorMustSpecifyType) + .WithLocation(0) + .WithArguments("Prop", "INativeAPI"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithMarshalUsingCountOnlyOnPropertySetter, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.MarshalUsingOnPropertyAccessorMustSpecifyType) + .WithLocation(0) + .WithArguments("Prop", "INativeAPI"), + } }; + + yield return new object[] { ID(), codeSnippets.InterfaceWithMarshalUsingDepthOnlyOnPropertyAccessor, new[] + { + VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.MarshalUsingOnPropertyAccessorMustSpecifyType) + .WithLocation(0) + .WithArguments("Prop", "INativeAPI"), + } }; + yield return new object[] { ID(), codeSnippets.DerivedComInterfaceTypeMismatchInWrappers, new[] { VerifyComInterfaceGenerator.Diagnostic(GeneratorDiagnostics.InvalidOptionsOnInterface) @@ -1065,5 +1187,68 @@ await VerifyComInterfaceGenerator.VerifySourceGeneratorAsync( .WithLocation(0) .WithArguments($"{nameof(MarshalAsAttribute)}{Type.Delimiter}{nameof(MarshalAsAttribute.IidParameterIndex)} (supported only on [MarshalAs(UnmanagedType.Interface)] out object parameters)")); } + + // Indexer accessors that use [MarshalUsing(CountElementName = ...)] are now rejected + // upfront by MarshalUsingOnPropertyAccessorMustSpecifyType: accessor-level [MarshalUsing] + // must specify a marshaller type, so neither the count-only form nor the depth-only form + // is reachable on a property/indexer accessor without first combining the marshaller type + // and count info on a single attribute. The earlier underlying limitation (indexer + // accessors are routed through EmptyElementInfoProvider so peer-element lookups against + // the index parameter fail) is still present in the marshalling pipeline, but is hidden + // behind this diagnostic from a user's perspective. + + [Fact] + public async Task IndexerGetterWithCountElementName_ReportsDiagnostic() + { + string source = """ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + [GeneratedComInterface] + [Guid("85E4DFAA-2E8B-4A7A-9D56-DAA54CC8BF3B")] + partial interface I + { + int[] {|#0:this|}[int count] { [return: MarshalUsing(CountElementName = "count")] get; } + } + """; + + await VerifyComInterfaceGenerator.VerifySourceGeneratorAsync( + source, + VerifyComInterfaceGenerator + .Diagnostic(GeneratorDiagnostics.MarshalUsingOnPropertyAccessorMustSpecifyType) + .WithLocation(0) + .WithArguments("this[]", "I")); + } + + [Fact] + public async Task IndexerSetterWithCountElementName_ReportsDiagnostic() + { + string source = """ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [assembly:DisableRuntimeMarshalling] + + [GeneratedComInterface] + [Guid("85E4DFAA-2E8B-4A7A-9D56-DAA54CC8BF3B")] + partial interface I + { + int[] {|#0:this|}[int count] { [param: MarshalUsing(CountElementName = "count")] set; } + } + """; + + await VerifyComInterfaceGenerator.VerifySourceGeneratorAsync( + source, + VerifyComInterfaceGenerator + .Diagnostic(GeneratorDiagnostics.MarshalUsingOnPropertyAccessorMustSpecifyType) + .WithLocation(0) + .WithArguments("this[]", "I")); + } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs index 1ac9197e15d521..cf1a2b5f4e0d34 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs @@ -351,6 +351,15 @@ public static IEnumerable ComInterfaceSnippetsToCompile() yield return new object[] { ID(), codeSnippets.ForwarderWithPreserveSigAndRefKind("out") }; yield return new object[] { ID(), codeSnippets.ComInterfaceWithNativeMarshalling }; yield return new object[] { ID(), codeSnippets.DerivedComInterfaceTypeWithUnsafeBaseMethod }; + yield return new object[] { ID(), codeSnippets.InterfaceWithProperties }; + yield return new object[] { ID(), codeSnippets.DerivedInterfaceWithNewProperty }; + yield return new object[] { ID(), codeSnippets.InterfaceWithDefaultImplementedMethod }; + yield return new object[] { ID(), codeSnippets.InterfaceWithDefaultImplementedProperty }; + yield return new object[] { ID(), codeSnippets.InterfaceWithIndexers }; + yield return new object[] { ID(), codeSnippets.InterfaceWithRenamedIndexer }; + yield return new object[] { ID(), codeSnippets.DerivedInterfaceWithNewIndexer }; + yield return new object[] { ID(), codeSnippets.DerivedInterfaceWithNewRenamedIndexer }; + yield return new object[] { ID(), codeSnippets.InterfaceWithDefaultImplementedIndexer }; } public static IEnumerable ManagedToUnmanagedComInterfaceSnippetsToCompile() diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDerivedIndexers.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDerivedIndexers.cs new file mode 100644 index 00000000000000..6a1ad4fce7249b --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDerivedIndexers.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + // Default-named indexer in the base; the derived interface shadows it with `new`. + // All indexers in a type must share the same effective IndexerName, so this interface + // contains only one indexer. + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IDerivedIndexersBase + { + public const string IID = "7E5C3F1B-0E2D-4F7A-A38C-2BFD8D5C1E72"; + + int this[int i] { get; set; } + } + + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IDerivedIndexers : IDerivedIndexersBase + { + public new const string IID = "B0C8D7A4-3FF8-4F1B-9C29-6B7D2C6F1A21"; + + new int this[int i] { get; set; } + } + + // [IndexerName]-renamed base + derived shadow. The derived shadow MUST also carry + // [IndexerName("Foo")] so its IL accessor names are get_Foo/set_Foo (not the default + // get_Item/set_Item) and stay in sync with the base accessor identity. + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IRenamedDerivedIndexersBase + { + public const string IID = "A48F37C2-0B6E-4B0A-9C77-7E1D3A19A4F6"; + + [IndexerName("Foo")] + int this[long l] { get; set; } + } + + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IRenamedDerivedIndexers : IRenamedDerivedIndexersBase + { + public new const string IID = "D9B12C71-44E8-4F6A-AB55-39C8E7F0C5D2"; + + [IndexerName("Foo")] + new int this[long l] { get; set; } + } +} + diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDerivedProperties.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDerivedProperties.cs new file mode 100644 index 00000000000000..2f3ea3236a97b7 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDerivedProperties.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IDerivedProperties : IProperties + { + public new const string IID = "28DBA1C7-AE33-47B9-AAE2-C7DF49F0042E"; + + int DerivedIntProperty { get; set; } + + string DerivedStringProperty { get; set; } + + int DerivedReadOnlyInt { get; } + } + + [GeneratedComClass] + internal partial class DerivedProperties : Properties, IDerivedProperties + { + private int _derivedInt; + private string _derivedString = string.Empty; + + public int DerivedIntProperty + { + get => _derivedInt; + set => _derivedInt = value; + } + + public string DerivedStringProperty + { + get => _derivedString; + set => _derivedString = value; + } + + public int DerivedReadOnlyInt => 2222; + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDimMembers.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDimMembers.cs new file mode 100644 index 00000000000000..df32f91e0c47f3 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IDimMembers.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + [GeneratedComInterface] + [Guid(IID)] + internal partial interface IDimMembers + { + public const string IID = "BCFE6CEA-9CE3-48B0-A19F-EC73EE9D3FA1"; + + // ABI methods that flow through the COM vtable. + double ReadValue(); + void WriteValue(double value); + int ReadCount(); + void WriteSink(int value); + + // Default-implemented wrapper property: no vtable slot, lights up + // property-style access on RCWs and CCWs by forwarding to the ABI + // accessors above. + double Value + { + get => ReadValue(); + set => WriteValue(value); + } + + int CountProperty => ReadCount(); + + int SinkProperty + { + set => WriteSink(value); + } + + // Default-implemented helper method (no vtable slot). + int DoubleIt(int value) => value * 2; + } + + [GeneratedComClass] + internal partial class DimMembers : IDimMembers + { + private double _value; + private int _writeOnlyIntSink; + + public double ReadValue() => _value; + + public void WriteValue(double value) => _value = value; + + public int ReadCount() => 111; + + public void WriteSink(int value) => _writeOnlyIntSink = value; + + // Non-interface accessor so the WriteSink side effect is observable from tests. + internal int WriteOnlyIntSink => _writeOnlyIntSink; + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IIndexers.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IIndexers.cs new file mode 100644 index 00000000000000..5b2868962060de --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IIndexers.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IIndexers + { + public const string IID = "7C0E5C9D-5F3E-49E0-8C9A-1A8A2A2C9B1A"; + + // Default-named single-parameter indexer. + int this[int i] { get; set; } + + // Two-parameter indexer overload. With the same default name ("Item") this exercises + // overloaded vtable slot placement (each get/set pair gets its own adjacent slot pair). + int this[int i, int j] { get; set; } + + // Read-only indexer overload by parameter type. Single vtable slot. + int this[long l] { get; } + + // Write-only indexer overload by parameter type. Single vtable slot. + int this[short s] { set; } + + // String-keyed, string-valued indexer overload. Exercises the UTF-16 string marshalling + // pipeline on both the index parameter and the value parameter / return type — distinct + // from the blittable primitive marshalling paths above. + string this[string key] { get; set; } + } + + [GeneratedComClass] + internal partial class Indexers : IIndexers + { + // Backing store keyed by index — small dictionary kept inline to avoid allocator churn in tests. + private int _singleValue; + private int _twoParamValue; + private int _writeOnlyShortSink; + private readonly Dictionary _stringMap = new(); + + public int this[int i] + { + get => _singleValue + i; + set => _singleValue = value - i; + } + + public int this[int i, int j] + { + get => _twoParamValue + (i * 100) + j; + set => _twoParamValue = value - (i * 100) - j; + } + + public int this[long l] => unchecked((int)(l * 7)); + + public int this[short s] + { + set => _writeOnlyShortSink = value + s; + } + + public string this[string key] + { + get => _stringMap.TryGetValue(key, out string? v) ? v : string.Empty; + set => _stringMap[key] = value; + } + + // Non-interface accessor so the WriteOnly setter's side effect is observable from tests. + internal int WriteOnlyShortSink => _writeOnlyShortSink; + } + + // A second indexer interface that uses [IndexerName] so the generated IL accessor names are + // get_Element / set_Element instead of the default get_Item / set_Item. This exercises the + // [IndexerName]-propagation path in the derived-interface shadow emitter as well. + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IRenamedIndexer + { + public const string IID = "9F5A6D1E-1F4B-4E22-9A7C-1E0E33D5A2E1"; + + [IndexerName("Element")] + int this[int i] { get; set; } + } + + [GeneratedComClass] + internal partial class RenamedIndexer : IRenamedIndexer + { + private int _value; + + public int this[int i] + { + get => _value + i; + set => _value = value - i; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IProperties.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IProperties.cs new file mode 100644 index 00000000000000..dff2c127651e54 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IProperties.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + internal partial interface IProperties + { + public const string IID = "F8E80A83-53F7-4F90-8009-C64FD405F445"; + + int IntProperty { get; set; } + + int ReadOnlyInt { get; } + + int WriteOnlyInt { set; } + + Guid GuidProperty { get; set; } + + string StringProperty { get; set; } + + IProperties? Self { get; set; } + } + + [GeneratedComClass] + internal partial class Properties : IProperties + { + private int _int; + private int _writeOnlyIntSink; + private Guid _guid; + private string _string = string.Empty; + private IProperties? _self; + + public int IntProperty + { + get => _int; + set => _int = value; + } + + public int ReadOnlyInt => 111; + + public int WriteOnlyInt + { + set => _writeOnlyIntSink = value; + } + + public Guid GuidProperty + { + get => _guid; + set => _guid = value; + } + + public string StringProperty + { + get => _string; + set => _string = value; + } + + public IProperties? Self + { + get => _self; + set => _self = value; + } + + // Non-interface accessor so the WriteOnlyInt setter's side effect is observable from tests. + internal int WriteOnlyIntSink => _writeOnlyIntSink; + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IPropertyMarshalling.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IPropertyMarshalling.cs new file mode 100644 index 00000000000000..7623525463306a --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IPropertyMarshalling.cs @@ -0,0 +1,176 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using System.Threading; + +namespace SharedTypes.ComInterfaces +{ + [GeneratedComInterface] + [Guid(IID)] + internal partial interface IPropertyMarshalling + { + public const string IID = "21DD41F1-7B6E-4F75-8C50-2D8E68C5C0BD"; + + int TargetScoped + { + [return: MarshalUsing(typeof(TrackedIntMarshaller))] + get; + [param: MarshalUsing(typeof(TrackedIntMarshaller))] + set; + } + + int ReadOnlyMarshalled + { + [return: MarshalUsing(typeof(TrackedIntMarshaller))] + get; + } + + int WriteOnlyMarshalled + { + [param: MarshalUsing(typeof(TrackedIntMarshaller))] + set; + } + + [MarshalUsing(typeof(TrackedIntMarshaller))] + int BareMarshalled { get; set; } + + [MarshalUsing(typeof(AlternateIntMarshaller))] + int AccessorOverridesProperty + { + [return: MarshalUsing(typeof(TrackedIntMarshaller))] + get; + [param: MarshalUsing(typeof(TrackedIntMarshaller))] + set; + } + + [MarshalUsing(typeof(AlternateIntMarshaller))] + int MixedPropertyAndAccessor + { + [return: MarshalUsing(typeof(TrackedIntMarshaller))] + get; + set; + } + + public const int ElementIndirectionArrayLength = 4; + + // The property-level [MarshalUsing] supplies the per-element marshaller at depth 1 + // (one indirection past the int[] value itself). Each accessor-level [MarshalUsing] + // supplies the depth-0 marshaller for the array and the constant collection size for + // the COM ABI. The accessor-level depth-0 attribute must NOT displace the property- + // level depth-1 attribute: dedup of property-vs-accessor [MarshalUsing] is partitioned + // by ElementIndirectionDepth. + [MarshalUsing(typeof(TrackedIntMarshaller), ElementIndirectionDepth = 1)] + int[] ElementIndirectionArray + { + [return: MarshalUsing(typeof(ArrayMarshaller), ConstantElementCount = ElementIndirectionArrayLength)] + get; + [param: MarshalUsing(typeof(ArrayMarshaller), ConstantElementCount = ElementIndirectionArrayLength)] + set; + } + } + + [GeneratedComClass] + internal partial class PropertyMarshalling : IPropertyMarshalling + { + private int _writeOnly; + + public int TargetScoped { get; set; } + public int ReadOnlyMarshalled { get; set; } = 99; + public int WriteOnlyMarshalled { get => _writeOnly; set => _writeOnly = value; } + public int BareMarshalled { get; set; } + public int AccessorOverridesProperty { get; set; } + public int MixedPropertyAndAccessor { get; set; } + public int[] ElementIndirectionArray { get; set; } = new int[IPropertyMarshalling.ElementIndirectionArrayLength]; + + public int WriteOnlySink => _writeOnly; + } + + [GeneratedComInterface] + [Guid(IID)] + internal partial interface IIndexerMarshalling + { + public const string IID = "9F8E7D6C-5B4A-3210-FEDC-BA9876543210"; + + [MarshalUsing(typeof(TrackedIntMarshaller))] + int this[int index] { get; set; } + } + + [GeneratedComClass] + internal partial class IndexerMarshalling : IIndexerMarshalling + { + private int _value; + + public int this[int index] + { + get => _value + index; + set => _value = value - index; + } + } + + [CustomMarshaller(typeof(int), MarshalMode.ManagedToUnmanagedIn, typeof(TrackedIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.UnmanagedToManagedIn, typeof(TrackedIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.ManagedToUnmanagedOut, typeof(TrackedIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.UnmanagedToManagedOut, typeof(TrackedIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.ElementIn, typeof(TrackedIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.ElementOut, typeof(TrackedIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.ElementRef, typeof(TrackedIntMarshaller))] + internal static class TrackedIntMarshaller + { + private static int s_managedToUnmanagedCount; + private static int s_unmanagedToManagedCount; + + public static int ManagedToUnmanagedCount => s_managedToUnmanagedCount; + public static int UnmanagedToManagedCount => s_unmanagedToManagedCount; + + public static void Reset() + { + s_managedToUnmanagedCount = 0; + s_unmanagedToManagedCount = 0; + } + + public static int ConvertToUnmanaged(int managed) + { + Interlocked.Increment(ref s_managedToUnmanagedCount); + return managed; + } + + public static int ConvertToManaged(int unmanaged) + { + Interlocked.Increment(ref s_unmanagedToManagedCount); + return unmanaged; + } + } + + [CustomMarshaller(typeof(int), MarshalMode.ManagedToUnmanagedIn, typeof(AlternateIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.UnmanagedToManagedIn, typeof(AlternateIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.ManagedToUnmanagedOut, typeof(AlternateIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.UnmanagedToManagedOut, typeof(AlternateIntMarshaller))] + internal static class AlternateIntMarshaller + { + private static int s_managedToUnmanagedCount; + private static int s_unmanagedToManagedCount; + + public static int ManagedToUnmanagedCount => s_managedToUnmanagedCount; + public static int UnmanagedToManagedCount => s_unmanagedToManagedCount; + + public static void Reset() + { + s_managedToUnmanagedCount = 0; + s_unmanagedToManagedCount = 0; + } + + public static int ConvertToUnmanaged(int managed) + { + Interlocked.Increment(ref s_managedToUnmanagedCount); + return managed; + } + + public static int ConvertToManaged(int unmanaged) + { + Interlocked.Increment(ref s_unmanagedToManagedCount); + return unmanaged; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IPropertyShadowing.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IPropertyShadowing.cs new file mode 100644 index 00000000000000..de2bb589f717f8 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IPropertyShadowing.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + [GeneratedComInterface] + [Guid("92541FF7-D3EE-4797-A691-638D89C3C9F6")] + internal partial interface IPropertyShadowingBase + { + int Shadowed { get; set; } + + int NotShadowed { get; set; } + } + + [GeneratedComInterface] + [Guid("96AC9769-EF1B-4A91-A6FC-B59770A3CA05")] + internal partial interface IPropertyShadowingDerived : IPropertyShadowingBase + { + new int Shadowed { get; set; } + } + + [GeneratedComClass] + [Guid("E51ACEC5-A7FA-4572-94FC-08B5F54405EE")] + /// + /// Implements with explicit interface + /// implementations so that the base and derived Shadowed property accessors + /// write to distinct backing fields. This lets tests verify that each property + /// occupies its own vtable slot and that QI navigation routes correctly. + /// + internal partial class PropertyShadowingImpl : IPropertyShadowingDerived + { + private int _baseShadowed; + private int _derivedShadowed; + private int _notShadowed; + + int IPropertyShadowingBase.Shadowed + { + get => _baseShadowed; + set => _baseShadowed = value; + } + + int IPropertyShadowingDerived.Shadowed + { + get => _derivedShadowed; + set => _derivedShadowed = value; + } + + public int NotShadowed + { + get => _notShadowed; + set => _notShadowed = value; + } + + internal int BaseShadowedSink => _baseShadowed; + + internal int DerivedShadowedSink => _derivedShadowed; + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IShadowPropertyAttributePropagation.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IShadowPropertyAttributePropagation.cs new file mode 100644 index 00000000000000..52c014de0d05eb --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/ComInterfaces/IShadowPropertyAttributePropagation.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + [GeneratedComInterface] + [Guid(IID)] + internal partial interface IShadowAttributePropagationBase + { + public const string IID = "67DFE7D0-95C3-4463-B59F-0825EB08AF24"; + + [ShadowAttributeMarker(7)] + int MarkedValue { get; set; } + + [MarshalUsing(typeof(ShadowAttributeIntMarshaller))] + int MarshalledValue { get; set; } + + [ShadowAttributeMarker(13)] + [MarshalUsing(typeof(ShadowAttributeIntMarshaller))] + int MarkedAndMarshalledValue { get; set; } + } + + [GeneratedComInterface] + [Guid(IID)] + internal partial interface IShadowAttributePropagationDerived : IShadowAttributePropagationBase + { + public new const string IID = "3463BFF4-7AAD-41B3-9110-E7E0D587E474"; + } + + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + internal sealed class ShadowAttributeMarkerAttribute : Attribute + { + public ShadowAttributeMarkerAttribute(int tag) + { + Tag = tag; + } + + public int Tag { get; } + } + + [CustomMarshaller(typeof(int), MarshalMode.ManagedToUnmanagedIn, typeof(ShadowAttributeIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.UnmanagedToManagedIn, typeof(ShadowAttributeIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.ManagedToUnmanagedOut, typeof(ShadowAttributeIntMarshaller))] + [CustomMarshaller(typeof(int), MarshalMode.UnmanagedToManagedOut, typeof(ShadowAttributeIntMarshaller))] + internal static class ShadowAttributeIntMarshaller + { + public static int ConvertToUnmanaged(int managed) => managed; + public static int ConvertToManaged(int unmanaged) => unmanaged; + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/DerivedProperties.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/DerivedProperties.cs new file mode 100644 index 00000000000000..76e8160c232440 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/DerivedProperties.cs @@ -0,0 +1,388 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; +using static System.Runtime.InteropServices.ComWrappers; + +namespace NativeExports.ComInterfaceGenerator +{ + public static unsafe class DerivedPropertiesExports + { + [UnmanagedCallersOnly(EntryPoint = "new_derived_properties")] + public static void* CreateComObject() + { + MyComWrapper cw = new(); + var myObject = new DerivedPropertiesImplementation(); + nint ptr = cw.GetOrCreateComInterfaceForObject(myObject, CreateComInterfaceFlags.None); + + return (void*)ptr; + } + + private sealed class MyComWrapper : ComWrappers + { + private static void* _basePropertiesVtable; + private static void* BasePropertiesVtable + { + get + { + if (_basePropertiesVtable is not null) + { + return _basePropertiesVtable; + } + + void** vtable = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DerivedPropertiesExports), sizeof(void*) * 13); + GetIUnknownImpl(out var fpQueryInterface, out var fpAddReference, out var fpRelease); + vtable[0] = (void*)fpQueryInterface; + vtable[1] = (void*)fpAddReference; + vtable[2] = (void*)fpRelease; + vtable[3] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetIntProperty; + vtable[4] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetIntProperty; + vtable[5] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetReadOnlyInt; + vtable[6] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetWriteOnlyInt; + vtable[7] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetGuidProperty; + vtable[8] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetGuidProperty; + vtable[9] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetStringProperty; + vtable[10] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetStringProperty; + vtable[11] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetSelf; + vtable[12] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetSelf; + _basePropertiesVtable = vtable; + return _basePropertiesVtable; + } + } + + private static void* _derivedPropertiesVtable; + private static void* DerivedPropertiesVtable + { + get + { + if (_derivedPropertiesVtable is not null) + { + return _derivedPropertiesVtable; + } + + void** vtable = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DerivedPropertiesExports), sizeof(void*) * 18); + GetIUnknownImpl(out var fpQueryInterface, out var fpAddReference, out var fpRelease); + vtable[0] = (void*)fpQueryInterface; + vtable[1] = (void*)fpAddReference; + vtable[2] = (void*)fpRelease; + // Inherited slots — must match the IProperties vtable layout above byte-for-byte + // so the IDerivedProperties RCW can dispatch base accessors through this pointer. + vtable[3] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetIntProperty; + vtable[4] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetIntProperty; + vtable[5] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetReadOnlyInt; + vtable[6] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetWriteOnlyInt; + vtable[7] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetGuidProperty; + vtable[8] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetGuidProperty; + vtable[9] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetStringProperty; + vtable[10] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetStringProperty; + vtable[11] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetSelf; + vtable[12] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetSelf; + // Derived-only slots. + vtable[13] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetDerivedIntProperty; + vtable[14] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetDerivedIntProperty; + vtable[15] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetDerivedStringProperty; + vtable[16] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.SetDerivedStringProperty; + vtable[17] = (delegate* unmanaged)&DerivedPropertiesImplementation.ABI.GetDerivedReadOnlyInt; + _derivedPropertiesVtable = vtable; + return _derivedPropertiesVtable; + } + } + + protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) + { + if (obj is DerivedPropertiesImplementation) + { + ComInterfaceEntry* comInterfaceEntry = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(DerivedPropertiesImplementation), sizeof(ComInterfaceEntry) * 2); + // Listing the derived entry first means the default IUnknown for the CCW exposes + // the derived vtable, so a QI for IID_IDerivedProperties resolves directly. + comInterfaceEntry[0].IID = new Guid(IDerivedProperties.IID); + comInterfaceEntry[0].Vtable = (nint)DerivedPropertiesVtable; + comInterfaceEntry[1].IID = new Guid(IProperties.IID); + comInterfaceEntry[1].Vtable = (nint)BasePropertiesVtable; + count = 2; + return comInterfaceEntry; + } + count = 0; + return null; + } + + protected override object? CreateObject(nint externalComObject, CreateObjectFlags flags) => throw new NotImplementedException(); + protected override void ReleaseObjects(IEnumerable objects) => throw new NotImplementedException(); + } + + private sealed class DerivedPropertiesImplementation : IProperties, IDerivedProperties + { + private int _int; + private int _writeOnlyIntSink; + private Guid _guid; + private string _string = string.Empty; + private int _derivedInt; + private string _derivedString = string.Empty; + + int IProperties.IntProperty + { + get => _int; + set => _int = value; + } + + int IProperties.ReadOnlyInt => 111; + + int IProperties.WriteOnlyInt + { + set => _writeOnlyIntSink = value; + } + + Guid IProperties.GuidProperty + { + get => _guid; + set => _guid = value; + } + + string IProperties.StringProperty + { + get => _string; + set => _string = value; + } + + // Self is not implemented through this shim; the ABI thunks below short-circuit it. + IProperties? IProperties.Self + { + get => null; + set { } + } + + int IDerivedProperties.DerivedIntProperty + { + get => _derivedInt; + set => _derivedInt = value; + } + + string IDerivedProperties.DerivedStringProperty + { + get => _derivedString; + set => _derivedString = value; + } + + int IDerivedProperties.DerivedReadOnlyInt => 2222; + + // Hand-rolled COM ABI thunks. Each one matches the unmanaged signature the + // generated RCW expects on the corresponding vtable slot. Thunks for the + // inherited (IProperties) slots are referenced from both the base and + // derived vtables; ComInterfaceDispatch.GetInstance recovers the same + // managed object regardless of which vtable entry was invoked. + // + // Every [UnmanagedCallersOnly] method in this assembly is also picked up by + // DNNE as a C export; the entry-point names below are explicitly unique to + // avoid clashing with the IProperties thunks of the same shape declared in + // Properties.cs. These thunks are never invoked through their DNNE C entry + // point; only their function-pointer addresses (used as vtable slots) matter. + public static class ABI + { + [UnmanagedCallersOnly(EntryPoint = "dprops_get_IntProperty")] + public static int GetIntProperty(void* @this, int* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).IntProperty; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_set_IntProperty")] + public static int SetIntProperty(void* @this, int newValue) + { + try + { + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).IntProperty = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_get_ReadOnlyInt")] + public static int GetReadOnlyInt(void* @this, int* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).ReadOnlyInt; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_set_WriteOnlyInt")] + public static int SetWriteOnlyInt(void* @this, int newValue) + { + try + { + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).WriteOnlyInt = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + // dnne_guid is declared by Properties.cs for the whole assembly; do not redeclare it here. + [UnmanagedCallersOnly(EntryPoint = "dprops_get_GuidProperty")] + public static int GetGuidProperty(void* @this, [DNNE.C99Type("struct dnne_guid*")] Guid* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).GuidProperty; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_set_GuidProperty")] + public static int SetGuidProperty(void* @this, [DNNE.C99Type("struct dnne_guid")] Guid newValue) + { + try + { + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).GuidProperty = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_get_StringProperty")] + public static int GetStringProperty(void* @this, ushort** value) + { + try + { + string currValue = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).StringProperty; + *value = Utf16StringMarshaller.ConvertToUnmanaged(currValue); + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_set_StringProperty")] + public static int SetStringProperty(void* @this, ushort* newValue) + { + try + { + string value = Utf16StringMarshaller.ConvertToManaged(newValue); + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).StringProperty = value; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_get_Self")] + public static int GetSelf(void* @this, void** value) + { + *value = null; + return 0; + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_set_Self")] + public static int SetSelf(void* @this, void* value) + { + return 0; + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_get_DerivedIntProperty")] + public static int GetDerivedIntProperty(void* @this, int* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).DerivedIntProperty; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_set_DerivedIntProperty")] + public static int SetDerivedIntProperty(void* @this, int newValue) + { + try + { + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).DerivedIntProperty = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_get_DerivedStringProperty")] + public static int GetDerivedStringProperty(void* @this, ushort** value) + { + try + { + string currValue = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).DerivedStringProperty; + *value = Utf16StringMarshaller.ConvertToUnmanaged(currValue); + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_set_DerivedStringProperty")] + public static int SetDerivedStringProperty(void* @this, ushort* newValue) + { + try + { + string value = Utf16StringMarshaller.ConvertToManaged(newValue); + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).DerivedStringProperty = value; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly(EntryPoint = "dprops_get_DerivedReadOnlyInt")] + public static int GetDerivedReadOnlyInt(void* @this, int* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).DerivedReadOnlyInt; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/Indexers.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/Indexers.cs new file mode 100644 index 00000000000000..05493f8deaa270 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/Indexers.cs @@ -0,0 +1,252 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; +using static System.Runtime.InteropServices.ComWrappers; + +namespace NativeExports.ComInterfaceGenerator +{ + public static unsafe class IndexersExports + { + [UnmanagedCallersOnly(EntryPoint = "new_indexers")] + public static void* CreateComObject() + { + MyComWrapper cw = new(); + var myObject = new IndexersImplementation(); + nint ptr = cw.GetOrCreateComInterfaceForObject(myObject, CreateComInterfaceFlags.None); + + return (void*)ptr; + } + + private sealed class MyComWrapper : ComWrappers + { + private static void* _vtable; + private static void* Vtable + { + get + { + if (_vtable is not null) + { + return _vtable; + } + + // 3 IUnknown slots + 8 indexer-accessor slots, matching the source-declaration + // order baked into the generator's InterfaceImplementationVtable for IIndexers: + // 3: get_Item(int) + // 4: set_Item(int) + // 5: get_Item(int, int) + // 6: set_Item(int, int) + // 7: get_Item(long) -- read-only + // 8: set_Item(short) -- write-only + // 9: get_Item(string) + // 10: set_Item(string) + void** vtable = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IndexersExports), sizeof(void*) * 11); + GetIUnknownImpl(out var fpQueryInterface, out var fpAddReference, out var fpRelease); + vtable[0] = (void*)fpQueryInterface; + vtable[1] = (void*)fpAddReference; + vtable[2] = (void*)fpRelease; + vtable[3] = (delegate* unmanaged)&IndexersImplementation.ABI.GetItemInt; + vtable[4] = (delegate* unmanaged)&IndexersImplementation.ABI.SetItemInt; + vtable[5] = (delegate* unmanaged)&IndexersImplementation.ABI.GetItemIntInt; + vtable[6] = (delegate* unmanaged)&IndexersImplementation.ABI.SetItemIntInt; + vtable[7] = (delegate* unmanaged)&IndexersImplementation.ABI.GetItemLong; + vtable[8] = (delegate* unmanaged)&IndexersImplementation.ABI.SetItemShort; + vtable[9] = (delegate* unmanaged)&IndexersImplementation.ABI.GetItemString; + vtable[10] = (delegate* unmanaged)&IndexersImplementation.ABI.SetItemString; + _vtable = vtable; + return _vtable; + } + } + + protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) + { + if (obj is IndexersImplementation) + { + ComInterfaceEntry* comInterfaceEntry = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(IndexersImplementation), sizeof(ComInterfaceEntry)); + comInterfaceEntry->IID = new Guid(IIndexers.IID); + comInterfaceEntry->Vtable = (nint)Vtable; + count = 1; + return comInterfaceEntry; + } + count = 0; + return null; + } + + protected override object? CreateObject(nint externalComObject, CreateObjectFlags flags) => throw new NotImplementedException(); + protected override void ReleaseObjects(IEnumerable objects) => throw new NotImplementedException(); + } + + private sealed class IndexersImplementation : IIndexers + { + // Matches the semantics of SharedTypes.ComInterfaces.Indexers so the tests + // observe identical behavior regardless of whether the CCW is generator- + // produced or native-shim produced. + private int _singleValue; + private int _twoParamValue; + private int _writeOnlyShortSink; + private readonly Dictionary _stringMap = new(); + + int IIndexers.this[int i] + { + get => _singleValue + i; + set => _singleValue = value - i; + } + + int IIndexers.this[int i, int j] + { + get => _twoParamValue + (i * 100) + j; + set => _twoParamValue = value - (i * 100) - j; + } + + int IIndexers.this[long l] => unchecked((int)(l * 7)); + + int IIndexers.this[short s] + { + set => _writeOnlyShortSink = value + s; + } + + string IIndexers.this[string key] + { + get => _stringMap.TryGetValue(key, out string? v) ? v : string.Empty; + set => _stringMap[key] = value; + } + + // Hand-rolled COM ABI thunks. Each one matches the unmanaged signature the + // generated RCW expects on the corresponding vtable slot. + public static class ABI + { + [UnmanagedCallersOnly] + public static int GetItemInt(void* @this, int i, int* value) + { + try + { + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + *value = impl[i]; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetItemInt(void* @this, int i, int newValue) + { + try + { + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + impl[i] = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int GetItemIntInt(void* @this, int i, int j, int* value) + { + try + { + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + *value = impl[i, j]; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetItemIntInt(void* @this, int i, int j, int newValue) + { + try + { + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + impl[i, j] = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int GetItemLong(void* @this, long l, int* value) + { + try + { + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + *value = impl[l]; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetItemShort(void* @this, short s, int newValue) + { + try + { + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + impl[s] = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int GetItemString(void* @this, ushort* key, ushort** value) + { + try + { + string managedKey = Utf16StringMarshaller.ConvertToManaged(key); + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + string result = impl[managedKey]; + // Ownership of the returned buffer transfers to the caller; the + // generated RCW frees it via Utf16StringMarshaller.Free. + *value = Utf16StringMarshaller.ConvertToUnmanaged(result); + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetItemString(void* @this, ushort* key, ushort* newValue) + { + try + { + string managedKey = Utf16StringMarshaller.ConvertToManaged(key); + string managedValue = Utf16StringMarshaller.ConvertToManaged(newValue); + IIndexers impl = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this); + impl[managedKey] = managedValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/Properties.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/Properties.cs new file mode 100644 index 00000000000000..9a5a9d51fa5c14 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/NativeExports/ComInterfaceGenerator/Properties.cs @@ -0,0 +1,250 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using SharedTypes.ComInterfaces; +using static System.Runtime.InteropServices.ComWrappers; + +namespace NativeExports.ComInterfaceGenerator +{ + public static unsafe class PropertiesExports + { + [UnmanagedCallersOnly(EntryPoint = "new_properties")] + public static void* CreateComObject() + { + MyComWrapper cw = new(); + var myObject = new PropertiesImplementation(); + nint ptr = cw.GetOrCreateComInterfaceForObject(myObject, CreateComInterfaceFlags.None); + + return (void*)ptr; + } + + private sealed class MyComWrapper : ComWrappers + { + private static void* _vtable; + private static void* Vtable + { + get + { + if (_vtable is not null) + { + return _vtable; + } + + void** vtable = (void**)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(PropertiesExports), sizeof(void*) * 13); + GetIUnknownImpl(out var fpQueryInterface, out var fpAddReference, out var fpRelease); + vtable[0] = (void*)fpQueryInterface; + vtable[1] = (void*)fpAddReference; + vtable[2] = (void*)fpRelease; + vtable[3] = (delegate* unmanaged)&PropertiesImplementation.ABI.GetIntProperty; + vtable[4] = (delegate* unmanaged)&PropertiesImplementation.ABI.SetIntProperty; + vtable[5] = (delegate* unmanaged)&PropertiesImplementation.ABI.GetReadOnlyInt; + vtable[6] = (delegate* unmanaged)&PropertiesImplementation.ABI.SetWriteOnlyInt; + vtable[7] = (delegate* unmanaged)&PropertiesImplementation.ABI.GetGuidProperty; + vtable[8] = (delegate* unmanaged)&PropertiesImplementation.ABI.SetGuidProperty; + vtable[9] = (delegate* unmanaged)&PropertiesImplementation.ABI.GetStringProperty; + vtable[10] = (delegate* unmanaged)&PropertiesImplementation.ABI.SetStringProperty; + vtable[11] = (delegate* unmanaged)&PropertiesImplementation.ABI.GetSelf; + vtable[12] = (delegate* unmanaged)&PropertiesImplementation.ABI.SetSelf; + _vtable = vtable; + return _vtable; + } + } + + protected override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) + { + if (obj is PropertiesImplementation) + { + ComInterfaceEntry* comInterfaceEntry = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(PropertiesImplementation), sizeof(ComInterfaceEntry)); + comInterfaceEntry->IID = new Guid(IProperties.IID); + comInterfaceEntry->Vtable = (nint)Vtable; + count = 1; + return comInterfaceEntry; + } + count = 0; + return null; + } + + protected override object? CreateObject(nint externalComObject, CreateObjectFlags flags) => throw new NotImplementedException(); + protected override void ReleaseObjects(IEnumerable objects) => throw new NotImplementedException(); + } + + private sealed class PropertiesImplementation : IProperties + { + private int _int; + private int _writeOnlyIntSink; + private Guid _guid; + private string _string = string.Empty; + + int IProperties.IntProperty + { + get => _int; + set => _int = value; + } + + int IProperties.ReadOnlyInt => 111; + + int IProperties.WriteOnlyInt + { + set => _writeOnlyIntSink = value; + } + + Guid IProperties.GuidProperty + { + get => _guid; + set => _guid = value; + } + + string IProperties.StringProperty + { + get => _string; + set => _string = value; + } + + // Self is not implemented through this shim; the ABI thunks below short-circuit it. + // The Self round-trip is covered by the in-process CCW-around-RCW test. + IProperties? IProperties.Self + { + get => null; + set { } + } + + // Hand-rolled COM ABI thunks. Each one matches the unmanaged signature the + // generated RCW expects on the corresponding vtable slot. + public static class ABI + { + [UnmanagedCallersOnly] + public static int GetIntProperty(void* @this, int* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).IntProperty; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetIntProperty(void* @this, int newValue) + { + try + { + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).IntProperty = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int GetReadOnlyInt(void* @this, int* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).ReadOnlyInt; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetWriteOnlyInt(void* @this, int newValue) + { + try + { + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).WriteOnlyInt = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [DNNE.C99DeclCode("struct dnne_guid { uint8_t b[16]; };")] + [UnmanagedCallersOnly] + public static int GetGuidProperty(void* @this, [DNNE.C99Type("struct dnne_guid*")] Guid* value) + { + try + { + *value = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).GuidProperty; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetGuidProperty(void* @this, [DNNE.C99Type("struct dnne_guid")] Guid newValue) + { + try + { + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).GuidProperty = newValue; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int GetStringProperty(void* @this, ushort** value) + { + try + { + string currValue = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).StringProperty; + *value = Utf16StringMarshaller.ConvertToUnmanaged(currValue); + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int SetStringProperty(void* @this, ushort* newValue) + { + try + { + string value = Utf16StringMarshaller.ConvertToManaged(newValue); + ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)@this).StringProperty = value; + return 0; + } + catch (Exception e) + { + return e.HResult; + } + } + + [UnmanagedCallersOnly] + public static int GetSelf(void* @this, void** value) + { + *value = null; + return 0; + } + + [UnmanagedCallersOnly] + public static int SetSelf(void* @this, void* value) + { + return 0; + } + } + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/BaseInterfaces/IExternalIndexersAndPropertiesDifferentAssembly.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/BaseInterfaces/IExternalIndexersAndPropertiesDifferentAssembly.cs new file mode 100644 index 00000000000000..b81241f549e91d --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/BaseInterfaces/IExternalIndexersAndPropertiesDifferentAssembly.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + // Cross-assembly base interface mixing overloaded indexers and regular properties. Indexer + // accessors all share the same IL name (get_Item / set_Item) regardless of parameter shape, + // so this verifies the externally-defined-accessor path distinguishes vtable slots by full + // signature rather than by accessor name. Regular property accessors are emitted alongside + // the indexer slots in declaration order to confirm both surfaces participate in the same + // cross-assembly inheritance path. + [GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)] + [Guid(IID)] + public partial interface IExternalIndexersAndProperties + { + public const string IID = "4E3F2A57-2DD6-4F32-9B17-B0B6DEE6B7E6"; + + int this[int i] { get; set; } + int this[int i, int j] { get; set; } + int this[long l] { get; } + int this[short s] { set; } + + int Value { get; set; } + string Name { get; } + int WriteOnlyCounter { set; } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/BaseInterfaces/ISameNameMethodsDifferentAssembly.cs b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/BaseInterfaces/ISameNameMethodsDifferentAssembly.cs new file mode 100644 index 00000000000000..f0145bf1e2ce0d --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/TestAssets/SharedTypes/BaseInterfaces/ISameNameMethodsDifferentAssembly.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace SharedTypes.ComInterfaces +{ + // Two disjoint base COM interfaces in a separate assembly that happen to declare a + // parameterless method of the same name (only the return type differs). Cross-assembly + // consumers that derive a [GeneratedComInterface] from each must still build, even though + // both inherited MyMethod members reduce to the same IL name with no parameters to + // distinguish them. + [GeneratedComInterface] + [Guid(IID_A)] + public partial interface IExternalSameNameA + { + public const string IID_A = "2d3b434a-9119-45c6-9f40-1d35bb38d494"; + + double MyMethod(); + } + + [GeneratedComInterface] + [Guid(IID_B)] + public partial interface IExternalSameNameB + { + public const string IID_B = "5ef8dd8a-6c27-4724-8645-4406a4e45a8d"; + + int MyMethod(); + } +} diff --git a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml index 849c92f43630d9..95960d00eda311 100644 --- a/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml +++ b/src/libraries/apicompat/ApiCompatBaseline.NetCoreAppLatestStable.xml @@ -1021,6 +1021,12 @@ net10.0/System.Runtime.dll net11.0/System.Runtime.dll + + CP0015 + T:System.Runtime.InteropServices.Marshalling.MarshalUsingAttribute:[T:System.AttributeUsageAttribute] + net10.0/System.Runtime.InteropServices.dll + net11.0/System.Runtime.InteropServices.dll + CP0015 T:System.Text.Json.Serialization.JsonIgnoreAttribute:[T:System.AttributeUsageAttribute]