Skip to content

Commit 170587e

Browse files
authored
Remove the TypeKey concept as the primary user for the concept is unable to use it effectively. (#79418)
1 parent fa19917 commit 170587e

File tree

13 files changed

+167
-234
lines changed

13 files changed

+167
-234
lines changed

docs/design/libraries/ComInterfaceGenerator/VTableStubs.md

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,29 @@ public readonly ref struct VirtualMethodTableInfo
8080
}
8181
}
8282

83-
public interface IUnmanagedVirtualMethodTableProvider<T> where T : IEquatable<T>
83+
public interface IUnmanagedVirtualMethodTableProvider
8484
{
85-
protected VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(T typeKey);
85+
protected VirtualMethodTableInfo GetVirtualMethodTableInfoForKey(Type type);
8686

8787
public sealed VirtualMethodTableInfo GetVirtualMethodTableInfoForKey<TUnmanagedInterfaceType>()
88-
where TUnmanagedInterfaceType : IUnmanagedInterfaceType<T>
88+
where TUnmanagedInterfaceType : IUnmanagedInterfaceType<TUnmanagedInterfaceType>
8989
{
90-
return GetVirtualMethodTableInfoForKey(TUnmanagedInterfaceType.TypeKey);
90+
// Dispatch from a non-virtual generic to a virtual non-generic with System.Type
91+
// to avoid generic virtual method dispatch, which is very slow.
92+
return GetVirtualMethodTableInfoForKey(typeof(TUnmanagedInterfaceType));
9193
}
9294
}
9395

94-
public interface IUnmanagedInterfaceType<T> where T : IEquatable<T>
96+
public interface IUnmanagedInterfaceType<TUnmanagedInterfaceType> where TUnmanagedInterfaceType : IUnmanagedInterfaceType<TUnmanagedInterfaceType>
9597
{
96-
public abstract static T TypeKey { get; }
9798
}
9899
```
99100

100101
## Required API Shapes
101102

102-
The user will be required to implement `IUnmanagedVirtualMethodTableProvider<T>` on the type that provides the method tables, and `IUnmanagedInterfaceType<T>` 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`.
103+
The user will be required to implement `IUnmanagedVirtualMethodTableProvider` on the type that provides the method tables, and `IUnmanagedInterfaceType<TUnmanagedInterfaceType>` on the type that defines the unmanaged interface. The `TUnmanagedInterfaceType` follows the same design principles as the generic math designs as somewhat of a "self" type to enable us to use the derived interface type in any additional APIs we add to support unmanaged-to-managed stubs.
104+
105+
Previously, each of these interface types were also generic on another type `T`. The `T` types were required to match between the two interfaces. This mechanism was 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`. However, practical implementation showed that providing just a "type key" was not enough information to cover any non-trivial scenarios (like COM) efficiently without effectively forcing a two-level lookup model or hard-coding type support in the `IUnmanagedVirtualMethodTableProvider<T>` implementation. Additionally, we determined that using reflection to get to attributes is considered "okay" and using generic attributes would enable APIs that build on this model like COM to effectively retrieve information from the `System.Type` instance without causing additional problems.
103106

104107
## Example Usage
105108

@@ -160,11 +163,8 @@ using System.Runtime.InteropServices;
160163
[assembly:DisableRuntimeMarshalling]
161164
162165
// Define the interface of the native API
163-
partial interface INativeAPI : IUnmanagedInterfaceType<NoCasting>
166+
partial interface INativeAPI : IUnmanagedInterfaceType<INativeAPI>
164167
{
165-
// There is no concept of casting for this API, but providing a type key is still required by the generator.
166-
// Use an empty readonly record struct to provide a type that implements IEquatable<T> but contains no data.
167-
static NoCasting IUnmanagedInterfaceType.TypeKey => default;
168168
169169
[VirtualMethodIndex(0, ImplicitThisParameter = false, Direction = CustomTypeMarshallerDirection.In)]
170170
int GetVersion();
@@ -176,11 +176,8 @@ partial interface INativeAPI : IUnmanagedInterfaceType<NoCasting>
176176
int Multiply(int x, int y);
177177
}
178178
179-
// Define the key for native "casting" support for our scenario
180-
readonly record struct NoCasting {}
181-
182179
// Define our runtime wrapper type for the native interface.
183-
unsafe class NativeAPI : IUnmanagedVirtualMethodTableProvider<NoCasting>, INativeAPI.Native
180+
unsafe class NativeAPI : IUnmanagedVirtualMethodTableProvider, INativeAPI.Native
184181
{
185182
private CNativeAPI* _nativeAPI;
186183
@@ -192,7 +189,7 @@ unsafe class NativeAPI : IUnmanagedVirtualMethodTableProvider<NoCasting>, INativ
192189
}
193190
}
194191
195-
VirtualMethodTableInfo IUnmanagedVirtualMethodTableProvider<NoCasting>.GetVirtualMethodTableInfoForKey(NoCasting _)
192+
VirtualMethodTableInfo IUnmanagedVirtualMethodTableProvider.GetVirtualMethodTableInfoForKey(Type _)
196193
{
197194
return new(IntPtr.Zero, MemoryMarshal.Cast<CNativeAPI, IntPtr>(new ReadOnlySpan<CNativeAPI>(_nativeAPI, 1)));
198195
}
@@ -229,7 +226,7 @@ partial interface INativeAPI
229226
{
230227
int INativeAPI.GetVersion()
231228
{
232-
var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider<NoCasting>)this).GetVirtualMethodTableInfoForKey<INativeAPI>();
229+
var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey<INativeAPI>();
233230
int retVal;
234231
retVal = ((delegate* unmanaged<int>)vtable[0])();
235232
return retVal;
@@ -242,7 +239,7 @@ partial interface INativeAPI
242239
{
243240
int INativeAPI.Add(int x, int y)
244241
{
245-
var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider<NoCasting>)this).GetVirtualMethodTableInfoForKey<INativeAPI>();
242+
var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey<INativeAPI>();
246243
int retVal;
247244
retVal = ((delegate* unmanaged<int, int, int>)vtable[1])(x, y);
248245
return retVal;
@@ -255,7 +252,7 @@ partial interface INativeAPI
255252
{
256253
int INativeAPI.Multiply(int x, int y)
257254
{
258-
var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider<NoCasting>)this).GetVirtualMethodTableInfoForKey<INativeAPI>();
255+
var (_, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey<INativeAPI>();
259256
int retVal;
260257
retVal = ((delegate* unmanaged<int, int, int>)vtable[2])(x, y);
261258
return retVal;
@@ -266,7 +263,7 @@ partial interface INativeAPI
266263
// LibraryImport-generated code omitted for brevity
267264
```
268265

269-
As this generator is primarily designed to provide building blocks for future work, it has a larger requirement on user-written code. In particular, this generator does not provide any support for authoring a runtime wrapper object that stores the native pointers for the underlying object or the virtual method table. However, this lack of support also provides significant flexibility for developers. The only requirement for the runtime wrapper object type is that it implements `IUnmanagedVirtualMethodTableProvider<T>` with a `T` matching the `TypeKey` type of the native interface.
266+
As this generator is primarily designed to provide building blocks for future work, it has a larger requirement on user-written code. In particular, this generator does not provide any support for authoring a runtime wrapper object that stores the native pointers for the underlying object or the virtual method table. However, this lack of support also provides significant flexibility for developers. The only requirement for the runtime wrapper object type is that it implements `IUnmanagedVirtualMethodTableProvider`.
270267

271268
The emitted interface implementation can be used in two ways:
272269

@@ -290,10 +287,8 @@ struct IUnknown
290287
using System;
291288
using System.Runtime.InteropServices;
292289

293-
interface IUnknown: IUnmanagedInterfaceType<Guid>
290+
interface IUnknown: IUnmanagedInterfaceType<IUnknown>
294291
{
295-
static Guid IUnmanagedTypeInterfaceType<Guid>.TypeKey => Guid.Parse("00000000-0000-0000-C000-000000000046");
296-
297292
[UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall), typeof(CallConvMemberFunction) })]
298293
[VirtualMethodIndex(0)]
299294
int QueryInterface(in Guid riid, out IntPtr ppvObject);
@@ -307,7 +302,7 @@ interface IUnknown: IUnmanagedInterfaceType<Guid>
307302
uint Release();
308303
}
309304

310-
class BaseIUnknownComObject : IUnmanagedVirtualMethodTableProvider<Guid>, IDynamicInterfaceCastable
305+
class BaseIUnknownComObject : IUnmanagedVirtualMethodTableProvider, IDynamicInterfaceCastable
311306
{
312307
private IntPtr _unknownPtr;
313308

@@ -316,9 +311,9 @@ class BaseIUnknownComObject : IUnmanagedVirtualMethodTableProvider<Guid>, IDynam
316311
_unknownPtr = unknown;
317312
}
318313

319-
unsafe VirtualMethodTableInfo IUnmanagedVirtualMethodTableProvider<Guid>.GetVirtualMethodTableInfoForKey(Guid iid)
314+
unsafe VirtualMethodTableInfo IUnmanagedVirtualMethodTableProvider.GetVirtualMethodTableInfoForKey(Type type)
320315
{
321-
if (iid == IUnknown.TypeKey)
316+
if (type == typeof(IUnknown))
322317
{
323318
return new VirtualMethodTableInfo(_unknownPtr, new ReadOnlySpan<IntPtr>(**(IntPtr***)_unknownPtr), 3);
324319
}
@@ -358,7 +353,7 @@ partial interface IUnknown
358353
{
359354
int IUnknown.QueryInterface(in Guid riid, out IntPtr ppvObject)
360355
{
361-
var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider<Guid>)this).GetVirtualMethodTableInfoForKey<IUnknown>();
356+
var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey<IUnknown>();
362357
int retVal;
363358
fixed (Guid* riid__gen_native = &riid)
364359
fixed (IntPtr* ppvObject__gen_native = &ppvObject)
@@ -375,7 +370,7 @@ partial interface IUnknown
375370
{
376371
uint IUnknown.AddRef()
377372
{
378-
var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider<Guid>)this).GetVirtualMethodTableInfoForKey<IUnknown>();
373+
var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey<IUnknown>();
379374
uint retVal;
380375
retVal = ((delegate* unmanaged[Stdcall, MemberFunction]<IntPtr, uint>)vtable[1])(thisPtr);
381376
return retVal;
@@ -388,7 +383,7 @@ partial interface IUnknown
388383
{
389384
uint IUnknown.Release()
390385
{
391-
var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider<Guid>)this).GetVirtualMethodTableInfoForKey<IUnknown>();
386+
var (thisPtr, vtable) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey<IUnknown>();
392387
uint retVal;
393388
retVal = ((delegate* unmanaged[Stdcall, MemberFunction]<IntPtr, uint>)vtable[2])(thisPtr);
394389
return retVal;

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ManagedToNativeVTableMethodGenerator.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@ public ManagedToNativeVTableMethodGenerator(
9797
/// <remarks>
9898
/// The generated code assumes it will be in an unsafe context.
9999
/// </remarks>
100-
public BlockSyntax GenerateStubBody(int index, ImmutableArray<FunctionPointerUnmanagedCallingConventionSyntax> callConv, TypeSyntax containingTypeName, ManagedTypeInfo typeKeyType)
100+
public BlockSyntax GenerateStubBody(int index, ImmutableArray<FunctionPointerUnmanagedCallingConventionSyntax> callConv, TypeSyntax containingTypeName)
101101
{
102102
var setupStatements = new List<StatementSyntax>
103103
{
104-
// var (<thisParameter>, <virtualMethodTable>) = ((IUnmanagedVirtualMethodTableProvider<<typeKeyType>>)this).GetVirtualMethodTableInfoForKey<<containingTypeName>>();
104+
// var (<thisParameter>, <virtualMethodTable>) = ((IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey<<containingTypeName>>();
105105
ExpressionStatement(
106106
AssignmentExpression(
107107
SyntaxKind.SimpleAssignmentExpression,
@@ -119,11 +119,7 @@ public BlockSyntax GenerateStubBody(int index, ImmutableArray<FunctionPointerUnm
119119
SyntaxKind.SimpleMemberAccessExpression,
120120
ParenthesizedExpression(
121121
CastExpression(
122-
GenericName(
123-
Identifier(TypeNames.IUnmanagedVirtualMethodTableProvider))
124-
.WithTypeArgumentList(
125-
TypeArgumentList(
126-
SingletonSeparatedList(typeKeyType.Syntax))),
122+
ParseTypeName(TypeNames.IUnmanagedVirtualMethodTableProvider),
127123
ThisExpression())),
128124
GenericName(
129125
Identifier("GetVirtualMethodTableInfoForKey"),

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Marshallers/NativeToManagedThisMarshallerFactory.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99

1010
namespace Microsoft.Interop
1111
{
12-
internal sealed record NativeThisInfo(ManagedTypeInfo TypeKeyType) : MarshallingInfo;
12+
internal sealed record NativeThisInfo : MarshallingInfo
13+
{
14+
public static readonly NativeThisInfo Instance = new();
15+
}
1316

1417
internal sealed class NativeToManagedThisMarshallerFactory : IMarshallingGeneratorFactory
1518
{
@@ -20,14 +23,10 @@ public NativeToManagedThisMarshallerFactory(IMarshallingGeneratorFactory inner)
2023
}
2124

2225
public IMarshallingGenerator Create(TypePositionInfo info, StubCodeContext context)
23-
=> info.MarshallingAttributeInfo is NativeThisInfo(ManagedTypeInfo typeKeyType) ? new Marshaller(typeKeyType) : _inner.Create(info, context);
26+
=> info.MarshallingAttributeInfo is NativeThisInfo ? new Marshaller() : _inner.Create(info, context);
2427

2528
private sealed class Marshaller : IMarshallingGenerator
2629
{
27-
private readonly ManagedTypeInfo _typeKeyType;
28-
29-
public Marshaller(ManagedTypeInfo typeKeyType) => _typeKeyType = typeKeyType;
30-
3130
public ManagedTypeInfo AsNativeType(TypePositionInfo info) => new PointerTypeInfo("void*", "void*", false);
3231
public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
3332
{
@@ -44,10 +43,7 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
4443
IdentifierName(managedIdentifier),
4544
InvocationExpression(
4645
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
47-
GenericName(Identifier(TypeNames.IUnmanagedVirtualMethodTableProvider),
48-
TypeArgumentList(
49-
SingletonSeparatedList(
50-
_typeKeyType.Syntax))),
46+
ParseTypeName(TypeNames.IUnmanagedVirtualMethodTableProvider),
5147
GenericName(Identifier("GetObjectForUnmanagedWrapper"),
5248
TypeArgumentList(
5349
SingletonSeparatedList(

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VtableIndexStubGenerator.cs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ internal sealed record IncrementalStubGenerationContext(
3030
MarshallingInfo ExceptionMarshallingInfo,
3131
MarshallingGeneratorFactoryKey<(TargetFramework TargetFramework, Version TargetFrameworkVersion)> ManagedToUnmanagedGeneratorFactory,
3232
MarshallingGeneratorFactoryKey<(TargetFramework TargetFramework, Version TargetFrameworkVersion)> UnmanagedToManagedGeneratorFactory,
33-
ManagedTypeInfo TypeKeyType,
3433
ManagedTypeInfo TypeKeyOwner,
3534
SequenceEqualImmutableArray<Diagnostic> Diagnostics);
3635

@@ -348,20 +347,15 @@ private static IncrementalStubGenerationContext CalculateStubInformation(MethodD
348347

349348
ImmutableArray<FunctionPointerUnmanagedCallingConventionSyntax> callConv = GenerateCallConvSyntaxFromAttributes(suppressGCTransitionAttribute, unmanagedCallConvAttribute);
350349

351-
var typeKeyOwner = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(symbol.ContainingType);
352-
ManagedTypeInfo typeKeyType = SpecialTypeInfo.Byte;
350+
var interfaceType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(symbol.ContainingType);
353351

354-
INamedTypeSymbol? iUnmanagedInterfaceTypeInstantiation = symbol.ContainingType.AllInterfaces.FirstOrDefault(iface => SymbolEqualityComparer.Default.Equals(iface.OriginalDefinition, iUnmanagedInterfaceTypeType));
355-
if (iUnmanagedInterfaceTypeInstantiation is null)
352+
INamedTypeSymbol expectedUnmanagedInterfaceType = iUnmanagedInterfaceTypeType.Construct(symbol.ContainingType);
353+
354+
bool implementsIUnmanagedInterfaceOfSelf = symbol.ContainingType.AllInterfaces.Any(iface => SymbolEqualityComparer.Default.Equals(iface, expectedUnmanagedInterfaceType));
355+
if (!implementsIUnmanagedInterfaceOfSelf)
356356
{
357357
// TODO: Report invalid configuration
358358
}
359-
else
360-
{
361-
// The type key is the second generic type parameter, so we need to get the info for the
362-
// second argument.
363-
typeKeyType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(iUnmanagedInterfaceTypeInstantiation.TypeArguments[1]);
364-
}
365359

366360
MarshallingInfo exceptionMarshallingInfo = CreateExceptionMarshallingInfo(virtualMethodIndexAttr, symbol, environment.Compilation, generatorDiagnostics, virtualMethodIndexData);
367361

@@ -375,8 +369,7 @@ private static IncrementalStubGenerationContext CalculateStubInformation(MethodD
375369
exceptionMarshallingInfo,
376370
ComInterfaceGeneratorHelpers.CreateGeneratorFactory(environment, MarshalDirection.ManagedToUnmanaged),
377371
ComInterfaceGeneratorHelpers.CreateGeneratorFactory(environment, MarshalDirection.UnmanagedToManaged),
378-
typeKeyType,
379-
typeKeyOwner,
372+
interfaceType,
380373
new SequenceEqualImmutableArray<Diagnostic>(generatorDiagnostics.Diagnostics.ToImmutableArray()));
381374
}
382375

@@ -442,8 +435,7 @@ private static (MemberDeclarationSyntax, ImmutableArray<Diagnostic>) GenerateMan
442435
BlockSyntax code = stubGenerator.GenerateStubBody(
443436
methodStub.VtableIndexData.Index,
444437
methodStub.CallingConvention.Array,
445-
methodStub.TypeKeyOwner.Syntax,
446-
methodStub.TypeKeyType);
438+
methodStub.TypeKeyOwner.Syntax);
447439

448440
return (
449441
methodStub.ContainingSyntaxContext.AddContainingSyntax(
@@ -518,7 +510,7 @@ private static ImmutableArray<TypePositionInfo> AddImplicitElementInfos(Incremen
518510

519511
var elements = ImmutableArray.CreateBuilder<TypePositionInfo>(originalElements.Length + 2);
520512

521-
elements.Add(new TypePositionInfo(methodStub.TypeKeyOwner, new NativeThisInfo(methodStub.TypeKeyType))
513+
elements.Add(new TypePositionInfo(methodStub.TypeKeyOwner, NativeThisInfo.Instance)
522514
{
523515
InstanceIdentifier = ThisParameterIdentifier,
524516
NativeIndex = 0,

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static class TypeNames
3838

3939
public const string IUnmanagedVirtualMethodTableProvider = "System.Runtime.InteropServices.IUnmanagedVirtualMethodTableProvider";
4040

41-
public const string IUnmanagedInterfaceType_Metadata = "System.Runtime.InteropServices.IUnmanagedInterfaceType`2";
41+
public const string IUnmanagedInterfaceType_Metadata = "System.Runtime.InteropServices.IUnmanagedInterfaceType`1";
4242

4343
public const string System_Span_Metadata = "System.Span`1";
4444
public const string System_Span = "System.Span";

0 commit comments

Comments
 (0)