diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs index 0ba64c060..5ba83f59f 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IDictionary2.cs @@ -1104,6 +1104,59 @@ public static void ImplType( ModuleDefinition module, out TypeDefinition implType) { + TypeSignature keyType = dictionaryType.TypeArguments[0]; + TypeSignature valueType = dictionaryType.TypeArguments[1]; + + // Define the 'Lookup' method + MethodDefinition lookupMethod = InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.Lookup( + readOnlyDictionaryType: dictionaryType, + lookupMethod: interopReferences.IReadOnlyDictionaryAdapter2Lookup(keyType, valueType), + interopReferences: interopReferences, + emitState: emitState, + module: module); + + // Define the 'get_Size' method + MethodDefinition sizeMethod = InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.get_Size( + readOnlyDictionaryType: dictionaryType, + sizeMethod: interopReferences.IDictionaryAdapter2Size(keyType, valueType), + interopReferences: interopReferences, + module: module); + + // Define the 'HasKey' method + MethodDefinition hasKeyMethod = InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.HasKey( + readOnlyDictionaryType: dictionaryType, + containsKeyMethod: interopReferences.IDictionary2ContainsKey(keyType, valueType), + interopReferences: interopReferences, + emitState: emitState, + module: module); + + // Define the 'GetView' method + MethodDefinition getViewMethod = InteropMethodDefinitionFactory.IDictionary2Impl.GetView( + dictionaryType: dictionaryType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + + // Define the 'Insert' method + MethodDefinition insertMethod = InteropMethodDefinitionFactory.IDictionary2Impl.Insert( + dictionaryType: dictionaryType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + + // Define the 'Remove' method + MethodDefinition removeMethod = InteropMethodDefinitionFactory.IDictionary2Impl.Remove( + dictionaryType: dictionaryType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + + // Define the 'Clear' method + MethodDefinition clearMethod = InteropMethodDefinitionFactory.IDictionary2Impl.Clear( + dictionaryType: dictionaryType, + interopReferences: interopReferences, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(dictionaryType), @@ -1113,7 +1166,14 @@ public static void ImplType( interopReferences: interopReferences, module: module, implType: out implType, - vtableMethods: []); + vtableMethods: [ + lookupMethod, + sizeMethod, + hasKeyMethod, + getViewMethod, + insertMethod, + removeMethod, + clearMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, dictionaryType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs index 9e39ca4d5..22eddfb7c 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyDictionary2.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Runtime.InteropServices; using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; @@ -602,9 +601,9 @@ public static void ImplType( module: module); // Define the 'HasKey' method - MethodDefinition hasKeymethod = InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.HasKey( + MethodDefinition hasKeyMethod = InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.HasKey( readOnlyDictionaryType: readOnlyDictionaryType, - hasKeyMethod: interopReferences.IReadOnlyDictionary2ContainsKey(keyType, valueType), + containsKeyMethod: interopReferences.IReadOnlyDictionary2ContainsKey(keyType, valueType), interopReferences: interopReferences, emitState: emitState, module: module); @@ -628,7 +627,7 @@ public static void ImplType( vtableMethods: [ getAtMethod, sizeMethod, - hasKeymethod, + hasKeyMethod, splitMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.cs index bfb3127f7..4754ea72b 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.cs @@ -461,7 +461,7 @@ private static void Impl( } // Enforce that we did initialize all vtable entries - //ArgumentOutOfRangeException.ThrowIfNotEqual(vtableOffset, vftblType.Fields.Count, nameof(vtableMethods)); // TODO + ArgumentOutOfRangeException.ThrowIfNotEqual(vtableOffset, vftblType.Fields.Count, nameof(vtableMethods)); // Don't forget the 'ret' at the end of the static constructor _ = cctor.CilMethodBody.Instructions.Add(Ret); diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index 6153b4263..0a6dd709d 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -255,6 +255,21 @@ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( // This is for the same reason why we need the other special cases in this method: members are not analyzed. discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments])); + // Whenever we find an 'IDictionary' instantiation, we also need to track the corresponding + // 'IReadOnlyDictionary' instantiation. This is because that interface is needed to marshal + // the return value of the 'IMap.GetView' method ('IMapView'). Same as 'IVector.GetView' above. + discoveryState.TrackIReadOnlyDictionary2Type(interopReferences.IReadOnlyDictionary2.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + + // We also need to track the constructed 'ReadOnlyDictionary' type, as that is used by + // 'IDictionaryAdapter.GetView' in case the input 'IDictionary' instance doesn't implement + // 'IReadOnlyDictionary' directly. Analogous to tracking 'ReadOnlyCollection' above. + TryTrackGenericTypeInstance( + typeSignature: interopReferences.ReadOnlyDictionary2.MakeGenericReferenceType([.. typeSignature.TypeArguments]), + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); + // When we discover a constructed 'IDictionary' instantiation, we'll be generating a native object type during // the emit phase, which is used to marshal anonymous objects. This derives from 'WindowsRuntimeDictionary'. // For the 'Keys' and 'Values' properties, that base type will return instances of the 'DictionaryKeyCollection' diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IDictionary2Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IDictionary2Impl.cs new file mode 100644 index 000000000..1dab64dfd --- /dev/null +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IDictionary2Impl.cs @@ -0,0 +1,432 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.InteropGenerator.Generation; +using WindowsRuntime.InteropGenerator.References; +using static AsmResolver.PE.DotNet.Cil.CilOpCodes; + +namespace WindowsRuntime.InteropGenerator.Factories; + +/// +/// A factory for interop method definitions. +/// +internal static partial class InteropMethodDefinitionFactory +{ + /// + /// Helpers for impl types for interfaces. + /// + public static class IDictionary2Impl + { + /// + /// Creates a for the GetView export method. + /// + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static MethodDefinition GetView( + GenericInstanceTypeSignature dictionaryType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature keyType = dictionaryType.TypeArguments[0]; + TypeSignature valueType = dictionaryType.TypeArguments[1]; + + // Define the 'GetView' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int GetView(void* thisPtr, void** result) + MethodDefinition getViewMethod = new( + name: "GetView"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [ + module.CorLibTypeFactory.Void.MakePointerType(), + module.CorLibTypeFactory.Void.MakePointerType().MakePointerType()])) + { + CustomAttributes = { InteropCustomAttributeFactory.UnmanagedCallersOnly(interopReferences, module) } + }; + + // Declare the local variables: + // [0]: '' (for 'thisObject') + // [1]: 'int' (the 'HRESULT' to return) + CilLocalVariable loc_0_thisObject = new(dictionaryType.Import(module)); + CilLocalVariable loc_1_hresult = new(module.CorLibTypeFactory.Int32); + + // Labels for jumps + CilInstruction nop_beforeTry = new(Nop); + CilInstruction ldarg_0_tryStart = new(Ldarg_0); + CilInstruction ldloc_1_returnHResult = new(Ldloc_1); + CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); + CilInstruction nop_convertToUnmanaged = new(Nop); + + // Create a method body for the 'GetView' method + getViewMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // Return 'E_POINTER' if the argument is 'null' + { Ldarg_1 }, + { Ldc_I4_0 }, + { Conv_U }, + { Bne_Un_S, nop_beforeTry.CreateLabel() }, + { Ldc_I4, unchecked((int)0x80004003) }, + { Ret }, + { nop_beforeTry }, + + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(dictionaryType).Import(module) }, + { Stloc_0 }, + { Ldarg_1 }, + { Ldloc_0 }, + { Call, interopReferences.IDictionaryAdapter2GetView(keyType, valueType).Import(module) }, + { nop_convertToUnmanaged }, + { Ldc_I4_0 }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // '.catch' code + { call_catchStartMarshalException }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // Return the 'HRESULT' from location [1] + { ldloc_1_returnHResult }, + { Ret } + }, + ExceptionHandlers = + { + new CilExceptionHandler + { + HandlerType = CilExceptionHandlerType.Exception, + TryStart = ldarg_0_tryStart.CreateLabel(), + TryEnd = call_catchStartMarshalException.CreateLabel(), + HandlerStart = call_catchStartMarshalException.CreateLabel(), + HandlerEnd = ldloc_1_returnHResult.CreateLabel(), + ExceptionType = interopReferences.Exception.Import(module) + } + } + }; + + // Track the method for rewrite to marshal the result value + emitState.TrackRetValValueMethodRewrite( + retValType: interopReferences.IReadOnlyDictionary2.MakeGenericReferenceType(keyType, valueType), + method: getViewMethod, + marker: nop_convertToUnmanaged); + + return getViewMethod; + } + + /// + /// Creates a for the Insert export method. + /// + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static MethodDefinition Insert( + GenericInstanceTypeSignature dictionaryType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature keyType = dictionaryType.TypeArguments[0]; + TypeSignature valueType = dictionaryType.TypeArguments[1]; + TypeSignature keyAbiType = keyType.GetAbiType(interopReferences); + TypeSignature valueAbiType = valueType.GetAbiType(interopReferences); + + // Define the 'Insert' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int Insert(void* thisPtr, key, value, bool* result) + MethodDefinition insertMethod = new( + name: "Insert"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [ + module.CorLibTypeFactory.Void.MakePointerType(), + keyAbiType.Import(module), + valueAbiType.Import(module), + module.CorLibTypeFactory.Boolean.MakePointerType()])) + { + CustomAttributes = { InteropCustomAttributeFactory.UnmanagedCallersOnly(interopReferences, module) } + }; + + // Declare the local variables: + // [0]: '' (for 'thisObject') + // [1]: 'int' (the 'HRESULT' to return) + CilLocalVariable loc_0_thisObject = new(dictionaryType.Import(module)); + CilLocalVariable loc_1_hresult = new(module.CorLibTypeFactory.Int32); + + // Labels for jumps + CilInstruction nop_beforeTry = new(Nop); + CilInstruction ldarg_0_tryStart = new(Ldarg_0); + CilInstruction nop_parameter1Rewrite = new(Nop); + CilInstruction nop_parameter2Rewrite = new(Nop); + CilInstruction ldloc_1_returnHResult = new(Ldloc_1); + CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); + + // Create a method body for the 'Insert' method + insertMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // Return 'E_POINTER' if the argument is 'null' + { Ldarg_1 }, + { Ldc_I4_0 }, + { Conv_U }, + { Bne_Un_S, nop_beforeTry.CreateLabel() }, + { Ldc_I4, unchecked((int)0x80004003) }, + { Ret }, + { nop_beforeTry }, + + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(dictionaryType).Import(module) }, + { Stloc_0 }, + { Ldarg_3 }, + { Ldloc_0 }, + { nop_parameter1Rewrite }, + { nop_parameter2Rewrite }, + { Call, interopReferences.IDictionaryAdapter2Insert(keyType, valueType).Import(module) }, + { Stind_I1 }, + { Ldc_I4_0 }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // '.catch' code + { call_catchStartMarshalException }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // Return the 'HRESULT' from location [1] + { ldloc_1_returnHResult }, + { Ret } + }, + ExceptionHandlers = + { + new CilExceptionHandler + { + HandlerType = CilExceptionHandlerType.Exception, + TryStart = ldarg_0_tryStart.CreateLabel(), + TryEnd = call_catchStartMarshalException.CreateLabel(), + HandlerStart = call_catchStartMarshalException.CreateLabel(), + HandlerEnd = ldloc_1_returnHResult.CreateLabel(), + ExceptionType = interopReferences.Exception.Import(module) + } + } + }; + + // Track rewriting the two parameters for this method + emitState.TrackManagedParameterMethodRewrite( + parameterType: keyType, + method: insertMethod, + marker: nop_parameter1Rewrite, + parameterIndex: 1); + + emitState.TrackManagedParameterMethodRewrite( + parameterType: valueType, + method: insertMethod, + marker: nop_parameter2Rewrite, + parameterIndex: 2); + + return insertMethod; + } + + /// + /// Creates a for the Remove export method. + /// + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static MethodDefinition Remove( + GenericInstanceTypeSignature dictionaryType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature keyType = dictionaryType.TypeArguments[0]; + TypeSignature valueType = dictionaryType.TypeArguments[1]; + + // Define the 'Remove' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int Remove(void* thisPtr, key) + MethodDefinition removeMethod = new( + name: "Remove"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [ + module.CorLibTypeFactory.Void.MakePointerType(), + keyType.GetAbiType(interopReferences).Import(module)])) + { + CustomAttributes = { InteropCustomAttributeFactory.UnmanagedCallersOnly(interopReferences, module) } + }; + + // Declare the local variables: + // [0]: '' (for 'thisObject') + // [1]: 'int' (the 'HRESULT' to return) + CilLocalVariable loc_0_thisObject = new(dictionaryType.Import(module)); + CilLocalVariable loc_1_hresult = new(module.CorLibTypeFactory.Int32); + + // Labels for jumps + CilInstruction ldarg_0_tryStart = new(Ldarg_0); + CilInstruction nop_parameter1Rewrite = new(Nop); + CilInstruction ldloc_1_returnHResult = new(Ldloc_1); + CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); + + // Get the target 'Remove' method (we can optimize for 'string' types) + IMethodDescriptor adapterRemoveMethod = keyType.IsTypeOfString() + ? interopReferences.IDictionaryAdapterOfStringRemove(valueType) + : interopReferences.IDictionaryAdapter2Remove(keyType, valueType); + + // Create a method body for the 'Remove' method + removeMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(dictionaryType).Import(module) }, + { Stloc_0 }, + { Ldloc_0 }, + { nop_parameter1Rewrite }, + { Call, adapterRemoveMethod.Import(module) }, + { Ldc_I4_0 }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // '.catch' code + { call_catchStartMarshalException }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // Return the 'HRESULT' from location [1] + { ldloc_1_returnHResult }, + { Ret } + }, + ExceptionHandlers = + { + new CilExceptionHandler + { + HandlerType = CilExceptionHandlerType.Exception, + TryStart = ldarg_0_tryStart.CreateLabel(), + TryEnd = call_catchStartMarshalException.CreateLabel(), + HandlerStart = call_catchStartMarshalException.CreateLabel(), + HandlerEnd = ldloc_1_returnHResult.CreateLabel(), + ExceptionType = interopReferences.Exception.Import(module) + } + } + }; + + // If the key type is 'string', we use 'ReadOnlySpan' to avoid an allocation + TypeSignature parameterType = keyType.IsTypeOfString() + ? interopReferences.ReadOnlySpanChar + : keyType; + + // Track rewriting the parameter for this method + emitState.TrackManagedParameterMethodRewrite( + parameterType: parameterType, + method: removeMethod, + marker: nop_parameter1Rewrite, + parameterIndex: 1); + + return removeMethod; + } + + /// + /// Creates a for the Clear export method. + /// + /// The for the type. + /// The instance to use. + /// The interop module being built. + public static MethodDefinition Clear( + GenericInstanceTypeSignature dictionaryType, + InteropReferences interopReferences, + ModuleDefinition module) + { + TypeSignature keyType = dictionaryType.TypeArguments[0]; + TypeSignature valueType = dictionaryType.TypeArguments[1]; + + // Define the 'Clear' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int Clear(void* thisPtr) + MethodDefinition clearMethod = new( + name: "Clear"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()])) + { + CustomAttributes = { InteropCustomAttributeFactory.UnmanagedCallersOnly(interopReferences, module) } + }; + + // Declare the local variables: + // [0]: '' (for 'thisObject') + // [1]: 'int' (the 'HRESULT' to return) + CilLocalVariable loc_0_thisObject = new(dictionaryType.Import(module)); + CilLocalVariable loc_1_hresult = new(module.CorLibTypeFactory.Int32); + + // Labels for jumps + CilInstruction ldarg_0_tryStart = new(Ldarg_0); + CilInstruction ldloc_1_returnHResult = new(Ldloc_1); + CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); + + // Create a method body for the 'Clear' method + clearMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(dictionaryType).Import(module) }, + { Stloc_0 }, + { Ldloc_0 }, + { Call, interopReferences.ICollection1Clear(interopReferences.KeyValuePair2.MakeGenericValueType(keyType, valueType)).Import(module) }, + { Ldc_I4_0 }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // '.catch' code + { call_catchStartMarshalException }, + { Stloc_1 }, + { Leave_S, ldloc_1_returnHResult.CreateLabel() }, + + // Return the 'HRESULT' from location [1] + { ldloc_1_returnHResult }, + { Ret } + }, + ExceptionHandlers = + { + new CilExceptionHandler + { + HandlerType = CilExceptionHandlerType.Exception, + TryStart = ldarg_0_tryStart.CreateLabel(), + TryEnd = call_catchStartMarshalException.CreateLabel(), + HandlerStart = call_catchStartMarshalException.CreateLabel(), + HandlerEnd = ldloc_1_returnHResult.CreateLabel(), + ExceptionType = interopReferences.Exception.Import(module) + } + } + }; + + return clearMethod; + } + } +} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.cs index 370114930..1a71cc127 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyDictionary2Impl.cs @@ -83,7 +83,7 @@ public static MethodDefinition Lookup( { adapterLookupMethod = SignatureComparer.IgnoreVersion.Equals(readOnlyDictionaryType.GenericType, interopReferences.IReadOnlyDictionary2) ? interopReferences.IReadOnlyDictionaryAdapterOfStringLookup(valueType) - : interopReferences.IListAdapterOfStringIndexOf(); // TODO + : interopReferences.IDictionaryAdapterOfStringLookup(valueType); } else { @@ -264,7 +264,7 @@ public static MethodDefinition get_Size( /// Creates a for the HasKey export method. /// /// The for the type. - /// The interface method to invoke on . + /// The interface method to invoke on . /// The instance to use. /// The emit state for this invocation. /// The interop module being built. @@ -273,7 +273,7 @@ public static MethodDefinition get_Size( /// public static MethodDefinition HasKey( GenericInstanceTypeSignature readOnlyDictionaryType, - MemberReference hasKeyMethod, + MemberReference containsKeyMethod, InteropReferences interopReferences, InteropGeneratorEmitState emitState, ModuleDefinition module) @@ -319,14 +319,14 @@ public static MethodDefinition HasKey( { MethodSpecification hasKeyMethodSpecification = SignatureComparer.IgnoreVersion.Equals(readOnlyDictionaryType.GenericType, interopReferences.IReadOnlyDictionary2) ? interopReferences.IReadOnlyDictionaryAdapterOfStringHasKey(valueType) - : interopReferences.IReadOnlyDictionaryAdapterOfStringHasKey(valueType); // TODO + : interopReferences.IDictionaryAdapterOfStringHasKey(valueType); callHasKeyMethod = new(Call, hasKeyMethodSpecification.Import(module)); } else { // Otherwise just use 'ContainsKey' method passed as input - callHasKeyMethod = new(Callvirt, hasKeyMethod.Import(module)); + callHasKeyMethod = new(Callvirt, containsKeyMethod.Import(module)); } // Create a method body for the 'HasKey' method diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs index a0636c7e0..f2fcf13e1 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs @@ -66,7 +66,7 @@ internal sealed class InteropGeneratorDiscoveryState /// Backing field for . private readonly ConcurrentDictionary _szArrayTypes = new(SignatureComparer.IgnoreVersion); - /// Backing field to support . + /// Backing field to support . private readonly ConcurrentDictionary _markedUserDefinedTypes = new(SignatureComparer.IgnoreVersion); /// Backing field for . diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 6f2e06165..67b51c00d 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -688,6 +688,16 @@ public InteropReferences( /// public TypeReference IReadOnlyListMethods1 => field ??= _windowsRuntimeModule.CreateTypeReference("WindowsRuntime.InteropServices"u8, "IReadOnlyListMethods`1"u8); + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<TKey, TValue>. + /// + public TypeReference IDictionaryAdapter2 => field ??= _windowsRuntimeModule.CreateTypeReference("WindowsRuntime.InteropServices"u8, "IDictionaryAdapter`2"u8); + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapterExtensions. + /// + public TypeReference IDictionaryAdapterExtensions => field ??= _windowsRuntimeModule.CreateTypeReference("WindowsRuntime.InteropServices"u8, "IDictionaryAdapterExtensions"u8); + /// /// Gets the for WindowsRuntime.InteropServices.IDictionaryMethods. /// @@ -803,6 +813,11 @@ public InteropReferences( /// public TypeReference DictionaryValueCollection2 => field ??= _windowsRuntimeModule.CreateTypeReference("WindowsRuntime"u8, "DictionaryValueCollection`2"u8); + /// + /// Gets the for . + /// + public TypeReference ReadOnlyDictionary2 => field ??= _corLibTypeFactory.CorLibScope.CreateTypeReference("System.Collections.ObjectModel"u8, "ReadOnlyDictionary`2"u8); + /// /// Gets the for WindowsRuntime.ReadOnlyDictionaryKeyCollection2<TKey, TValue>. /// @@ -3642,6 +3657,146 @@ public MethodSpecification IDictionaryMethods2RemoveKeyValuePair(TypeSignature k .MakeGenericInstanceMethod(mapMethods.ToReferenceTypeSignature()); } + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<TKey, TValue>.Lookup. + /// + /// The input key type. + /// The input value type. + public MemberReference IDictionaryAdapter2Lookup(TypeSignature keyType, TypeSignature valueType) + { + return IDictionaryAdapter2 + .MakeGenericReferenceType(keyType, valueType) + .ToTypeDefOrRef() + .CreateMemberReference("Lookup"u8, MethodSignature.CreateStatic( + returnType: new GenericParameterSignature(GenericParameterType.Type, 1), + parameterTypes: [ + IDictionary2.MakeGenericReferenceType( + new GenericParameterSignature(GenericParameterType.Type, 0), + new GenericParameterSignature(GenericParameterType.Type, 1)), + keyType])); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<string, TValue>.Lookup. + /// + /// The input value type. + public MethodSpecification IDictionaryAdapterOfStringLookup(TypeSignature valueType) + { + return IDictionaryAdapterExtensions + .CreateMemberReference("Lookup"u8, MethodSignature.CreateStatic( + returnType: new GenericParameterSignature(GenericParameterType.Method, 0), + genericParameterCount: 1, + parameterTypes: [ + IDictionary2.MakeGenericReferenceType(_corLibTypeFactory.String, new GenericParameterSignature(GenericParameterType.Method, 0)), + ReadOnlySpanChar])) + .MakeGenericInstanceMethod(valueType); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<TKey, TValue>.Size. + /// + /// The input key type. + /// The input value type. + public MemberReference IDictionaryAdapter2Size(TypeSignature keyType, TypeSignature valueType) + { + return IDictionaryAdapter2 + .MakeGenericReferenceType(keyType, valueType) + .ToTypeDefOrRef() + .CreateMemberReference("Size"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.UInt32, + parameterTypes: [IDictionary2.MakeGenericReferenceType( + new GenericParameterSignature(GenericParameterType.Type, 0), + new GenericParameterSignature(GenericParameterType.Type, 1))])); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<string, TValue>.HasKey. + /// + /// The input value type. + public MethodSpecification IDictionaryAdapterOfStringHasKey(TypeSignature valueType) + { + return IDictionaryAdapterExtensions + .CreateMemberReference("HasKey"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Boolean, + genericParameterCount: 1, + parameterTypes: [ + IDictionary2.MakeGenericReferenceType(_corLibTypeFactory.String, new GenericParameterSignature(GenericParameterType.Method, 0)), + ReadOnlySpanChar])) + .MakeGenericInstanceMethod(valueType); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<string, TValue>.GetView. + /// + /// The input key type. + /// The input value type. + public MemberReference IDictionaryAdapter2GetView(TypeSignature keyType, TypeSignature valueType) + { + return IDictionaryAdapter2 + .MakeGenericReferenceType(keyType, valueType) + .ToTypeDefOrRef() + .CreateMemberReference("GetView"u8, MethodSignature.CreateStatic( + returnType: IReadOnlyDictionary2.MakeGenericReferenceType( + new GenericParameterSignature(GenericParameterType.Type, 0), + new GenericParameterSignature(GenericParameterType.Type, 1)), + parameterTypes: [IDictionary2.MakeGenericReferenceType( + new GenericParameterSignature(GenericParameterType.Type, 0), + new GenericParameterSignature(GenericParameterType.Type, 1))])); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<string, TValue>.Insert. + /// + /// The input key type. + /// The input value type. + public MemberReference IDictionaryAdapter2Insert(TypeSignature keyType, TypeSignature valueType) + { + return IDictionaryAdapter2 + .MakeGenericReferenceType(keyType, valueType) + .ToTypeDefOrRef() + .CreateMemberReference("Insert"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Boolean, + parameterTypes: [IDictionary2.MakeGenericReferenceType( + new GenericParameterSignature(GenericParameterType.Type, 0), + new GenericParameterSignature(GenericParameterType.Type, 1)), + new GenericParameterSignature(GenericParameterType.Type, 0), + new GenericParameterSignature(GenericParameterType.Type, 1)])); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<string, TValue>.Remove. + /// + /// The input key type. + /// The input value type. + public MemberReference IDictionaryAdapter2Remove(TypeSignature keyType, TypeSignature valueType) + { + return IDictionaryAdapter2 + .MakeGenericReferenceType(keyType, valueType) + .ToTypeDefOrRef() + .CreateMemberReference("Remove"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Void, + parameterTypes: [IDictionary2.MakeGenericReferenceType( + new GenericParameterSignature(GenericParameterType.Type, 0), + new GenericParameterSignature(GenericParameterType.Type, 1)), + new GenericParameterSignature(GenericParameterType.Type, 0)])); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IDictionaryAdapter<string, TValue>.Remove. + /// + /// The input value type. + public MethodSpecification IDictionaryAdapterOfStringRemove(TypeSignature valueType) + { + return IDictionaryAdapterExtensions + .CreateMemberReference("Remove"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Void, + genericParameterCount: 1, + parameterTypes: [ + IDictionary2.MakeGenericReferenceType(_corLibTypeFactory.String, new GenericParameterSignature(GenericParameterType.Method, 0)), + ReadOnlySpanChar])) + .MakeGenericInstanceMethod(valueType); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyDictionaryMethods<TKey, TValue>.Item. /// diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IDictionaryAdapterExtensions.cs b/src/WinRT.Runtime2/InteropServices/Collections/IDictionaryAdapterExtensions.cs new file mode 100644 index 000000000..114a6bef3 --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Collections/IDictionaryAdapterExtensions.cs @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Concurrent; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#pragma warning disable IDE0045, IDE0046 + +namespace WindowsRuntime.InteropServices; + +/// +/// Extensions for the type. +/// +[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, + DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, + UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] +[EditorBrowsable(EditorBrowsableState.Never)] +public static class IDictionaryAdapterExtensions +{ + extension(IDictionaryAdapter) + { + /// + /// + /// This overload can be used to avoid a allocation on the caller side. + /// + public static TValue Lookup(IDictionary dictionary, ReadOnlySpan key) + { + bool found; + TValue? value; + + // Same logic as in 'IReadOnlyDictionaryAdapterExtensions.Lookup' for trying to avoid materializing the 'string' key + if (dictionary.GetType() == typeof(Dictionary) && + Unsafe.As>(dictionary).TryGetAlternateLookup(out Dictionary.AlternateLookup> lookup1)) + { + found = lookup1.TryGetValue(key, out value); + } + else if (dictionary.GetType() == typeof(ConcurrentDictionary) && + Unsafe.As>(dictionary).TryGetAlternateLookup(out ConcurrentDictionary.AlternateLookup> lookup2)) + { + found = lookup2.TryGetValue(key, out value); + } + else if (dictionary is FrozenDictionary candidate3 && + candidate3.TryGetAlternateLookup(out FrozenDictionary.AlternateLookup> lookup3)) + { + found = lookup3.TryGetValue(key, out value); + } + else + { + found = dictionary.TryGetValue(key.ToString(), out value); + } + + // Throw the correct exception if the lookup failed + if (!found) + { + [DoesNotReturn] + static void ThrowKeyNotFoundException() + { + throw new KeyNotFoundException("Arg_KeyNotFoundWithKey") + { + HResult = WellKnownErrorCodes.E_BOUNDS + }; + } + + ThrowKeyNotFoundException(); + } + + return value!; + } + + /// + /// Determines whether the map contains the specified key. + /// + /// The wrapped instance. + /// The key to locate in the map. + /// Whether the key was found. + /// + /// This overload can be used to avoid a allocation on the caller side. + /// + /// + public static bool HasKey(IDictionary dictionary, ReadOnlySpan key) + { + // Same logic as in 'Lookup' above for trying to avoid materializing the 'string' key + if (dictionary.GetType() == typeof(Dictionary) && + Unsafe.As>(dictionary).TryGetAlternateLookup(out Dictionary.AlternateLookup> lookup1)) + { + return lookup1.ContainsKey(key); + } + + if (dictionary.GetType() == typeof(ConcurrentDictionary) && + Unsafe.As>(dictionary).TryGetAlternateLookup(out ConcurrentDictionary.AlternateLookup> lookup2)) + { + return lookup2.ContainsKey(key); + } + + if (dictionary is FrozenDictionary candidate3 && + candidate3.TryGetAlternateLookup(out FrozenDictionary.AlternateLookup> lookup3)) + { + return lookup3.ContainsKey(key); + } + + return dictionary.ContainsKey(key.ToString()); + } + + /// + /// + /// This overload can be used to avoid a allocation on the caller side. + /// + public static void Remove(IDictionary dictionary, ReadOnlySpan key) + { + bool removed; + + // Same logic as in 'IReadOnlyDictionaryAdapterExtensions.Lookup' for trying to avoid materializing the 'string' key. + // We don't handle 'FrozenDictionary' since instances of that type are immutable (removing would throw). + if (dictionary.GetType() == typeof(Dictionary) && + Unsafe.As>(dictionary).TryGetAlternateLookup(out Dictionary.AlternateLookup> lookup1)) + { + removed = lookup1.Remove(key); + } + else if (dictionary.GetType() == typeof(ConcurrentDictionary) && + Unsafe.As>(dictionary).TryGetAlternateLookup(out ConcurrentDictionary.AlternateLookup> lookup2)) + { + removed = lookup2.TryRemove(key, out _); + } + else + { + removed = dictionary.Remove(key.ToString()); + } + + if (!removed) + { + [DoesNotReturn] + static void ThrowKeyNotFoundException() + { + throw new KeyNotFoundException("Arg_KeyNotFoundWithKey") + { + HResult = WellKnownErrorCodes.E_BOUNDS + }; + } + + ThrowKeyNotFoundException(); + } + } + } +} \ No newline at end of file diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IDictionaryAdapter{TKey, TValue}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IDictionaryAdapter{TKey, TValue}.cs new file mode 100644 index 000000000..c43e9ce11 --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Collections/IDictionaryAdapter{TKey, TValue}.cs @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#pragma warning disable CS8714 + +namespace WindowsRuntime.InteropServices; + +/// +/// A stateless adapter for , to be exposed as Windows.Foundation.Collections.IMap<K, V>. +/// +/// The type of keys in the dictionary. +/// The type of values in the dictionary. +/// +[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, + DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, + UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] +[EditorBrowsable(EditorBrowsableState.Never)] +public static class IDictionaryAdapter +{ + /// + /// Returns the item at the specified key in the map. + /// + /// The wrapped instance. + /// The key associated with the item to locate. + /// The value, if an item with the specified key exists. + /// + public static TValue Lookup(IDictionary dictionary, TKey key) + { + // Same logic as in 'IReadOnlyDictionaryAdapter.Lookup' + if (!dictionary.TryGetValue(key, out TValue? value)) + { + [DoesNotReturn] + static void ThrowKeyNotFoundException() + { + throw new KeyNotFoundException("Arg_KeyNotFoundWithKey") + { + HResult = WellKnownErrorCodes.E_BOUNDS + }; + } + + ThrowKeyNotFoundException(); + } + + return value; + } + + /// + /// Gets the number of items in the map. + /// + /// The wrapped instance. + /// The number of elements in the map. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Size(IDictionary dictionary) + { + return (uint)dictionary.Count; + } + + /// + /// Returns an immutable view of the map. + /// + /// The wrapped instance. + /// The view of the map. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IReadOnlyDictionary GetView(IDictionary dictionary) + { + // Analogous implementation to 'IListAdapter.GetView', see additional notes there + return dictionary as IReadOnlyDictionary ?? new ReadOnlyDictionary(dictionary); + } + + /// + /// Inserts or replaces an item in the map. + /// + /// The wrapped instance. + /// The key associated with the item to insert. + /// The item to insert. + /// Whether an item with the specified key is an existing item that was replaced. + /// + public static bool Insert(IDictionary dictionary, TKey key, TValue value) + { + // If the target dictionary is a 'Dictionary' instance, we can optimize the + // insertion by avoiding the duplicate lookup. We only do this for exactly this type, as + // derived implementations could otherwise alter the standard indexer behavior. We don't + // need to worry about concurrent operations here: same result as using the indexer. + // The 'CollectionsMarshal' APIs already guarantee that concurrent operations might + // possibly throw or result in invalid results, but that type safety violations can't happen. + if (dictionary.GetType() == typeof(Dictionary)) + { + CollectionsMarshal.GetValueRefOrAddDefault( + dictionary: Unsafe.As>(dictionary), + key: key, + exists: out bool exists) = value; + + return exists; + } + + bool replacing = dictionary.ContainsKey(key); + + dictionary[key] = value; + + return replacing; + } + + /// + /// Removes an item from the map. + /// + /// The wrapped instance. + /// The key associated with the item to remove. + /// + public static void Remove(IDictionary dictionary, TKey key) + { + bool removed = dictionary.Remove(key); + + if (!removed) + { + [DoesNotReturn] + static void ThrowKeyNotFoundException() + { + throw new KeyNotFoundException("Arg_KeyNotFoundWithKey") + { + HResult = WellKnownErrorCodes.E_BOUNDS + }; + } + + ThrowKeyNotFoundException(); + } + } +} \ No newline at end of file