From f5f08686faf9b857548e8e492345b97550cc3a52 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 3 Apr 2023 15:19:04 -0700 Subject: [PATCH 1/3] Derived Interface support in ComInterfaceGenerator Implement support for deriving COM interfaces to append their members to the vtables of their base interface type. --- .../DerivedComInterfaces.md | 191 +++++++ .../ComInterfaceGenerator.cs | 273 +++++++--- .../GeneratorDiagnostics.cs | 11 + .../Resources/Strings.resx | 471 +++++++++--------- .../Resources/xlf/Strings.cs.xlf | 15 + .../Resources/xlf/Strings.de.xlf | 15 + .../Resources/xlf/Strings.es.xlf | 15 + .../Resources/xlf/Strings.fr.xlf | 15 + .../Resources/xlf/Strings.it.xlf | 15 + .../Resources/xlf/Strings.ja.xlf | 15 + .../Resources/xlf/Strings.ko.xlf | 15 + .../Resources/xlf/Strings.pl.xlf | 15 + .../Resources/xlf/Strings.pt-BR.xlf | 15 + .../Resources/xlf/Strings.ru.xlf | 15 + .../Resources/xlf/Strings.tr.xlf | 15 + .../Resources/xlf/Strings.zh-Hans.xlf | 15 + .../Resources/xlf/Strings.zh-Hant.xlf | 15 + .../TypeNames.cs | 2 + .../GeneratedComInterfaceTests.cs | 57 +++ .../IDerivedComInterface.cs | 19 + .../ComInterfaceGenerator.Tests/RcwTests.cs | 37 -- .../CodeSnippets.cs | 39 +- .../ComInterfaceGeneratorOutputShape.cs | 33 ++ .../CompileFails.cs | 57 +++ .../Compiles.cs | 7 + 25 files changed, 1047 insertions(+), 345 deletions(-) create mode 100644 docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md create mode 100644 src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs create mode 100644 src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedComInterface.cs delete mode 100644 src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/RcwTests.cs create mode 100644 src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs diff --git a/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md b/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md new file mode 100644 index 00000000000000..83a6d7eb65b8d5 --- /dev/null +++ b/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md @@ -0,0 +1,191 @@ +# Derived Interfaces and COM + +In the ComInterfaceGenerator, we want to improve the experience when writing COM interfaces that derive from other COM interfaces. The built-in system has some quirks due to differences between how interfaces work in C# in comparison to COM interfaces in C++. + +## COM Interface Inheritance in C++ + +In C++, developers can declare COM interfaces that derive from other COM interfaces as follows: + +```cpp +struct IComInterface : public IUnknown +{ + STDMETHOD(Method)() = 0; + STDMETHOD(Method2)() = 0; +}; + +struct IComInterface2 : public IComInterface +{ + STDMETHOD(Method3)() = 0; +}; +``` + +This declaration style is regularly as a mechanism to add methods to COM objects without changing existing interfaces, which would be a breaking change. This inheritance mechanism results in the following vtable layouts: + +| `IComInterface` VTable slot | Method name | +|-----------------------------|-------------| +| 0 | `IUnknown::QueryInterface` | +| 1 | `IUnknown::AddRef` | +| 2 | `IUnknown::Release` | +| 3 | `IComInterface::Method` | +| 4 | `IComInterface::Method2` | + + +| `IComInterface2` VTable slot | Method name | +|-----------------------------|-------------| +| 0 | `IUnknown::QueryInterface` | +| 1 | `IUnknown::AddRef` | +| 2 | `IUnknown::Release` | +| 3 | `IComInterface::Method` | +| 4 | `IComInterface::Method2` | +| 5 | `IComInterface2::Method3` | + +As a result, it is very easy to call a method defined on `IComInterface` from an `IComInterface2*`. Specifically, calling a method on a base interface does not require a call to `QueryInterface` to get a pointer to the base interface. Additionally, C++ allows an implicit conversion from `IComInterface2*` to `IComInterface*`, which is well defined and allows avoiding a `QueryInterface` call again. As a result, in C or C++, you never need to call `QueryInterface` to get to the base type if you do not want to, which can allow some performance improvements. + +> Note: WinRT interfaces do not follow this inheritance model. They are defined to follow the same model as the `[ComImport]`-based COM interop model in .NET. + +## COM Interface Inheritance in .NET with `[ComImport]` + +In .NET, C# code that looks like interface inheritance isn't actually interface inheritance. Let's look at the following code: + +```csharp +interface I +{ + void Method1(); +} +interface J : I +{ + void Method2(); +} +``` + +This code does not say that "`J` implements `I`." It actually says "any type that implements `J` must also implement `I`." This difference leads to the fundamental design decision that makes interface inheritance in `[ComImport]`-based interop unergonomic. In .NET's COM interop, interfaces are always considered on their own; an interface's base interface list has no impact on any calculations to determing a vtable for a given .NET interface. + +As a result, the natural equivalent of the above provided C++ COM interface example leads to a different vtable layout. + +C# code: +```csharp +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +interface IComInterface +{ + void Method(); + void Method2(); +} + +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +interface IComInterface2 : IComInterface +{ + void Method3(); +} +``` + +VTable layouts: + +| `IComInterface` VTable slot | Method name | +|-----------------------------|-------------| +| 0 | `IUnknown::QueryInterface` | +| 1 | `IUnknown::AddRef` | +| 2 | `IUnknown::Release` | +| 3 | `IComInterface::Method` | +| 4 | `IComInterface::Method2` | + + +| `IComInterface2` VTable slot | Method name | +|-----------------------------|-------------| +| 0 | `IUnknown::QueryInterface` | +| 1 | `IUnknown::AddRef` | +| 2 | `IUnknown::Release` | +| 3 | `IComInterface2::Method3` | + +As these vtables differ from the C++ example, this will lead to serious problems at runtime. The correct definition of these interfaces in C# for the `[ComImport]` interop system is as follows: + +```csharp +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +interface IComInterface +{ + void Method(); + void Method2(); +} + +[ComImport] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +interface IComInterface2 : IComInterface +{ + new void Method(); + new void Method2(); + void Method3(); +} +``` + +Each method from the base interface types must be redeclared, as at the metadata level, `IComInterface2` does not implement `IComInterface`, but only specifies that implementors of `IComInterface2` must also implement `IComInterface`. This design decision is an ugly wart on the built-in COM interop system, so we want to improve this in the new source-generated COM interop system. + +## COM Interface Inheritance in Source-Generated COM + +In the new source-generated COM model, we will enable writing COM interfaces using natural C# interface "inheritance" with some restrictions to ensure a valid model. Here's an example: + +C# code: +```csharp +[GeneratedComInterface] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +interface IComInterface +{ + void Method(); + void Method2(); +} + +[GeneratedComInterface] +[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +interface IComInterface2 : IComInterface +{ + void Method3(); +} +``` + +VTable slots: + +| `IComInterface` VTable slot | Method name | +|-----------------------------|-------------| +| 0 | `IUnknown::QueryInterface` | +| 1 | `IUnknown::AddRef` | +| 2 | `IUnknown::Release` | +| 3 | `IComInterface::Method` | +| 4 | `IComInterface::Method2` | + + +| `IComInterface2` VTable slot | Method name | +|-----------------------------|-------------| +| 0 | `IUnknown::QueryInterface` | +| 1 | `IUnknown::AddRef` | +| 2 | `IUnknown::Release` | +| 3 | `IComInterface::Method` | +| 4 | `IComInterface::Method2` | +| 5 | `IComInterface2::Method3` | + +As you can see, this new model will allow the "natural" pattern for authoring .NET interfaces in C# result in the "natural" vtable layout as if the type was written in C++. There is a restriction on this pattern to ensure an easy-to-construct model: + +- A `[GeneratedComInterface]`-attributed type may inherit from at most one `[GeneratedComInterface]`-attribute type. + +This ensures that our COM-interop .NET interfaces will always follow the model of single inheritance, which is the only direct inheritance model that COM allows. + +### Designing for Performance + +This new model has really nice ergonomics for authoring, but it has some performance deficits compared to the built-in model. In particular, when running the following code: + +```csharp +IComInterface2 obj = /* get the COM object */; +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. + +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: + +1. Always try to emit shadowing methods with marshalling and error out (either with C# compiler errors or in the generator itself) when the marshallers are not accessible. +2. Don't emit shadowing methods when the marshallers aren't accessible. +3. Emit shadowing stubs that call the base interface method when the marshallers are not accessible. + +Option 2 is very fragile and makes it really easy to accidentally cause a binary breaking change, so we should either go with Option 1 or 3. For simplicity of implementation, we will go with Option 1 until we get customer feedback that this is a serious issue, at which point we will consider switching to Option 3. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs index 17704038a19bcd..26216e391c4e1d 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs @@ -2,13 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using static System.Net.Mime.MediaTypeNames; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.Interop @@ -66,12 +69,66 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var interfacesToGenerate = interfacesWithDiagnostics.Where(static data => data.Diagnostic is null); var invalidTypeDiagnostics = interfacesWithDiagnostics.Where(static data => data.Diagnostic is not null); - var interfaceContexts = interfacesToGenerate.Select((data, ct) => + var interfaceBaseInfo = interfacesToGenerate.Collect().SelectMany((data, ct) => { - // Start at offset 3 as 0-2 are IUnknown. - // TODO: Calculate starting offset based on base types. + ImmutableArray<(int StartingOffset, ManagedTypeInfo? BaseInterface)>.Builder baseInterfaceInfo = ImmutableArray.CreateBuilder<(int, ManagedTypeInfo?)>(data.Length); + // Track the calculated last offsets of the interfaces. + // If a type has invalid methods, we'll count them and issue an error when generating code for that + // interface. + Dictionary derivedNextOffset = new(SymbolEqualityComparer.Default); + foreach (var iface in data) + { + var (starting, baseType, _) = CalculateOffsetsForInterface(iface.Symbol); + baseInterfaceInfo.Add((starting, baseType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(baseType) : null)); + } + return baseInterfaceInfo.MoveToImmutable(); + + (int Starting, INamedTypeSymbol? BaseType, int DerivedStarting) CalculateOffsetsForInterface(INamedTypeSymbol iface) + { + INamedTypeSymbol? baseInterface = null; + foreach (var implemented in iface.Interfaces) + { + if (implemented.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) + { + // We'll filter out cases where there's multiple matching interfaces when determining + // if this is a valid candidate for generation. + Debug.Assert(baseInterface is null); + baseInterface = implemented; + } + } + + // Cache the starting offsets for each base interface so we don't have to recalculate them. + int startingOffset = 3; + if (baseInterface is not null) + { + if (!derivedNextOffset.TryGetValue(baseInterface, out int offset)) + { + offset = CalculateOffsetsForInterface(baseInterface).DerivedStarting; + } + + startingOffset = offset; + } + + // This calculation isn't strictly accurate. This will count methods that aren't in the same declaring syntax as the attribute on the interface, + // but we'll emit an error later if that's a problem. We also can't detect this error if the base type is in metadata. + int ifaceDerivedNextOffset = startingOffset + iface.GetMembers().Where(m => m is IMethodSymbol { IsStatic: false }).Count(); + derivedNextOffset[iface] = ifaceDerivedNextOffset; + + return (startingOffset, baseInterface, ifaceDerivedNextOffset); + } + }); + + // Zip the interface base information back with the symbols and syntax for the interface + // to calculate the interface context. + // The generator infrastructure preserves ordering of the tables once Select statements are in use, + // so we can rely on the order matching here. + var interfaceContexts = interfacesToGenerate + .Zip(interfaceBaseInfo.Select((data, ct) => data.StartingOffset)) + .Select((data, ct) => + { + var (iface, startingOffset) = data; Guid? guid = null; - var guidAttr = data.Symbol.GetAttributes().Where(attr => attr.AttributeClass.ToDisplayString() == TypeNames.System_Runtime_InteropServices_GuidAttribute).SingleOrDefault(); + var guidAttr = iface.Symbol.GetAttributes().Where(attr => attr.AttributeClass.ToDisplayString() == TypeNames.System_Runtime_InteropServices_GuidAttribute).SingleOrDefault(); if (guidAttr is not null) { string? guidstr = guidAttr.ConstructorArguments.SingleOrDefault().Value as string; @@ -79,10 +136,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) guid = new Guid(guidstr); } return new ComInterfaceContext( - ManagedTypeInfo.CreateTypeInfoForTypeSymbol(data.Symbol), - new ContainingSyntaxContext(data.Syntax), - new ContainingSyntax(data.Syntax.Modifiers, data.Syntax.Kind(), data.Syntax.Identifier, data.Syntax.TypeParameterList), - 3, + ManagedTypeInfo.CreateTypeInfoForTypeSymbol(iface.Symbol), + new ContainingSyntaxContext(iface.Syntax), + new ContainingSyntax(iface.Syntax.Modifiers, iface.Syntax.Kind(), iface.Syntax.Identifier, iface.Syntax.TypeParameterList), + startingOffset, guid ?? Guid.Empty); }); @@ -90,8 +147,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Zip the incremental interface context back with the symbols and syntax for the interface // to calculate the methods to generate. - // The generator infrastructure preserves ordering of the tables once Select statements are in use, - // so we can rely on the order matching here. var interfacesWithMethods = interfacesToGenerate .Zip(interfaceContexts) .Select(static (data, ct) => @@ -260,7 +315,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) generateStubInformation .Collect() .SelectMany(static (data, ct) => GroupContextsForInterfaceGeneration(data.CastArray())) - .Select(static (vtable, ct) => GenerateImplementationVTable(ImmutableArray.CreateRange(vtable.Array.Cast()))) + .Zip(interfaceBaseInfo) + .Select(static (data, ct) => GenerateImplementationVTable(ImmutableArray.CreateRange(data.Left.Array.Cast()), data.Right)) .WithTrackingName(StepNames.GenerateNativeToManagedVTable) .WithComparer(SyntaxEquivalentComparer.Instance) .SelectNormalized(); @@ -487,6 +543,20 @@ private static IncrementalMethodStubGenerationContext CalculateStubInformation(M // Missing Guid } + // TODO: Error if more than one GeneratedComInterface base interface type. + INamedTypeSymbol? baseInterface = null; + foreach (var implemented in type.Interfaces) + { + if (implemented.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) + { + if (baseInterface is not null) + { + return Diagnostic.Create(GeneratorDiagnostics.MultipleComInterfaceBaseTypesAttribute, syntax.Identifier.GetLocation(), type.ToDisplayString()); + } + baseInterface = implemented; + } + } + return null; } @@ -575,7 +645,7 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTableMethods(Im private static readonly MethodDeclarationSyntax CreateManagedVirtualFunctionTableMethodTemplate = MethodDeclaration(VoidStarStarSyntax, CreateManagedVirtualFunctionTableMethodName) .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword)); - private static InterfaceDeclarationSyntax GenerateImplementationVTable(ImmutableArray interfaceMethodStubs) + private static InterfaceDeclarationSyntax GenerateImplementationVTable(ImmutableArray interfaceMethodStubs, (int StartingOffset, ManagedTypeInfo? BaseInterface) baseInterfaceTypeInfo) { const string vtableLocalName = "vtable"; var interfaceType = interfaceMethodStubs[0].OriginalDefiningType; @@ -618,70 +688,121 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTable(Immutable SizeOfExpression(PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword)))), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1 + interfaceMethodStubs.Max(x => x.VtableIndexData.Index)))))))))))); - var fillIUnknownSlots = Block() - .AddStatements( - // nint v0, v1, v2; - LocalDeclarationStatement(VariableDeclaration(ParseTypeName("nint")) - .AddVariables( - VariableDeclarator("v0"), - VariableDeclarator("v1"), - VariableDeclarator("v2") - )), - // ComWrappers.GetIUnknownImpl(out v0, out v1, out v2); - ExpressionStatement( - InvocationExpression( - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - ParseTypeName(TypeNames.System_Runtime_InteropServices_ComWrappers), - IdentifierName("GetIUnknownImpl"))) - .AddArgumentListArguments( - Argument(IdentifierName("v0")) + BlockSyntax fillBaseInterfaceSlots; + + if (baseInterfaceTypeInfo.BaseInterface is null) + { + // If we don't have a base interface, we need to manually fill in the base iUnknown slots. + fillBaseInterfaceSlots = Block() + .AddStatements( + // nint v0, v1, v2; + LocalDeclarationStatement(VariableDeclaration(ParseTypeName("nint")) + .AddVariables( + VariableDeclarator("v0"), + VariableDeclarator("v1"), + VariableDeclarator("v2") + )), + // ComWrappers.GetIUnknownImpl(out v0, out v1, out v2); + ExpressionStatement( + InvocationExpression( + MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, + ParseTypeName(TypeNames.System_Runtime_InteropServices_ComWrappers), + IdentifierName("GetIUnknownImpl"))) + .AddArgumentListArguments( + Argument(IdentifierName("v0")) + .WithRefKindKeyword(Token(SyntaxKind.OutKeyword)), + Argument(IdentifierName("v1")) .WithRefKindKeyword(Token(SyntaxKind.OutKeyword)), - Argument(IdentifierName("v1")) - .WithRefKindKeyword(Token(SyntaxKind.OutKeyword)), - Argument(IdentifierName("v2")) - .WithRefKindKeyword(Token(SyntaxKind.OutKeyword)))), - // m_vtable[0] = (void*)v0; - ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, - ElementAccessExpression( - IdentifierName(vtableLocalName), - BracketedArgumentList( - SingletonSeparatedList( - Argument( - LiteralExpression( - SyntaxKind.NumericLiteralExpression, - Literal(0)))))), - CastExpression( - PointerType( - PredefinedType(Token(SyntaxKind.VoidKeyword))), - IdentifierName("v0")))), - // m_vtable[1] = (void*)v1; - ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, - ElementAccessExpression( - IdentifierName(vtableLocalName), - BracketedArgumentList( - SingletonSeparatedList( - Argument( - LiteralExpression( - SyntaxKind.NumericLiteralExpression, - Literal(1)))))), - CastExpression( - PointerType( - PredefinedType(Token(SyntaxKind.VoidKeyword))), - IdentifierName("v1")))), - // m_vtable[2] = (void*)v2; - ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, - ElementAccessExpression( - IdentifierName(vtableLocalName), - BracketedArgumentList( - SingletonSeparatedList( - Argument( - LiteralExpression( - SyntaxKind.NumericLiteralExpression, - Literal(2)))))), - CastExpression( - PointerType( - PredefinedType(Token(SyntaxKind.VoidKeyword))), - IdentifierName("v2"))))); + Argument(IdentifierName("v2")) + .WithRefKindKeyword(Token(SyntaxKind.OutKeyword)))), + // m_vtable[0] = (void*)v0; + ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + ElementAccessExpression( + IdentifierName(vtableLocalName), + BracketedArgumentList( + SingletonSeparatedList( + Argument( + LiteralExpression( + SyntaxKind.NumericLiteralExpression, + Literal(0)))))), + CastExpression( + PointerType( + PredefinedType(Token(SyntaxKind.VoidKeyword))), + IdentifierName("v0")))), + // m_vtable[1] = (void*)v1; + ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + ElementAccessExpression( + IdentifierName(vtableLocalName), + BracketedArgumentList( + SingletonSeparatedList( + Argument( + LiteralExpression( + SyntaxKind.NumericLiteralExpression, + Literal(1)))))), + CastExpression( + PointerType( + PredefinedType(Token(SyntaxKind.VoidKeyword))), + IdentifierName("v1")))), + // m_vtable[2] = (void*)v2; + ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + ElementAccessExpression( + IdentifierName(vtableLocalName), + BracketedArgumentList( + SingletonSeparatedList( + Argument( + LiteralExpression( + SyntaxKind.NumericLiteralExpression, + Literal(2)))))), + CastExpression( + PointerType( + PredefinedType(Token(SyntaxKind.VoidKeyword))), + IdentifierName("v2"))))); + } + else + { + // NativeMemory.Copy(StrategyBasedComWrappers.DefaultIUnknownInteraceDetailsStrategy.GetIUnknownDerivedDetails(typeof().TypeHandle).ManagedVirtualMethodTable, vtable, (nuint)(sizeof(void*) * )); + fillBaseInterfaceSlots = Block() + .AddStatements( + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParseTypeName(TypeNames.System_Runtime_InteropServices_NativeMemory), + IdentifierName("Copy"))) + .WithArgumentList( + ArgumentList( + SeparatedList( + new[] + { + Argument( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + ParseTypeName(TypeNames.StrategyBasedComWrappers), + IdentifierName("DefaultIUnknownInterfaceDetailsStrategy")), + IdentifierName("GetIUnknownDerivedDetails"))) + .WithArgumentList( + ArgumentList( + SingletonSeparatedList( + Argument( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + TypeOfExpression( + ParseTypeName(baseInterfaceTypeInfo.BaseInterface.FullTypeName)), + IdentifierName("TypeHandle")))))), + IdentifierName("ManagedVirtualMethodTable"))), + Argument(IdentifierName(vtableLocalName)), + Argument(CastExpression(IdentifierName("nuint"), + ParenthesizedExpression( + BinaryExpression(SyntaxKind.MultiplyExpression, + SizeOfExpression(PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword)))), + LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(baseInterfaceTypeInfo.StartingOffset)))))) + }))))); + } var vtableSlotAssignments = VirtualMethodPointerStubGenerator.GenerateVirtualMethodTableSlotAssignments(interfaceMethodStubs, vtableLocalName); @@ -691,7 +812,7 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTable(Immutable .WithBody( Block( vtableDeclarationStatement, - fillIUnknownSlots, + fillBaseInterfaceSlots, vtableSlotAssignments, ReturnStatement(IdentifierName(vtableLocalName))))); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs index 777680dba75fc1..5d563b290942f3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs @@ -22,6 +22,7 @@ public class Ids public const string ConfigurationNotSupported = Prefix + "1052"; public const string MethodNotDeclaredInAttributedInterface = Prefix + "1091"; public const string InvalidGeneratedComInterfaceAttributeUsage = Prefix + "1092"; + public const string MultipleComInterfaceBaseTypes = Prefix + "1093"; } private const string Category = "ComInterfaceGenerator"; @@ -186,6 +187,16 @@ public class Ids isEnabledByDefault: true, description: GetResourceString(nameof(SR.InvalidGeneratedComInterfaceAttributeUsageDescription))); + public static readonly DiagnosticDescriptor MultipleComInterfaceBaseTypesAttribute = + new DiagnosticDescriptor( + Ids.MultipleComInterfaceBaseTypes, + GetResourceString(nameof(SR.MultipleComInterfaceBaseTypesTitle)), + GetResourceString(nameof(SR.MultipleComInterfaceBaseTypesMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(SR.MultipleComInterfaceBaseTypesDescription))); + private readonly List _diagnostics = new List(); public IEnumerable Diagnostics => _diagnostics; diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx index 48383800438a9e..0d9b0b6631e087 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx @@ -1,231 +1,240 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Source-generated P/Invokes will ignore any configuration that is not supported. - - - The '{0}' configuration is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. - - - The specified marshalling configuration is not supported by source-generated P/Invokes. {0}. - - - The specified '{0}' configuration for parameter '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. - - - The specified '{0}' configuration for the return value of method '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. - - - The specified value '{0}' for '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. - - - Specified configuration is not supported by source-generated P/Invokes. - - - The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' is invalid. - - - The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on method '{0}' is invalid. {1} - {1} is a message containing additional details about what is not valid - - - 'StringMarshallingCustomType' must be specified when 'StringMarshalling' is set to 'StringMarshalling.Custom'. - - - 'StringMarshalling' should be set to 'StringMarshalling.Custom' when 'StringMarshallingCustomType' is specified. - - - For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. - - - The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of parameter '{1}'. - - - {0} The generated source will not handle marshalling of parameter '{1}'. - {0} is a message containing additional details about what is not supported -{1} is the name of the parameter - - - The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of the return value of method '{1}'. - - - {0} The generated source will not handle marshalling of the return value of method '{1}'. - {0} is a message containing additional details about what is not supported -{1} is the name of the method - - - Specified type is not supported by source-generated P/Invokes - - - Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'. - - - Methods marked with 'LibraryImportAttribute' should be 'static', 'partial', and non-generic. P/Invoke source generation will ignore methods that are non-'static', non-'partial', or generic. - - - Method '{0}' should be 'static', 'partial', and non-generic when marked with 'LibraryImportAttribute'. P/Invoke source generation will ignore method '{0}'. - - - Invalid 'VirtualMethodIndexAttribute' usage - - - The configuration of 'ExceptionMarshalling' and 'ExceptionMarshallingCustomType' is invalid. - - - The configuration of 'ExceptionMarshalling' and 'ExceptionMarshallingCustomType' on method '{0}' is invalid. {1} - {1} is a message containing additional details about what is not valid - - - 'ExceptionMarshallingCustomType' must be specified when 'ExceptionMarshalling' is set to 'ExceptionMarshalling.Custom'. - - - 'ExceptionMarshalling' should be set to 'ExceptionMarshalling.Custom' when 'ExceptionMarshallingCustomType' is specified. - - - The provided value is not a known flag of the 'ExceptionMarshalling' enum. - - - 'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type. - - - Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. - - - Containing type of method with VirtualMethodIndexAttribute does not have a UnmanagedObjectUnwrapperAttribute. - - - All methods must be declared in the same partial definition of a 'GeneratedComInterface'-attributed interface type to ensure reliable calculation for virtual method table offsets. - - - The method '{0}' is declared on a different partial definition of the interface '{1}' than the definition that has the 'GeneratedComInterface' attribute - - - Method is declared in different partial declaration than the 'GeneratedComInterface' attribute. - - - Interfaces attributed with 'GeneratedComInterfaceAttribute' must be partial and must specify a GUID with 'System.Runtime.InteropServices.GuidAttribute'. - - - Interface '{0}' is attributed with 'GeneratedComInterfaceAttribute' but is missing 'System.Runtime.InteropServices.GuidAttribute'. - - - Invalid 'GeneratedComInterfaceAttribute' usage. - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Source-generated P/Invokes will ignore any configuration that is not supported. + + + The '{0}' configuration is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + The specified marshalling configuration is not supported by source-generated P/Invokes. {0}. + + + The specified '{0}' configuration for parameter '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + The specified '{0}' configuration for the return value of method '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + The specified value '{0}' for '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + Specified configuration is not supported by source-generated P/Invokes. + + + The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' is invalid. + + + The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on method '{0}' is invalid. {1} + {1} is a message containing additional details about what is not valid + + + 'StringMarshallingCustomType' must be specified when 'StringMarshalling' is set to 'StringMarshalling.Custom'. + + + 'StringMarshalling' should be set to 'StringMarshalling.Custom' when 'StringMarshallingCustomType' is specified. + + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. + + + The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of parameter '{1}'. + + + {0} The generated source will not handle marshalling of parameter '{1}'. + {0} is a message containing additional details about what is not supported +{1} is the name of the parameter + + + The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of the return value of method '{1}'. + + + {0} The generated source will not handle marshalling of the return value of method '{1}'. + {0} is a message containing additional details about what is not supported +{1} is the name of the method + + + Specified type is not supported by source-generated P/Invokes + + + Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'. + + + Methods marked with 'LibraryImportAttribute' should be 'static', 'partial', and non-generic. P/Invoke source generation will ignore methods that are non-'static', non-'partial', or generic. + + + Method '{0}' should be 'static', 'partial', and non-generic when marked with 'LibraryImportAttribute'. P/Invoke source generation will ignore method '{0}'. + + + Invalid 'VirtualMethodIndexAttribute' usage + + + The configuration of 'ExceptionMarshalling' and 'ExceptionMarshallingCustomType' is invalid. + + + The configuration of 'ExceptionMarshalling' and 'ExceptionMarshallingCustomType' on method '{0}' is invalid. {1} + {1} is a message containing additional details about what is not valid + + + 'ExceptionMarshallingCustomType' must be specified when 'ExceptionMarshalling' is set to 'ExceptionMarshalling.Custom'. + + + 'ExceptionMarshalling' should be set to 'ExceptionMarshalling.Custom' when 'ExceptionMarshallingCustomType' is specified. + + + The provided value is not a known flag of the 'ExceptionMarshalling' enum. + + + 'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type. + + + Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'. + + + Containing type of method with VirtualMethodIndexAttribute does not have a UnmanagedObjectUnwrapperAttribute. + + + All methods must be declared in the same partial definition of a 'GeneratedComInterface'-attributed interface type to ensure reliable calculation for virtual method table offsets. + + + The method '{0}' is declared on a different partial definition of the interface '{1}' than the definition that has the 'GeneratedComInterface' attribute + + + Method is declared in different partial declaration than the 'GeneratedComInterface' attribute. + + + Interfaces attributed with 'GeneratedComInterfaceAttribute' must be partial and must specify a GUID with 'System.Runtime.InteropServices.GuidAttribute'. + + + Interface '{0}' is attributed with 'GeneratedComInterfaceAttribute' but is missing 'System.Runtime.InteropServices.GuidAttribute'. + + + Invalid 'GeneratedComInterfaceAttribute' usage. + + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + \ No newline at end of file diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf index 5e3bf889ff9741..2fb6cde49c22cb 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf @@ -147,6 +147,21 @@ Metoda je deklarovaná v jiné částečné deklaraci než atribut GeneratedComInterface. + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. U typů, které nejsou podporovány zdrojem generovanými voláními P/Invoke, bude výsledné volání P/Invoke záviset na podkladovém modulu runtime, aby určený typ zařadil. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf index 2883a6d442081c..20b89838183638 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.de.xlf @@ -147,6 +147,21 @@ Die Methode wird in einer anderen partiellen Deklaration als das GeneratedComInterface-Attribut deklariert. + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. Bei Typen, die von dquellgenerierten P/Invokes nicht unterstützt werden, basiert der resultierende P/Invoke auf der zugrunde liegenden Laufzeit, um den angegebenen Typ zu marshallen. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf index b81f00acea64b4..186d4d8b5e236f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.es.xlf @@ -147,6 +147,21 @@ El método se declara en una declaración parcial diferente a la del atributo "GeneratedComInterface". + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. Para los tipos que no son compatibles con P/Invokes de un generador de código fuente, el P/Invoke resultante se basará en el entorno de ejecución subyacente para serializar las referencias del tipo especificado. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf index 6784bff965f5b3..5935b03f28b3ec 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.fr.xlf @@ -147,6 +147,21 @@ La méthode est déclarée dans une déclaration partielle différente de l’attribut « GeneratedComInterface ». + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. Pour les types qui ne sont pas pris en charge par les P/Invok générés par la source, le P/Invoke résultant se base sur le runtime sous-jacent pour marshaler le type spécifié. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf index a761329e8fbcf0..10d6e996e5c100 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.it.xlf @@ -147,6 +147,21 @@ Il metodo è dichiarato in una dichiarazione parziale diversa rispetto all'attributo 'GeneratedComInterface'. + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. Per i tipi non supportati da P/Invoke generati dall'origine, il P/Invoke risultante si baserà sul runtime sottostante per effettuare il marshalling del tipo specificato. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf index 44ee84c3661b99..93d0ca3c441353 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ja.xlf @@ -147,6 +147,21 @@ メソッドは、'GeneratedComInterface' 属性とは異なる部分宣言で宣言されています。 + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. ソース生成済みの P/Invoke でサポートされていない型である場合、生成された P/Invoke は、基礎となるなるランタイムに依存して、指定された型をマーシャリングします。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf index fa664f762edc3c..58847814ca1c16 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ko.xlf @@ -147,6 +147,21 @@ 메소드가 'GeneratedComInterface' 속성과 다른 부분 선언에서 선언되었습니다. + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. 소스 생성 P/Invoke에서 지원하지 않는 형식의 경우 결과 P/Invoke는 기본 런타임에 의존하여 지정된 형식을 마샬링합니다. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf index 8b118da21a7fd5..65395bb8b563be 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pl.xlf @@ -147,6 +147,21 @@ Metoda jest zadeklarowana w innej deklaracji częściowej niż atrybut „GeneratedComInterface”. + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. W przypadku typów, które nie są obsługiwane przez funkcję P/Invokes generowaną przez źródło, wynikowa funkcja P/Invoke będzie polegać na bazowym środowisku uruchomieniowym, aby skierować określony typ. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf index 6aa82e455dd71a..6b3010308fa52f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.pt-BR.xlf @@ -147,6 +147,21 @@ O método é declarado em uma declaração parcial diferente do atributo "GeneratedComInterface". + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. Para tipos sem suporte por P/Invokes gerados pela origem, o P/Invoke resultante dependerá do tempo de execução subjacente para realizar marshaling no tipo especificado. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf index 180bd1e169fbde..e33d15425f9fe3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.ru.xlf @@ -147,6 +147,21 @@ Метод объявлен в частичном объявлении, отличном от атрибута GeneratedComInterface. + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. Для типов, которые не поддерживаются в P/Invoke с созданием источника, в полученном P/Invoke для маршализации указанного типа будет использоваться среда выполнения. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf index 73da227ecd8317..e8fb6da003cf04 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.tr.xlf @@ -147,6 +147,21 @@ Metot, 'GeneratedComInterface' özniteliğinden farklı bir kısmi bildirimde bildirilmiş. + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. Kaynak tarafından oluşturulan P/Invokes tarafından desteklenmeyen türler için, elde edilen P/Invoke, belirtilen türü sıralamak için temel alınan çalışma zamanını kullanır. diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf index 592aa2172a54e0..07d89755dae81b 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hans.xlf @@ -147,6 +147,21 @@ 方法是在不同于 “GeneratedComInterface” 属性的部分声明中声明的。 + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. 对于源生成的 P/Invoke 不支持的类型,生成的 P/Invoke 将依赖基础运行时来封送指定的类型。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf index 8fdc8c26bf8466..0a2c1d1004a49f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.zh-Hant.xlf @@ -147,6 +147,21 @@ 方法是在不同於 'GeneratedComInterface' 屬性的部分宣告中宣告。 + + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + A 'GeneratedComInterfaceAttribute'-attributed interface can only derive from at most one other 'GeneratedComInterfaceAttribute'-attributed interface. + + + + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + Interface '{0}' is derived from two or more interfaces attributed with 'GeneratedComInterfaceAttribute'. + + + + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + Specified interface derives from two or more 'GeneratedComInterfaceAttribute'-attributed interfaces. + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. 對於來源產生的 P/Invokes 不支援的類型,產生的 P/Invoke 將依賴基礎運行時間來封送指定的類型。 diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs index fa271c817da5e8..4862761b026222 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs @@ -119,6 +119,8 @@ public static string MarshalEx(InteropGenerationOptions options) public const string System_Runtime_InteropServices_ComWrappers_ComInterfaceEntry = "System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry"; + public const string System_Runtime_InteropServices_NativeMemory = "System.Runtime.InteropServices.NativeMemory"; + public const string StrategyBasedComWrappers = "System.Runtime.InteropServices.Marshalling.StrategyBasedComWrappers"; public const string IIUnknownInterfaceType = "System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType"; diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs new file mode 100644 index 00000000000000..494f0baf88a66b --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/GeneratedComInterfaceTests.cs @@ -0,0 +1,57 @@ +// 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.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using Xunit; +using Xunit.Sdk; + +namespace ComInterfaceGenerator.Tests; + +internal unsafe partial class NativeExportsNE +{ + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "get_com_object")] + public static partial void* NewNativeObject(); +} + + +public class GeneratedComInterfaceTests +{ + [Fact] + public unsafe void CallNativeComObjectThroughGeneratedStub() + { + var ptr = NativeExportsNE.NewNativeObject(); // new_native_object + var cw = new StrategyBasedComWrappers(); + var obj = cw.GetOrCreateObjectForComInstance((nint)ptr, CreateObjectFlags.None); + + var intObj = (IComInterface1)obj; + Assert.Equal(0, intObj.GetData()); + intObj.SetData(2); + Assert.Equal(2, intObj.GetData()); + } + + [Fact] + public unsafe void DerivedInterfaceTypeProvidesBaseInterfaceUnmanagedToManagedMembers() + { + // Make sure that we have the correct derived and base types here. + Assert.Contains(typeof(IComInterface1), typeof(IDerivedComInterface).GetInterfaces()); + + IIUnknownDerivedDetails baseInterfaceDetails = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IComInterface1).TypeHandle); + IIUnknownDerivedDetails derivedInterfaceDetails = StrategyBasedComWrappers.DefaultIUnknownInterfaceDetailsStrategy.GetIUnknownDerivedDetails(typeof(IDerivedComInterface).TypeHandle); + + var numBaseMethods = typeof(IComInterface1).GetMethods().Length; + + var numPointersToCompare = 3 + numBaseMethods; + + var expected = new ReadOnlySpan(baseInterfaceDetails.ManagedVirtualMethodTable, numPointersToCompare); + var actual = new ReadOnlySpan(derivedInterfaceDetails.ManagedVirtualMethodTable, numPointersToCompare); + + Assert.True(expected.SequenceEqual(actual)); + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedComInterface.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedComInterface.cs new file mode 100644 index 00000000000000..7df314587e0777 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/IDerivedComInterface.cs @@ -0,0 +1,19 @@ +// 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 ComInterfaceGenerator.Tests +{ + [GeneratedComInterface] + [Guid("7F0DB364-3C04-4487-9193-4BB05DC7B654")] + public partial interface IDerivedComInterface : IComInterface1 + { + void SetName([MarshalUsing(typeof(Utf16StringMarshaller))] string name); + + [return:MarshalUsing(typeof(Utf16StringMarshaller))] + string GetName(); + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/RcwTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/RcwTests.cs deleted file mode 100644 index 4f41e80511f5be..00000000000000 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/RcwTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.Marshalling; -using Xunit; -using Xunit.Sdk; - -namespace ComInterfaceGenerator.Tests; - -internal unsafe partial class NativeExportsNE -{ - [LibraryImport(NativeExportsNE_Binary, EntryPoint = "get_com_object")] - public static partial void* NewNativeObject(); -} - - -public class RcwTests -{ - [Fact] - public unsafe void CallRcwFromGeneratedComInterface() - { - var ptr = NativeExportsNE.NewNativeObject(); // new_native_object - var cw = new StrategyBasedComWrappers(); - var obj = cw.GetOrCreateObjectForComInstance((nint)ptr, CreateObjectFlags.None); - - var intObj = (IComInterface1)obj; - Assert.Equal(0, intObj.GetData()); - intObj.SetData(2); - Assert.Equal(2, intObj.GetData()); - } -} 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 be354044bc6114..d2de4fa3877aeb 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 @@ -204,7 +204,7 @@ public string BasicReturnTypeComExceptionHandling(string typeName, string preDec {GeneratedComInterface} partial interface INativeAPI {{ - {VirtualMethodIndex(0, ExceptionMarshalling : ExceptionMarshalling.Com)} + {VirtualMethodIndex(0, ExceptionMarshalling: ExceptionMarshalling.Com)} {typeName} Method(); }}" + _attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI"); @@ -222,6 +222,43 @@ partial interface INativeAPI {typeName} Method(); }}" + _attributeProvider.AdditionalUserRequiredInterfaces("INativeAPI"); + public string DerivedComInterfaceType => $@" +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +{GeneratedComInterface} +partial interface IComInterface +{{ + void Method(); +}} +{GeneratedComInterface} +partial interface IComInterface2 : IComInterface +{{ + void Method2(); +}} +"; + public string DerivedComInterfaceTypeMultipleComInterfaceBases => $@" +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +{GeneratedComInterface} +partial interface IComInterface +{{ + void Method(); +}} +{GeneratedComInterface} +partial interface IOtherComInterface +{{ + void MethodA(); +}} +{GeneratedComInterface} +partial interface IComInterface2 : IComInterface, IOtherComInterface +{{ + void Method2(); +}} +"; public class ManagedToUnmanaged : IVirtualMethodIndexSignatureProvider { public MarshalDirection Direction => MarshalDirection.ManagedToUnmanaged; 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 c2ea6ee6e063da..4b1eafc9c82400 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 @@ -107,6 +107,39 @@ partial interface J // We'll create one syntax tree per user-defined interface. Assert.Equal(comp.SyntaxTrees.Count() + 3, newComp.SyntaxTrees.Count()); + VerifyShape(newComp, "I"); + VerifyShape(newComp, "Empty"); + VerifyShape(newComp, "J"); + } + + [Fact] + public async Task InheritingComInterfaces() + { + string source = $$""" + using System.Runtime.InteropServices; + using System.Runtime.InteropServices.Marshalling; + + [GeneratedComInterface] + partial interface I + { + void Method(); + void Method2(); + } + [GeneratedComInterface] + partial interface J : I + { + void MethodA(); + void MethodB(); + } + """; + Compilation comp = await TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.ComInterfaceGenerator()); + TestUtils.AssertPostSourceGeneratorCompilation(newComp); + // We'll create one syntax tree per user-defined interface. + Assert.Equal(comp.SyntaxTrees.Count() + 2, newComp.SyntaxTrees.Count()); + VerifyShape(newComp, "I"); VerifyShape(newComp, "J"); } 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 new file mode 100644 index 00000000000000..d69ce9e2c79414 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs @@ -0,0 +1,57 @@ +// 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.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using Microsoft.CodeAnalysis; +using Microsoft.Interop.UnitTests; +using Xunit; + +namespace ComInterfaceGenerator.Unit.Tests +{ + public class CompileFails + { + private static string ID( + [CallerLineNumber] int lineNumber = 0, + [CallerFilePath] string? filePath = null) + => TestUtils.GetFileLineName(lineNumber, filePath); + + public static IEnumerable ComInterfaceGeneratorSnippetsToCompile() + { + CodeSnippets codeSnippets = new(new GeneratedComInterfaceAttributeProvider()); + // Inheriting from multiple GeneratedComInterface-marked interfaces. + yield return new object[] { ID(), codeSnippets.DerivedComInterfaceTypeMultipleComInterfaceBases, 1, 0 }; + } + + [Theory] + [MemberData(nameof(ComInterfaceGeneratorSnippetsToCompile))] + public async Task ValidateComInterfaceGeneratorSnippets(string id, string source, int expectedGeneratorErrors, int expectedCompilerErrors) + { + TestUtils.Use(id); + Compilation comp = await TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.ComInterfaceGenerator()); + + // Verify the compilation failed with errors. + IEnumerable generatorErrors = generatorDiags.Where(d => d.Severity == DiagnosticSeverity.Error); + int generatorErrorCount = generatorErrors.Count(); + Assert.True( + expectedGeneratorErrors == generatorErrorCount, + $"Expected {expectedGeneratorErrors} errors, but encountered {generatorErrorCount}. Errors: {string.Join(Environment.NewLine, generatorErrors.Select(d => d.ToString()))}"); + + IEnumerable compilerErrors = newComp.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error); + int compilerErrorCount = compilerErrors.Count(); + Assert.True( + expectedCompilerErrors == compilerErrorCount, + $"Expected {expectedCompilerErrors} errors, but encountered {compilerErrorCount}. Errors: {string.Join(Environment.NewLine, compilerErrors.Select(d => d.ToString()))}"); + } + } +} 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 1f4e361b2b7003..af356caa47eabd 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 @@ -319,9 +319,16 @@ public async Task ValidateVTableIndexSnippets(string id, string source) TestUtils.AssertPostSourceGeneratorCompilation(newComp, "CS0105"); } + public static IEnumerable ComInterfaceSnippetsToCompile() + { + CodeSnippets codeSnippets = new(new GeneratedComInterfaceAttributeProvider()); + yield return new object[] { ID(), codeSnippets.DerivedComInterfaceType }; + } + [Theory] [MemberData(nameof(CodeSnippetsToCompile), GeneratorKind.ComInterfaceGenerator)] [MemberData(nameof(CustomCollections), GeneratorKind.ComInterfaceGenerator)] + [MemberData(nameof(ComInterfaceSnippetsToCompile))] public async Task ValidateComInterfaceSnippets(string id, string source) { _ = id; From b34f607f76106e601823cda7be89a794dd454bde Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 4 Apr 2023 16:37:25 -0700 Subject: [PATCH 2/3] When calculating base info, determine if the interface is a marker interface to ensure we don't break later steps. --- .../ComInterfaceGenerator/ComInterfaceGenerator.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs index 26216e391c4e1d..4c915d2ef4c73f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using static System.Net.Mime.MediaTypeNames; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.Interop @@ -71,15 +70,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var interfaceBaseInfo = interfacesToGenerate.Collect().SelectMany((data, ct) => { - ImmutableArray<(int StartingOffset, ManagedTypeInfo? BaseInterface)>.Builder baseInterfaceInfo = ImmutableArray.CreateBuilder<(int, ManagedTypeInfo?)>(data.Length); + ImmutableArray<(int StartingOffset, ManagedTypeInfo? BaseInterface, bool IsMarkerInterface)>.Builder baseInterfaceInfo = ImmutableArray.CreateBuilder<(int, ManagedTypeInfo?, bool)>(data.Length); // Track the calculated last offsets of the interfaces. // If a type has invalid methods, we'll count them and issue an error when generating code for that // interface. Dictionary derivedNextOffset = new(SymbolEqualityComparer.Default); foreach (var iface in data) { - var (starting, baseType, _) = CalculateOffsetsForInterface(iface.Symbol); - baseInterfaceInfo.Add((starting, baseType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(baseType) : null)); + var (starting, baseType, derivedStarting) = CalculateOffsetsForInterface(iface.Symbol); + baseInterfaceInfo.Add((starting, baseType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(baseType) : null, starting == derivedStarting)); } return baseInterfaceInfo.MoveToImmutable(); @@ -315,7 +314,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) generateStubInformation .Collect() .SelectMany(static (data, ct) => GroupContextsForInterfaceGeneration(data.CastArray())) - .Zip(interfaceBaseInfo) + .Zip(interfaceBaseInfo.Where(static info => !info.IsMarkerInterface)) .Select(static (data, ct) => GenerateImplementationVTable(ImmutableArray.CreateRange(data.Left.Array.Cast()), data.Right)) .WithTrackingName(StepNames.GenerateNativeToManagedVTable) .WithComparer(SyntaxEquivalentComparer.Instance) @@ -645,7 +644,7 @@ private static InterfaceDeclarationSyntax GenerateImplementationVTableMethods(Im private static readonly MethodDeclarationSyntax CreateManagedVirtualFunctionTableMethodTemplate = MethodDeclaration(VoidStarStarSyntax, CreateManagedVirtualFunctionTableMethodName) .AddModifiers(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword)); - private static InterfaceDeclarationSyntax GenerateImplementationVTable(ImmutableArray interfaceMethodStubs, (int StartingOffset, ManagedTypeInfo? BaseInterface) baseInterfaceTypeInfo) + private static InterfaceDeclarationSyntax GenerateImplementationVTable(ImmutableArray interfaceMethodStubs, (int StartingOffset, ManagedTypeInfo? BaseInterface, bool) baseInterfaceTypeInfo) { const string vtableLocalName = "vtable"; var interfaceType = interfaceMethodStubs[0].OriginalDefiningType; From b97d619efd6075d4dc26d58db582769c6f75620b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 7 Apr 2023 12:02:03 -0700 Subject: [PATCH 3/3] PR feedback --- .../ComInterfaceGenerator.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs index 0f4367475be565..b4310b307adf16 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceGenerator.cs @@ -77,17 +77,17 @@ public void Initialize(IncrementalGeneratorInitializationContext context) Dictionary derivedNextOffset = new(SymbolEqualityComparer.Default); foreach (var iface in data) { - var (starting, baseType, derivedStarting) = CalculateOffsetsForInterface(iface.Symbol); + var (starting, baseType, derivedStarting) = CalculateOffsetsForInterface(iface.Symbol, derivedNextOffset); baseInterfaceInfo.Add((starting, baseType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(baseType) : null, starting == derivedStarting)); } return baseInterfaceInfo.MoveToImmutable(); - (int Starting, INamedTypeSymbol? BaseType, int DerivedStarting) CalculateOffsetsForInterface(INamedTypeSymbol iface) + static (int Starting, INamedTypeSymbol? BaseType, int DerivedStarting) CalculateOffsetsForInterface(INamedTypeSymbol iface, Dictionary derivedNextOffsetCache) { INamedTypeSymbol? baseInterface = null; foreach (var implemented in iface.Interfaces) { - if (implemented.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) + if (implemented.GetAttributes().Any(static attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) { // We'll filter out cases where there's multiple matching interfaces when determining // if this is a valid candidate for generation. @@ -100,9 +100,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) int startingOffset = 3; if (baseInterface is not null) { - if (!derivedNextOffset.TryGetValue(baseInterface, out int offset)) + if (!derivedNextOffsetCache.TryGetValue(baseInterface, out int offset)) { - offset = CalculateOffsetsForInterface(baseInterface).DerivedStarting; + offset = CalculateOffsetsForInterface(baseInterface, derivedNextOffsetCache).DerivedStarting; } startingOffset = offset; @@ -110,8 +110,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // This calculation isn't strictly accurate. This will count methods that aren't in the same declaring syntax as the attribute on the interface, // but we'll emit an error later if that's a problem. We also can't detect this error if the base type is in metadata. - int ifaceDerivedNextOffset = startingOffset + iface.GetMembers().Where(m => m is IMethodSymbol { IsStatic: false }).Count(); - derivedNextOffset[iface] = ifaceDerivedNextOffset; + int ifaceDerivedNextOffset = startingOffset + iface.GetMembers().Where(static m => m is IMethodSymbol { IsStatic: false }).Count(); + derivedNextOffsetCache[iface] = ifaceDerivedNextOffset; return (startingOffset, baseInterface, ifaceDerivedNextOffset); } @@ -542,11 +542,11 @@ private static IncrementalMethodStubGenerationContext CalculateStubInformation(M // Missing Guid } - // TODO: Error if more than one GeneratedComInterface base interface type. + // Error if more than one GeneratedComInterface base interface type. INamedTypeSymbol? baseInterface = null; foreach (var implemented in type.Interfaces) { - if (implemented.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) + if (implemented.GetAttributes().Any(static attr => attr.AttributeClass?.ToDisplayString() == TypeNames.GeneratedComInterfaceAttribute)) { if (baseInterface is not null) {