diff --git a/docs/design/libraries/ComInterfaceGenerator/VTableStubs.md b/docs/design/libraries/ComInterfaceGenerator/VTableStubs.md index d11034770609c..21a10864759c8 100644 --- a/docs/design/libraries/ComInterfaceGenerator/VTableStubs.md +++ b/docs/design/libraries/ComInterfaceGenerator/VTableStubs.md @@ -57,7 +57,7 @@ public class VirtualMethodIndexAttribute : Attribute ``` -A new interface will be defined and used by the source generator to fetch the native `this` pointer and the vtable that the function pointer is stored in. This interface is designed to provide an API that various native platforms, like COM, WinRT, or Swift, could use to provide support for multiple managed interface wrappers from a single native object. In particular, this interface was designed to ensure it is possible support a managed gesture to do an unmanaged "type cast" (i.e., `QueryInterface` in the COM and WinRT worlds). +New interfaces will be defined and used by the source generator to fetch the native `this` pointer and the vtable that the function pointer is stored in. These interfaces are designed to provide an API that various native platforms, like COM, WinRT, or Swift, could use to provide support for multiple managed interface wrappers from a single native object. In particular, these interfaces are designed to ensure it is possible support a managed gesture to do an unmanaged "type cast" (i.e., `QueryInterface` in the COM and WinRT worlds). ```csharp namespace System.Runtime.InteropServices; @@ -82,13 +82,24 @@ public readonly ref struct VirtualMethodTableInfo public interface IUnmanagedVirtualMethodTableProvider where T : IEquatable { - VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(T typeKey); + protected VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(T typeKey); + + public sealed VirtualMethodTableInfo GetVirtualMethodTableInfoForKey() + where TUnmanagedInterfaceType : IUnmanagedInterfaceType + { + return GetVirtualMethodTableInfoForKey(TUnmanagedInterfaceType.TypeKey); + } +} + +public interface IUnmanagedInterfaceType where T : IEquatable +{ + public abstract static T TypeKey { get; } } ``` ## Required API Shapes -In addition to the provided APIs above, users will be required to add a `readonly static` field or `get`-able property to their user-defined interface type named `TypeKey`. The type of this member will be used as the `T` in `IUnmanagedVirtualMethodTableProvider` and the value will be passed to `GetVirtualMethodTableInfoForKey`. This mechanism is designed to enable each native API platform to provide their own casting key, for example `IID`s in COM, without interfering with each other or requiring using reflection-based types like `System.Type`. +The user will be required to implement `IUnmanagedVirtualMethodTableProvider` on the type that provides the method tables, and `IUnmanagedInterfaceType` on the type that defines the unmanaged interface. The `T` types must match between the two interfaces. This mechanism is designed to enable each native API platform to provide their own casting key, for example `IID`s in COM, without interfering with each other or requiring using reflection-based types like `System.Type`. ## Example Usage @@ -149,11 +160,11 @@ using System.Runtime.InteropServices; [assembly:DisableRuntimeMarshalling] // Define the interface of the native API -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType { // There is no concept of casting for this API, but providing a type key is still required by the generator. // Use an empty readonly record struct to provide a type that implements IEquatable but contains no data. - readonly static NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = false, Direction = CustomTypeMarshallerDirection.In)] int GetVersion(); @@ -218,7 +229,7 @@ partial interface INativeAPI { int INativeAPI.GetVersion() { - var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(INativeAPI.TypeKey); + var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(); int retVal; retVal = ((delegate* unmanaged)vtable[0])(); return retVal; @@ -231,7 +242,7 @@ partial interface INativeAPI { int INativeAPI.Add(int x, int y) { - var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(INativeAPI.TypeKey); + var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(); int retVal; retVal = ((delegate* unmanaged)vtable[1])(x, y); return retVal; @@ -244,7 +255,7 @@ partial interface INativeAPI { int INativeAPI.Multiply(int x, int y) { - var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(INativeAPI.TypeKey); + var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(); int retVal; retVal = ((delegate* unmanaged)vtable[2])(x, y); return retVal; @@ -279,9 +290,9 @@ struct IUnknown using System; using System.Runtime.InteropServices; -interface IUnknown +interface IUnknown: IUnmanagedInterfaceType { - public static readonly Guid TypeKey = Guid.Parse("00000000-0000-0000-C000-000000000046"); + static Guid IUnmanagedTypeInterfaceType.TypeKey => Guid.Parse("00000000-0000-0000-C000-000000000046"); [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall), typeof(CallConvMemberFunction) })] [VirtualMethodIndex(0)] @@ -347,7 +358,7 @@ partial interface IUnknown { int IUnknown.QueryInterface(in Guid riid, out IntPtr ppvObject) { - var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(IUnknown.TypeKey); + var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(); int retVal; fixed (Guid* riid__gen_native = &riid) fixed (IntPtr* ppvObject__gen_native = &ppvObject) @@ -364,7 +375,7 @@ partial interface IUnknown { uint IUnknown.AddRef() { - var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(IUnknown.TypeKey); + var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(); uint retVal; retVal = ((delegate* unmanaged[Stdcall, MemberFunction])vtable[1])(thisPtr); return retVal; @@ -377,7 +388,7 @@ partial interface IUnknown { uint IUnknown.Release() { - var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(IUnknown.TypeKey); + var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(); uint retVal; retVal = ((delegate* unmanaged[Stdcall, MemberFunction])vtable[2])(thisPtr); return retVal; diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedToNativeVTableMethodGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedToNativeVTableMethodGenerator.cs index 645af622c4c6d..ae784ff047d2f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedToNativeVTableMethodGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedToNativeVTableMethodGenerator.cs @@ -117,7 +117,7 @@ public BlockSyntax GenerateStubBody(int index, ImmutableArray { - // var (, ) = ((IUnmanagedVirtualMethodTableProvider<>)this).GetVirtualMethodTableInfoForKey(.TypeKey) + // var (, ) = ((IUnmanagedVirtualMethodTableProvider<>)this).GetVirtualMethodTableInfoForKey<>(); ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, @@ -141,15 +141,12 @@ public BlockSyntax GenerateStubBody(int index, ImmutableArray().FirstOrDefault(f => f.IsStatic); - if (typeKeyField is null) + INamedTypeSymbol? iUnmanagedInterfaceTypeInstantiation = symbol.ContainingType.AllInterfaces.FirstOrDefault(iface => SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, iUnmanagedInterfaceTypeType)); + if (iUnmanagedInterfaceTypeInstantiation is null) { // Report invalid configuration } else { - typeKeyType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(typeKeyField.Type); + typeKeyType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(iUnmanagedInterfaceTypeInstantiation.TypeArguments[0]); } return new IncrementalStubGenerationContext( 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 9fb72bdb94c02..e999aed83cdbb 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 @@ -32,6 +32,8 @@ public static class TypeNames public const string IUnmanagedVirtualMethodTableProvider = "System.Runtime.InteropServices.IUnmanagedVirtualMethodTableProvider"; + public const string IUnmanagedInterfaceType_Metadata = "System.Runtime.InteropServices.IUnmanagedInterfaceType`1"; + public const string System_Span_Metadata = "System.Span`1"; public const string System_Span = "System.Span"; public const string System_ReadOnlySpan_Metadata = "System.ReadOnlySpan`1"; diff --git a/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/IUnmanagedVirtualMethodTableProvider.cs b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/IUnmanagedVirtualMethodTableProvider.cs index 29e56c729f5c2..9b77ec6493b8a 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/IUnmanagedVirtualMethodTableProvider.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/Ancillary.Interop/IUnmanagedVirtualMethodTableProvider.cs @@ -29,7 +29,18 @@ public void Deconstruct(out IntPtr thisPointer, out ReadOnlySpan virtual public interface IUnmanagedVirtualMethodTableProvider where T : IEquatable { - VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(T typeKey); + protected VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(T typeKey); + + public sealed VirtualMethodTableInfo GetVirtualMethodTableInfoForKey() + where TUnmanagedInterfaceType : IUnmanagedInterfaceType + { + return GetVirtualMethodTableInfoForKey(TUnmanagedInterfaceType.TypeKey); + } } + + public interface IUnmanagedInterfaceType where T : IEquatable + { + public abstract static T TypeKey { get; } + } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ImplicitThisTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ImplicitThisTests.cs index 0c88984c9fec5..fa2e70015200d 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ImplicitThisTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/ImplicitThisTests.cs @@ -18,9 +18,9 @@ internal partial class ImplicitThis { public readonly record struct NoCasting; - internal partial interface INativeObject + internal partial interface INativeObject : IUnmanagedInterfaceType { - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = true)] int GetData(); diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/NoImplicitThisTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/NoImplicitThisTests.cs index c24892b62c7e8..4219427a4a31f 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/NoImplicitThisTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Tests/NoImplicitThisTests.cs @@ -18,9 +18,9 @@ internal partial class NoImplicitThis { public readonly record struct NoCasting; - internal partial interface IStaticMethodTable + internal partial interface IStaticMethodTable : IUnmanagedInterfaceType { - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = false)] int Add(int x, int y); 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 648bf48640168..fa15018f69173 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 @@ -27,9 +27,9 @@ sealed class NativeAPI : IUnmanagedVirtualMethodTableProvider, INativ using System.Runtime.InteropServices.Marshalling; readonly record struct NoCasting {} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType { - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] void Method(); }" + NativeInterfaceUsage(); @@ -39,9 +39,9 @@ partial interface INativeAPI using System.Runtime.InteropServices.Marshalling; readonly record struct NoCasting {} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType { - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = false)] void Method(); }" + NativeInterfaceUsage(); @@ -52,9 +52,9 @@ partial interface INativeAPI using System.Runtime.InteropServices.Marshalling; readonly record struct NoCasting {} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType { - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] [VirtualMethodIndex(0)] @@ -86,9 +86,9 @@ public static string BasicParametersAndModifiers(string typeName, string preDecl [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] {typeName} Method({typeName} value, in {typeName} inValue, ref {typeName} refValue, out {typeName} outValue); }}" + NativeInterfaceUsage(); @@ -102,9 +102,9 @@ public static string BasicParametersAndModifiersNoRef(string typeName, string pr [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] {typeName} Method({typeName} value, in {typeName} inValue, out {typeName} outValue); }}" + NativeInterfaceUsage(); @@ -114,9 +114,9 @@ public static string BasicParametersAndModifiersNoImplicitThis(string typeName) using System.Runtime.InteropServices.Marshalling; readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = false)] {typeName} Method({typeName} value, in {typeName} inValue, ref {typeName} refValue, out {typeName} outValue); }}" + NativeInterfaceUsage(); @@ -130,9 +130,9 @@ public static string BasicParameterByValue(string typeName, string preDeclaratio {preDeclaration} readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = false)] void Method({typeName} value); }}" + NativeInterfaceUsage(); @@ -145,9 +145,9 @@ public static string BasicParameterWithByRefModifier(string modifier, string typ [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = false)] void Method({modifier} {typeName} value); }}" + NativeInterfaceUsage(); @@ -158,9 +158,9 @@ public static string BasicReturnType(string typeName, string preDeclaration = "" {preDeclaration} readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0, ImplicitThisParameter = false)] {typeName} Method(); }}" + NativeInterfaceUsage(); @@ -171,9 +171,9 @@ public static string MarshalUsingParametersAndModifiers(string typeName, string {preDeclaration} readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] [return: MarshalUsing(typeof({marshallerTypeName}))] {typeName} Method( @@ -190,9 +190,9 @@ public static string MarshalUsingCollectionCountInfoParametersAndModifiers(strin [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] [return:MarshalUsing(ConstantElementCount=10)] {collectionType} Method( @@ -211,9 +211,9 @@ public static string MarshalUsingCollectionParametersAndModifiers(string collect [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] [return:MarshalUsing(typeof({marshallerType}), ConstantElementCount=10)] {collectionType} Method( @@ -233,9 +233,9 @@ public static string MarshalUsingCollectionReturnValueLength(string collectionTy [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] int Method( [MarshalUsing(typeof({marshallerType}), CountElementName = MarshalUsingAttribute.ReturnsCountValue)] out {collectionType} pOut @@ -251,9 +251,9 @@ public static string MarshalUsingCollectionOutConstantLength(string collectionTy [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] int Method( [MarshalUsing(ConstantElementCount = 10)] out {collectionType} pOut @@ -269,9 +269,9 @@ public static string MarshalUsingCollectionReturnConstantLength(string collectio [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] [return:MarshalUsing(ConstantElementCount = 10)] {collectionType} Method(); @@ -286,9 +286,9 @@ public static string CustomElementMarshalling(string collectionType, string elem [assembly:DisableRuntimeMarshalling] readonly record struct NoCasting {{}} -partial interface INativeAPI +partial interface INativeAPI : IUnmanagedInterfaceType {{ - public static readonly NoCasting TypeKey = default; + static NoCasting IUnmanagedInterfaceType.TypeKey => default; [VirtualMethodIndex(0)] [return:MarshalUsing(ConstantElementCount=10)] [return:MarshalUsing(typeof({elementMarshaller}), ElementIndirectionDepth = 1)]