-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathInteropInterfaceEntriesResolver.cs
More file actions
422 lines (362 loc) · 21.7 KB
/
Copy pathInteropInterfaceEntriesResolver.cs
File metadata and controls
422 lines (362 loc) · 21.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures;
using WindowsRuntime.Generator;
using WindowsRuntime.InteropGenerator.Generation;
using WindowsRuntime.InteropGenerator.Models;
using WindowsRuntime.InteropGenerator.References;
using static AsmResolver.PE.DotNet.Cil.CilOpCodes;
namespace WindowsRuntime.InteropGenerator.Resolvers;
/// <summary>
/// A resolver for CCW interface entries for some managed type to be exposed to native code.
/// </summary>
internal static class InteropInterfaceEntriesResolver
{
/// <summary>
/// The number of default, always present COM interface entries (returned by <see cref="EnumerateNativeInterfaceEntries"/>).
/// </summary>
public const int NumberOfNativeComInterfaceEntries = 6;
/// <summary>
/// Creates an <see cref="InteropInterfaceEntryInfo"/> instance with a provided set of methods.
/// </summary>
/// <param name="get_IID">The <see cref="IMethodDefOrRef"/> value to get the interface IID.</param>
/// <param name="get_Vtable">The <see cref="IMethodDefOrRef"/> value to get the interface vtable.</param>
/// <returns>The resulting <see cref="InteropInterfaceEntryInfo"/> instance.</returns>
public static InteropInterfaceEntryInfo Create(IMethodDefOrRef get_IID, IMethodDefOrRef get_Vtable)
{
return new WindowsRuntimeInterfaceEntryInfo(get_IID, get_Vtable);
}
/// <summary>
/// Enumerates all <see cref="InteropInterfaceEntryInfo"/> values from a given source set of vtable types.
/// </summary>
/// <param name="vtableTypes">The vtable types to use as source.</param>
/// <param name="interopDefinitions">The <see cref="InteropDefinitions"/> instance to use.</param>
/// <param name="interopReferences">The <see cref="InteropReferences"/> instance to use.</param>
/// <param name="emitState">The emit state for this invocation.</param>
/// <param name="useWindowsUIXamlProjections">Whether to use <c>Windows.UI.Xaml</c> projections.</param>
public static IEnumerable<InteropInterfaceEntryInfo> EnumerateMetadataInterfaceEntries(
TypeSignatureEquatableSet vtableTypes,
InteropDefinitions interopDefinitions,
InteropReferences interopReferences,
InteropGeneratorEmitState emitState,
bool useWindowsUIXamlProjections)
{
// Append all entries for the type (which we share for all matching user-defined types)
foreach (TypeSignature typeSignature in vtableTypes)
{
// Handle generic types first, and then custom-mapped and manually projected types.
// These require special handling, because their ABI types are in different locations.
if (typeSignature is GenericInstanceTypeSignature genericTypeSignature)
{
(IMethodDefOrRef get_IIDMethod, IMethodDefOrRef get_VtableMethod) = InteropImplTypeResolver.GetGenericInstanceTypeImpl(
type: genericTypeSignature,
interopDefinitions: interopDefinitions,
interopReferences: interopReferences,
emitState: emitState);
yield return new WindowsRuntimeInterfaceEntryInfo(get_IIDMethod, get_VtableMethod);
continue;
}
// If the interface type is custom-mapped or manually projected, it means its interop code lives in
// the 'WinRT.Runtime.dll' assembly. So we have to special-case this scenario for all of those types.
if (typeSignature.IsCustomMappedWindowsRuntimeInterfaceType(interopReferences) ||
typeSignature.IsManuallyProjectedWindowsRuntimeInterfaceType(interopReferences))
{
// If the user explicitly implemented 'IStringable', we skip it here. We want to always emit it
// at the end of the list of entries, to have consistent ordering with the built-in interfaces.
if (SignatureComparer.IgnoreVersion.Equals(typeSignature, interopReferences.IStringable))
{
continue;
}
// For all other cases, we just emit the entry in the same order we found it, same as above
(IMethodDefOrRef get_IIDMethod, IMethodDefOrRef get_VtableMethod) = InteropImplTypeResolver.GetCustomMappedOrManuallyProjectedTypeImpl(
type: typeSignature,
interopReferences: interopReferences,
useWindowsUIXamlProjections: useWindowsUIXamlProjections);
yield return new WindowsRuntimeInterfaceEntryInfo(get_IIDMethod, get_VtableMethod);
continue;
}
// We always need to resolve the user-defined types in all cases below, so just do it once first
TypeDefinition interfaceType = typeSignature.Resolve(interopReferences.RuntimeContext);
// If the type represents an '[exclusiveto]' interface for an authored type from a Windows Runtime component
// written in C#, we resolve the implementation from the generated 'WinRT.Component.dll' assembly. We check
// this before the general projected type check, because these interfaces also have '[WindowsRuntimeMetadata]'
// but their implementations live in 'WinRT.Component.dll', not in projections.
if (interopDefinitions.WindowsRuntimeComponentModule is not null &&
ReferenceEquals(interfaceType.DeclaringModule, interopDefinitions.WindowsRuntimeComponentModule) &&
!interfaceType.IsPublic)
{
(IMethodDefOrRef get_IIDMethod, IMethodDefOrRef get_VtableMethod) = InteropImplTypeResolver.GetComponentTypeImpl(
type: interfaceType,
interopDefinitions: interopDefinitions);
yield return new WindowsRuntimeInterfaceEntryInfo(get_IIDMethod, get_VtableMethod);
continue;
}
// Handle the common case for all normally projected, non-generic Windows Runtime interface types. For those, all the
// interop code will just live in the 'WinRT.Projection.dll' assembly, with all projected types for the application domain.
if (interfaceType.IsProjectedWindowsRuntimeType)
{
(IMethodDefOrRef get_IIDMethod, IMethodDefOrRef get_VtableMethod) = InteropImplTypeResolver.GetProjectedTypeImpl(
type: interfaceType,
interopReferences: interopReferences);
yield return new WindowsRuntimeInterfaceEntryInfo(get_IIDMethod, get_VtableMethod);
continue;
}
// For public (non-exclusive) interfaces from authored component assemblies, the type resolves
// from the authored assembly but the Impl and IID live in 'WinRT.Component.dll'.
if (interopDefinitions.WindowsRuntimeComponentModule is not null &&
interfaceType.IsInterface && interfaceType.IsComponentWindowsRuntimeType)
{
(IMethodDefOrRef get_IIDMethod, IMethodDefOrRef get_VtableMethod) = InteropImplTypeResolver.GetComponentPublicInterfaceTypeImpl(
type: interfaceType,
interopDefinitions: interopDefinitions);
yield return new WindowsRuntimeInterfaceEntryInfo(get_IIDMethod, get_VtableMethod);
continue;
}
// For '[GeneratedComInterface]', we need to retrieve and use the generated vtable from the COM generators
if (interfaceType.IsGeneratedComInterfaceType)
{
// Ignore interfaces we can't retrieve information for (this should never happen, interfaces are filtered during discovery)
if (!interfaceType.TryGetInterfaceInformationType(interopReferences, out TypeSignature? interfaceInformationType))
{
continue;
}
// Get the IID of the interface (same as above, this is pre-validated)
if (!interfaceType.TryGetGuidAttribute(interopReferences, out Guid interfaceId))
{
continue;
}
// If we find the special 'IMarshal' interface, ignore it here. We want to use this
// later to replace our built-in 'IMarshal' implementation in its own vtable slot.
if (interfaceId == WellKnownInterfaceIIDs.IID_IMarshal)
{
continue;
}
yield return new ComInterfaceEntryInfo(interfaceInformationType);
continue;
}
}
}
/// <summary>
/// Enumerates all <see cref="InteropInterfaceEntryInfo"/> values for a given component type, if applicable.
/// </summary>
/// <param name="componentType">The component type to analyze.</param>
/// <param name="interopDefinitions">The <see cref="InteropDefinitions"/> instance to use.</param>
/// <param name="interopReferences">The <see cref="InteropReferences"/> instance to use.</param>
/// <remarks>
/// This method doesn't enumerate a set of interfaces, because it's responsible for producing Windows Runtime interfaces
/// for a value type from a component written in C# (as such, there's no managed interface type to use). If the type is
/// not a value type from a component, this method will just return an empty set. The logic is here for consistency.
/// </remarks>
public static IEnumerable<InteropInterfaceEntryInfo> EnumerateComponentInterfaceEntries(
TypeSignature componentType,
InteropDefinitions interopDefinitions,
InteropReferences interopReferences)
{
// For public value types from component assemblies, add 'IReference<T>' and 'IPropertyValue' entries.
// These are needed so the struct can be boxed as 'IReference<T>' when used as a generic type argument.
TypeDefinition userDefinedTypeDefinition = componentType.Resolve(interopReferences.RuntimeContext);
// We're only looking for value types (classes are already handled just like any other user-defined type)
if (!userDefinedTypeDefinition.IsValueType)
{
yield break;
}
// Filter down to just authored types from components written in C#
if (!userDefinedTypeDefinition.IsComponentWindowsRuntimeType ||
interopDefinitions.WindowsRuntimeComponentModule is not { } componentModule)
{
yield break;
}
// Special case: we want to exclude enum types, and only look at user-defined structs. The reason why we are handling
// struct types here is that they might be implementing 'ICustomPropertyProvider', which is the only special Windows
// Runtime interface that's allowed in authored struct types. For enum types, the set of implemented interfaces is
// fixed, so the full CCW implementation is just statically generated as part of 'WinRT.Component.dll', meaning it
// doesn't require any supporting dynamic code produced by this generator.
if (userDefinedTypeDefinition.IsEnum)
{
yield break;
}
// Look up the 'ReferenceImpl' type in the component module by exact name
string referenceImplName = $"{userDefinedTypeDefinition.Name}ReferenceImpl";
string abiNamespace = $"ABI.{userDefinedTypeDefinition.Namespace}";
// Retrieve the 'ReferenceImpl' type from the component module via the optimized mapping
if (!componentModule.GetTopLevelTypesLookup().TryGetValue(
key: (abiNamespace, referenceImplName),
value: out TypeDefinition? referenceImplType))
{
yield break;
}
// Get the 'IReference<T>' IID from the component module's 'ABI.InterfaceIIDs'
string referenceIidName = $"{userDefinedTypeDefinition.FullName}Reference";
MethodDefinition referenceIidMethod = componentModule
.GetType("ABI"u8, "InterfaceIIDs"u8)
.GetMethod($"get_IID_{referenceIidName.Replace('.', '_')}");
// Get the vtable from the 'ReferenceImpl' type
MethodDefinition referenceVtableMethod = referenceImplType.GetMethod("get_Vtable"u8);
// Add the 'IReferenceValue' entry
yield return new WindowsRuntimeInterfaceEntryInfo(referenceIidMethod, referenceVtableMethod);
// Add the 'IPropertyValue' entry
yield return new WindowsRuntimeInterfaceEntryInfo(
interopReferences.WellKnownInterfaceIIDsget_IID_IPropertyValue,
interopReferences.IPropertyValueImplget_OtherTypeVtable);
}
/// <summary>
/// Enumerates all <see cref="InteropInterfaceEntryInfo"/> values for native interfaces.
/// </summary>
/// <param name="vtableTypes">The vtable types to use as source.</param>
/// <param name="interopReferences">The <see cref="InteropReferences"/> instance to use.</param>
public static IEnumerable<InteropInterfaceEntryInfo> EnumerateNativeInterfaceEntries(
TypeSignatureEquatableSet vtableTypes,
InteropReferences interopReferences)
{
// Only include the built-in 'IStringable' implementation (which just calls 'object.ToString()') if the user didn't explicitly implement the
// projected 'IStringable' interface. If that's the case (since we can only have one of the two), we give precedence to the explicit one.
if (!TryGetUserDefinedIStringableInterfaceImplementation(
vtableTypes: vtableTypes,
interopReferences: interopReferences,
interfaceEntryInfo: out InteropInterfaceEntryInfo? stringableInterfaceEntryInfo))
{
stringableInterfaceEntryInfo = new WindowsRuntimeInterfaceEntryInfo(interopReferences.WellKnownInterfaceIIDsget_IID_IStringable, interopReferences.IStringableImplget_Vtable);
}
// Get the entry info for 'IMarshal', either user-provided or the built-in one
if (!TryGetUserDefinedIMarshalInterfaceImplementation(
vtableTypes: vtableTypes,
interopReferences: interopReferences,
interfaceEntryInfo: out InteropInterfaceEntryInfo? marshalInterfaceEntryInfo))
{
marshalInterfaceEntryInfo = new WindowsRuntimeInterfaceEntryInfo(interopReferences.WellKnownInterfaceIIDsget_IID_IMarshal, interopReferences.IMarshalImplget_Vtable);
}
// Prepare the set of all built-in native interface implementations. These always follow the vtable slots for
// user-defined interfaces implemented by exposed types. 'IUnknown' in particular must always be the last one.
yield return stringableInterfaceEntryInfo;
yield return new WindowsRuntimeInterfaceEntryInfo(interopReferences.WellKnownInterfaceIIDsget_IID_IWeakReferenceSource, interopReferences.IWeakReferenceSourceImplget_Vtable);
yield return marshalInterfaceEntryInfo;
yield return new WindowsRuntimeInterfaceEntryInfo(interopReferences.WellKnownInterfaceIIDsget_IID_IAgileObject, interopReferences.IAgileObjectImplget_Vtable);
yield return new WindowsRuntimeInterfaceEntryInfo(interopReferences.WellKnownInterfaceIIDsget_IID_IInspectable, interopReferences.IInspectableImplget_Vtable);
yield return new WindowsRuntimeInterfaceEntryInfo(interopReferences.WellKnownInterfaceIIDsget_IID_IUnknown, interopReferences.IUnknownImplget_Vtable);
}
/// <summary>
/// Tries to get the <see cref="InteropInterfaceEntryInfo"/> value for a user-defined <c>IStringable</c> interface.
/// </summary>
/// <param name="vtableTypes">The vtable types to use as source.</param>
/// <param name="interopReferences">The <see cref="InteropReferences"/> instance to use.</param>
/// <param name="interfaceEntryInfo">The resulting <see cref="InteropInterfaceEntryInfo"/> value for <c>IStringable</c>, if found.</param>
/// <returns>Whether <paramref name="interfaceEntryInfo"/> was found.</returns>
private static bool TryGetUserDefinedIStringableInterfaceImplementation(
TypeSignatureEquatableSet vtableTypes,
InteropReferences interopReferences,
[NotNullWhen(true)] out InteropInterfaceEntryInfo? interfaceEntryInfo)
{
TypeSignature typeSignature = interopReferences.IStringable.ToReferenceTypeSignature();
if (vtableTypes.Contains(typeSignature))
{
// Get the implementation for the manually-projected 'IStringable' interface. Note that
// we're always specifying 'Microsoft.UI.Xaml' projections to be used, but this doesn't
// matter here since 'IStringable' is not a XAML interface. We're hardcoding this here
// to simplify callsites, and allow them to not have to pass this parameter through.
(IMethodDefOrRef get_IIDMethod, IMethodDefOrRef get_VtableMethod) = InteropImplTypeResolver.GetCustomMappedOrManuallyProjectedTypeImpl(
type: typeSignature,
interopReferences: interopReferences,
useWindowsUIXamlProjections: false);
interfaceEntryInfo = new WindowsRuntimeInterfaceEntryInfo(get_IIDMethod, get_VtableMethod);
return true;
}
interfaceEntryInfo = null;
return false;
}
/// <summary>
/// Tries to get the <see cref="InteropInterfaceEntryInfo"/> value for a user-defined <c>IMarshal</c> interface.
/// </summary>
/// <param name="vtableTypes">The vtable types to use as source.</param>
/// <param name="interopReferences">The <see cref="InteropReferences"/> instance to use.</param>
/// <param name="interfaceEntryInfo">The resulting <see cref="InteropInterfaceEntryInfo"/> value for <c>IMarshal</c>, if found.</param>
/// <returns>Whether <paramref name="interfaceEntryInfo"/> was found.</returns>
private static bool TryGetUserDefinedIMarshalInterfaceImplementation(
TypeSignatureEquatableSet vtableTypes,
InteropReferences interopReferences,
[NotNullWhen(true)] out InteropInterfaceEntryInfo? interfaceEntryInfo)
{
foreach (TypeSignature typeSignature in vtableTypes)
{
// Ignore generic interfaces ('IMarshal' isn't generic)
if (typeSignature is GenericInstanceTypeSignature)
{
continue;
}
// Ignore all custom-mapped and special interfaces as well
if (typeSignature.IsCustomMappedWindowsRuntimeInterfaceType(interopReferences) ||
typeSignature.IsManuallyProjectedWindowsRuntimeInterfaceType(interopReferences))
{
continue;
}
// Resolve the user-defined interface type (same as above)
TypeDefinition interfaceType = typeSignature.Resolve(interopReferences.RuntimeContext);
// We only care about '[GeneratedComInterface]' types
if (!interfaceType.IsGeneratedComInterfaceType)
{
continue;
}
// Get the IID of the interface (same as above)
if (!interfaceType.TryGetGuidAttribute(interopReferences, out Guid interfaceId))
{
continue;
}
// Make sure that this is the 'IMarshal' implementation (we might not find one at all)
if (interfaceId != WellKnownInterfaceIIDs.IID_IMarshal)
{
continue;
}
// Only get the information type now that we know we do need it
if (!interfaceType.TryGetInterfaceInformationType(interopReferences, out TypeSignature? interfaceInformationType))
{
continue;
}
interfaceEntryInfo = new ComInterfaceEntryInfo(interfaceInformationType);
return true;
}
interfaceEntryInfo = null;
return false;
}
/// <summary>
/// An <see cref="InteropInterfaceEntryInfo"/> type for Windows Runtime types.
/// </summary>
/// <param name="get_IID">The <see cref="IMethodDefOrRef"/> value to get the interface IID.</param>
/// <param name="get_Vtable">The <see cref="IMethodDefOrRef"/> value to get the interface vtable.</param>
private sealed class WindowsRuntimeInterfaceEntryInfo(IMethodDefOrRef get_IID, IMethodDefOrRef get_Vtable) : InteropInterfaceEntryInfo
{
/// <inheritdoc/>
public override void LoadIID(CilInstructionCollection instructions, InteropReferences interopReferences)
{
_ = instructions.Add(Call, get_IID);
_ = instructions.Add(Ldobj, interopReferences.Guid);
}
/// <inheritdoc/>
public override void LoadVtable(CilInstructionCollection instructions, InteropReferences interopReferences)
{
_ = instructions.Add(Call, get_Vtable);
}
}
/// <summary>
/// An <see cref="InteropInterfaceEntryInfo"/> type for COM types.
/// </summary>
/// <param name="interfaceInformationType">The <c>InterfaceInformation</c> type for the current interface.</param>
private sealed class ComInterfaceEntryInfo(TypeSignature interfaceInformationType) : InteropInterfaceEntryInfo
{
/// <inheritdoc/>
public override void LoadIID(CilInstructionCollection instructions, InteropReferences interopReferences)
{
_ = instructions.Add(Constrained, interfaceInformationType.ToTypeDefOrRef());
_ = instructions.Add(Call, interopReferences.IIUnknownInterfaceTypeget_Iid);
}
/// <inheritdoc/>
public override void LoadVtable(CilInstructionCollection instructions, InteropReferences interopReferences)
{
_ = instructions.Add(Constrained, interfaceInformationType.ToTypeDefOrRef());
_ = instructions.Add(Call, interopReferences.IIUnknownInterfaceTypeget_ManagedVirtualMethodTable);
}
}
}