From 964e711b724fd4776990c1aba5efe7997fe0da31 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 27 Dec 2025 20:35:58 -0800 Subject: [PATCH 01/20] Refactor IReadOnlyList method definitions to accept MemberReference Updated GetAt and get_Size methods in InteropMethodDefinitionFactory.IReadOnlyList1Impl to accept explicit MemberReference parameters for the interface methods to invoke. Adjusted InteropTypeDefinitionBuilder to pass the correct references. This improves flexibility and clarity in method invocation for interop scenarios. --- ...ropTypeDefinitionBuilder.IReadOnlyList1.cs | 4 +++ ...hodDefinitionFactory.IReadOnlyList1Impl.cs | 30 ++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs index ca474b1ef..2823d274c 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs @@ -426,9 +426,12 @@ public static void ImplType( ModuleDefinition module, out TypeDefinition implType) { + TypeSignature elementType = readOnlyListType.TypeArguments[0]; + // Define the 'GetAt' method MethodDefinition getAtMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetAt( readOnlyListType: readOnlyListType, + getAtMethod: interopReferences.IReadOnlyListAdapter1GetAt(elementType), interopReferences: interopReferences, emitState: emitState, module: module); @@ -436,6 +439,7 @@ public static void ImplType( // Define the 'get_Size' method MethodDefinition sizeMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.get_Size( readOnlyListType: readOnlyListType, + sizeMethod: interopReferences.IReadOnlyListAdapter1Size(elementType), interopReferences: interopReferences, module: module); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs index a91a51881..07601d4ff 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs @@ -28,11 +28,16 @@ public static class IReadOnlyList1Impl /// Creates a for the GetAt export method. /// /// The for the type. + /// The interface method to invoke on . /// The instance to use. /// The emit state for this invocation. /// The interop module being built. + /// + /// This method can also be used to define the GetAt method for interfaces. + /// public static MethodDefinition GetAt( GenericInstanceTypeSignature readOnlyListType, + MemberReference getAtMethod, InteropReferences interopReferences, InteropGeneratorEmitState emitState, ModuleDefinition module) @@ -43,7 +48,7 @@ public static MethodDefinition GetAt( // // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] // private static int GetAt(void* thisPtr, uint index, * result) - MethodDefinition getAtMethod = new( + MethodDefinition getAtImplMethod = new( name: "GetAt"u8, attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, signature: MethodSignature.CreateStatic( @@ -70,7 +75,7 @@ public static MethodDefinition GetAt( CilInstruction nop_convertToUnmanaged = new(Nop); // Create a method body for the 'GetAt' method - getAtMethod.CilMethodBody = new CilMethodBody() + getAtImplMethod.CilMethodBody = new CilMethodBody() { LocalVariables = { loc_0_thisObject, loc_1_hresult }, Instructions = @@ -91,7 +96,7 @@ public static MethodDefinition GetAt( { Ldarg_2 }, { Ldloc_0 }, { Ldarg_1 }, - { Call, interopReferences.IReadOnlyListAdapter1GetAt(elementType).Import(module) }, + { Call, getAtMethod.Import(module) }, { nop_convertToUnmanaged }, { Ldc_I4_0 }, { Stloc_1 }, @@ -123,30 +128,33 @@ public static MethodDefinition GetAt( // Track the method for rewrite to marshal the result value emitState.TrackRetValValueMethodRewrite( retValType: elementType, - method: getAtMethod, + method: getAtImplMethod, marker: nop_convertToUnmanaged); - return getAtMethod; + return getAtImplMethod; } /// /// Creates a for the get_Size export method. /// /// The for the type. + /// The interface method to invoke on . /// The instance to use. /// The interop module being built. + /// + /// This method can also be used to define the GetAt method for interfaces. + /// public static MethodDefinition get_Size( GenericInstanceTypeSignature readOnlyListType, + MemberReference sizeMethod, InteropReferences interopReferences, ModuleDefinition module) { - TypeSignature elementType = readOnlyListType.TypeArguments[0]; - // Define the 'get_Size' method as follows: // // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] // private static int get_Size(void* thisPtr, uint* result) - MethodDefinition sizeMethod = new( + MethodDefinition sizeImplMethod = new( name: "get_Size"u8, attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, signature: MethodSignature.CreateStatic( @@ -171,7 +179,7 @@ public static MethodDefinition get_Size( CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); // Create a method body for the 'get_Size' method - sizeMethod.CilMethodBody = new CilMethodBody() + sizeImplMethod.CilMethodBody = new CilMethodBody() { LocalVariables = { loc_0_thisObject, loc_1_hresult }, Instructions = @@ -191,7 +199,7 @@ public static MethodDefinition get_Size( { Stloc_0 }, { Ldarg_1 }, { Ldloc_0 }, - { Call, interopReferences.IReadOnlyListAdapter1Size(elementType).Import(module) }, + { Call, sizeMethod.Import(module) }, { Stind_I4 }, { Ldc_I4_0 }, { Stloc_1 }, @@ -220,7 +228,7 @@ public static MethodDefinition get_Size( } }; - return sizeMethod; + return sizeImplMethod; } /// From 0d3f2829e3c17da0091ecf07f8f223893845bc5b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 27 Dec 2025 20:40:29 -0800 Subject: [PATCH 02/20] Add IListAdapter and IListAdapterExtensions Introduces IListAdapter as a stateless adapter for IList to expose vector-like operations, including GetAt, Size, IndexOf, and GetMany. Adds IListAdapterExtensions with an IndexOf overload for IList supporting ReadOnlySpan comparisons. Both are marked obsolete for internal use. --- .../Collections/IListAdapterExtensions.cs | 42 ++++++ .../Collections/IListAdapter{T}.cs | 122 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/WinRT.Runtime2/InteropServices/Collections/IListAdapterExtensions.cs create mode 100644 src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapterExtensions.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapterExtensions.cs new file mode 100644 index 000000000..9bb52083c --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapterExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace WindowsRuntime.InteropServices; + +/// +/// Extensions for the type. +/// +[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, + DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, + UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] +[EditorBrowsable(EditorBrowsableState.Never)] +public static class IListAdapterExtensions +{ + extension(IListAdapter) + { + /// + /// + public static bool IndexOf(IList list, ReadOnlySpan value, out uint index) + { + int count = list.Count; + + for (int i = 0; i < count; i++) + { + if (list[i].SequenceEqual(value)) + { + index = (uint)i; + + return true; + } + } + + index = 0; + + return false; + } + } +} \ No newline at end of file diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs new file mode 100644 index 000000000..80c91cbde --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace WindowsRuntime.InteropServices; + +/// +/// A stateless adapter for , to be exposed as Windows.Foundation.Collections.IVector<T>. +/// +/// The type of objects to enumerate. +/// +[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, + DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, + UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] +[EditorBrowsable(EditorBrowsableState.Never)] +public static class IListAdapter +{ + /// + /// Returns the item at the specified index in the vector. + /// + /// The wrapped instance. + /// The zero-based index of the item. + /// The item at the specified index. + /// + public static T GetAt(IList list, uint index) + { + IReadOnlyListAdapterHelpers.EnsureIndexInValidRange(index, list.Count); + + try + { + return list[(int)index]; + } + catch (ArgumentOutOfRangeException e) + { + e.HResult = WellKnownErrorCodes.E_BOUNDS; + + throw; + } + } + + /// + /// Gets the number of items in the vector. + /// + /// The wrapped instance. + /// The number of items in the vector. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Size(IList list) + { + return (uint)list.Count; + } + + /// + /// Retrieves the index of a specified item in the vector. + /// + /// The wrapped instance. + /// The item to find in the vector. + /// If the item is found, this is the zero-based index of the item; otherwise, this parameter is 0. + /// if the item is found; otherwise, . + /// + public static bool IndexOf(IList list, T value, out uint index) + { + int count = list.Count; + + // Scan the list and look for the target item + for (int i = 0; i < count; i++) + { + if (EqualityComparer.Default.Equals(value, list[i])) + { + index = (uint)i; + + return true; + } + } + + // Same as 'IndexOf' for 'IReadOnlyList', see notes there + index = 0; + + return false; + } + + /// + /// Retrieves multiple items from the vector view beginning at the given index. + /// + /// The wrapped instance. + /// The zero-based index to start at. + /// The target to write items into. + /// The number of items that were retrieved. This value can be less than the size of if the end of the list is reached. + /// + public static int GetMany(IList list, uint startIndex, Span items) + { + int count = list.Count; + + // See notes in 'GetMany' for 'IReadOnlyList' + if (startIndex == count) + { + return 0; + } + + IReadOnlyListAdapterHelpers.EnsureIndexInValidRange(startIndex, count); + + // Empty spans are supported, we just stop immediately + if (items.IsEmpty) + { + return 0; + } + + int itemCount = int.Min(items.Length, count - (int)startIndex); + + // Copy all items to the target span + for (int i = 0; i < itemCount; ++i) + { + items[i] = list[i + (int)startIndex]; + } + + return itemCount; + } +} \ No newline at end of file From 7d45623ced910745a0f550be921af4b9c243da23 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 27 Dec 2025 20:43:50 -0800 Subject: [PATCH 03/20] Add IListAdapter support to IList1 implementation Introduces methods for 'GetAt' and 'Size' using IListAdapter in InteropTypeDefinitionBuilder.IList1. Adds new type and member references for IListAdapter and IListAdapterExtensions in InteropReferences, enabling more complete IList1 interop support. --- .../InteropTypeDefinitionBuilder.IList1.cs | 21 +++++- .../References/InteropReferences.cs | 68 +++++++++++++++---- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index 84a9ce8ff..a6bf334cc 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -982,6 +982,23 @@ public static void ImplType( ModuleDefinition module, out TypeDefinition implType) { + TypeSignature elementType = listType.TypeArguments[0]; + + // Define the 'GetAt' method + MethodDefinition getAtMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetAt( + readOnlyListType: listType, + getAtMethod: interopReferences.IListAdapter1GetAt(elementType), + interopReferences: interopReferences, + emitState: emitState, + module: module); + + // Define the 'get_Size' method + MethodDefinition sizeMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.get_Size( + readOnlyListType: listType, + sizeMethod: interopReferences.IListAdapter1Size(elementType), + interopReferences: interopReferences, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -991,7 +1008,9 @@ public static void ImplType( interopReferences: interopReferences, module: module, implType: out implType, - vtableMethods: []); + vtableMethods: [ + getAtMethod, + sizeMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 36fa36124..f464f9313 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -658,6 +658,16 @@ public InteropReferences( /// public TypeReference IListMethods1 => field ??= _windowsRuntimeModule.CreateTypeReference("WindowsRuntime.InteropServices"u8, "IListMethods`1"u8); + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>. + /// + public TypeReference IListAdapter1 => field ??= _windowsRuntimeModule.CreateTypeReference("WindowsRuntime.InteropServices"u8, "IListAdapter`1"u8); + + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapterExtensions. + /// + public TypeReference IListAdapterExtensions => field ??= _windowsRuntimeModule.CreateTypeReference("WindowsRuntime.InteropServices"u8, "IListAdapterExtensions"u8); + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>. /// @@ -3072,6 +3082,36 @@ public MethodSpecification IListMethods1Insert(TypeSignature elementType, TypeDe .MakeGenericInstanceMethod(vectorMethods.ToReferenceTypeSignature()); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.GetAt. + /// + /// The input element type. + public MemberReference IListAdapter1GetAt(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("GetAt"u8, MethodSignature.CreateStatic( + returnType: new GenericParameterSignature(GenericParameterType.Type, 0), + parameterTypes: [ + IList1.MakeGenericReferenceType(elementType), + _corLibTypeFactory.UInt32])); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.Size. + /// + /// The input element type. + public MemberReference IListAdapter1Size(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("Size"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.UInt32, + parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.GetAt. /// @@ -3088,6 +3128,20 @@ public MemberReference IReadOnlyListAdapter1GetAt(TypeSignature elementType) _corLibTypeFactory.UInt32])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.Size. + /// + /// The input element type. + public MemberReference IReadOnlyListAdapter1Size(TypeSignature elementType) + { + return IReadOnlyListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("Size"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.UInt32, + parameterTypes: [IReadOnlyList1.MakeGenericReferenceType(elementType)])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.IndexOf. /// @@ -3119,20 +3173,6 @@ public MemberReference IReadOnlyListAdapterOfStringIndexOf() _corLibTypeFactory.UInt32.MakeByReferenceType()])); } - /// - /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.Size. - /// - /// The input element type. - public MemberReference IReadOnlyListAdapter1Size(TypeSignature elementType) - { - return IReadOnlyListAdapter1 - .MakeGenericReferenceType(elementType) - .ToTypeDefOrRef() - .CreateMemberReference("Size"u8, MethodSignature.CreateStatic( - returnType: _corLibTypeFactory.UInt32, - parameterTypes: [IReadOnlyList1.MakeGenericReferenceType(elementType)])); - } - /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListMethods<T>.Item. /// From 9a9ced344021008107cb575b3ae1d08dfcba2f7f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 27 Dec 2025 20:58:30 -0800 Subject: [PATCH 04/20] Add GetView method to IListAdapter Introduced the GetView method to return an immutable view of the IList as IReadOnlyList. This improves performance by avoiding unnecessary allocations when possible and aligns with the expected behavior for vector views in interop scenarios. --- .../Collections/IListAdapter{T}.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs index 80c91cbde..f2cebcb64 100644 --- a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -54,6 +55,26 @@ public static uint Size(IList list) return (uint)list.Count; } + /// + /// Returns an immutable view of the vector. + /// + /// The wrapped instance. + /// The view of the vector. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IReadOnlyList GetView(IList list) + { + // This list is not really read-only: once marshalled, native code could do + // a 'QueryInterface' call back to 'IVector', which would succeed, and would + // return a modifiable reference for this view. We believe this is accetable, as + // it allows us to gain some performance. For instance, in most situations (because + // pretty much all built-in .NET collection types implementing 'IList' also implement + // 'IReadOnlyList'), this allows us to not allocate anything. That is, when native + // code calls 'GetView()', it would get back the same CCW instance, just through a + // 'QueryInterface' call for 'IVectorView' instead. + return list as IReadOnlyList ?? new ReadOnlyCollection(list); + } + /// /// Retrieves the index of a specified item in the vector. /// From f266a1fcc3b919b0a1d97b7e4f117538cc6455e3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 27 Dec 2025 21:09:16 -0800 Subject: [PATCH 05/20] Add GetView support for IList interop generation Introduces a new GetView method for IList interop types, including its method factory and reference resolution. Updates type discovery to track IReadOnlyList when IList is found, ensuring correct marshaling for IVector.GetView. This enables proper support for projecting IList to IReadOnlyList in generated interop code. --- .../InteropTypeDefinitionBuilder.IList1.cs | 10 +- .../InteropTypeDiscovery.Generics.cs | 4 + ...teropMethodDefinitionFactory.IList1Impl.cs | 130 ++++++++++++++++++ .../References/InteropReferences.cs | 14 ++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index a6bf334cc..4394ad1ae 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -999,6 +999,13 @@ public static void ImplType( interopReferences: interopReferences, module: module); + // Define the 'GetView' method + MethodDefinition getViewMethod = InteropMethodDefinitionFactory.IList1Impl.GetView( + listType: listType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1010,7 +1017,8 @@ public static void ImplType( implType: out implType, vtableMethods: [ getAtMethod, - sizeMethod]); + sizeMethod, + getViewMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index 6b8294d01..05f77a8cf 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -226,6 +226,10 @@ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IList1)) { discoveryState.TrackIList1Type(typeSignature); + + // Whenever we find an 'IList' instantiation, we also need to track the corresponding 'IReadOnlyList' instantiation. + // This is because that interface is needed to marshal the return value of the 'IVector.GetView' method ('IVectorView'). + discoveryState.TrackIReadOnlyList1Type(interopReferences.IReadOnlyList1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); } else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyList1)) { diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs new file mode 100644 index 000000000..8cdcf66fd --- /dev/null +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -0,0 +1,130 @@ +// 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; + +#pragma warning disable IDE1006 + +namespace WindowsRuntime.InteropGenerator.Factories; + +/// +/// A factory for interop method definitions. +/// +internal static partial class InteropMethodDefinitionFactory +{ + /// + /// Helpers for impl types for interfaces. + /// + public static class IList1Impl + { + /// + /// 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 listType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + // 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(listType.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(listType).Import(module) }, + { Stloc_0 }, + { Ldarg_1 }, + { Ldloc_0 }, + { Call, interopReferences.IListAdapter1GetView(elementType).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.IReadOnlyList1.MakeGenericReferenceType(elementType), + method: getViewMethod, + marker: nop_convertToUnmanaged); + + return getViewMethod; + } + } +} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index f464f9313..e7565c682 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3112,6 +3112,20 @@ public MemberReference IListAdapter1Size(TypeSignature elementType) parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.GetView. + /// + /// The input element type. + public MemberReference IListAdapter1GetView(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("GetView"u8, MethodSignature.CreateStatic( + returnType: IReadOnlyList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), + parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.GetAt. /// From 41ac7f150664d33d03c173c383fe53f5389b809b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 28 Dec 2025 15:17:34 -0800 Subject: [PATCH 06/20] Prevent infinite recursion in user-defined type discovery Adds a check to avoid reprocessing user-defined types during interop type discovery, preventing stack overflows from infinite recursion. Also tracks ReadOnlyCollection when IList is discovered to ensure correct marshaling, and introduces supporting fields and methods for marking user-defined types. --- .../Discovery/InteropTypeDiscovery.Generics.cs | 10 ++++++++++ .../Discovery/InteropTypeDiscovery.cs | 18 ++++++++++++++++++ .../InteropGeneratorDiscoveryState.cs | 14 ++++++++++++++ .../References/InteropReferences.cs | 5 +++++ 4 files changed, 47 insertions(+) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index 05f77a8cf..ba291ee87 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -230,6 +230,16 @@ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( // Whenever we find an 'IList' instantiation, we also need to track the corresponding 'IReadOnlyList' instantiation. // This is because that interface is needed to marshal the return value of the 'IVector.GetView' method ('IVectorView'). discoveryState.TrackIReadOnlyList1Type(interopReferences.IReadOnlyList1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + + // We also need to track the constructed 'ReadOnlyCollection' type, as that is used by 'IListAdapter.GetView' in case the + // input 'IList' instance doesn't implement 'IReadOnlyList' directly. In that case, we return a 'ReadOnlyCollection' + // object instead. This needs special handling because we won't analyze indirect (generated) calls into that adapter type. + TryTrackGenericTypeInstance( + typeSignature: interopReferences.ReadOnlyCollection1.MakeGenericReferenceType([.. typeSignature.TypeArguments]), + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyList1)) { diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index 22d45aae9..6546c8fac 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -109,6 +109,24 @@ public static void TryTrackExposedUserDefinedType( return; } + // Check if this is the first time that this user-defined type has been seen, and stop immediately if not. + // If the type has been seen before, it means that it either has already been fully processed, or that it + // is currently being processed (possibly by another thread, if multi-threading discovery is enabled). The + // reason for this check is not so much to improve performance (although it does avoid some repeated work), + // but most importantly to avoid stack overlows due to infinite recursion in cases where user-defined types + // implement interfaces that then transitively required the same user-defined type to be tracked. + // + // For instance, consider a scenario where 'List' is being discovered. While processing the implemented + // interfaces, 'IList' will also be discovered. This will then require 'ReadOnlyCollection' to be + // tracked, because it is used by the fallback code for the CCW implementation method of 'IVector.GetView'. + // However, 'ReadOnlyCollection' itself will also implement 'IList', which would then require tracking + // 'ReadOnlyCollection' itself again too, etc. That would just recurse forever without this check, because + // all those interfaces would keep being discovered before the initial processing of the type has finished. + if (!discoveryState.TryMarkUserDefinedType(typeSignature)) + { + return; + } + // Reuse the thread-local builder to track all implemented interfaces for the current type TypeSignatureEquatableSet.Builder interfaces = TypeSignatures ??= new TypeSignatureEquatableSet.Builder(); diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs index 1ef8c2e9d..a0636c7e0 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorDiscoveryState.cs @@ -66,6 +66,9 @@ internal sealed class InteropGeneratorDiscoveryState /// Backing field for . private readonly ConcurrentDictionary _szArrayTypes = new(SignatureComparer.IgnoreVersion); + /// Backing field to support . + private readonly ConcurrentDictionary _markedUserDefinedTypes = new(SignatureComparer.IgnoreVersion); + /// Backing field for . private readonly ConcurrentDictionary _userDefinedTypes = new(SignatureComparer.IgnoreVersion); @@ -389,6 +392,17 @@ public void TrackSzArrayType(SzArrayTypeSignature szArrayType) _ = _szArrayTypes.TryAdd(szArrayType, 0); } + /// + /// Tries to mark a user-defined type as having been seen the first time, + /// and indicating that it's in the process of being processed. + /// + /// The user-defined type. + /// Whether this was the first time that was seen. + public bool TryMarkUserDefinedType(TypeSignature userDefinedType) + { + return _markedUserDefinedTypes.TryAdd(userDefinedType, 0); + } + /// /// Tracks a user-defined type. /// diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index e7565c682..bace454ae 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -338,6 +338,11 @@ public InteropReferences( /// public TypeReference KeyValuePair2 => field ??= _corLibTypeFactory.CorLibScope.CreateTypeReference("System.Collections.Generic"u8, "KeyValuePair`2"u8); + /// + /// Gets the for . + /// + public TypeReference ReadOnlyCollection1 => field ??= _corLibTypeFactory.CorLibScope.CreateTypeReference("System.Collections.ObjectModel"u8, "ReadOnlyCollection`1"u8); + /// /// Gets the for . /// From 2b92404a09d06982462b37c1f968619c02e5f852 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 28 Dec 2025 20:41:09 -0800 Subject: [PATCH 07/20] Add IListAdapter1 IndexOf support to interop generator Introduces methods and references to support IListAdapter1.IndexOf in the interop generator. Updates method factories and type builders to correctly handle IndexOf for both IList and IReadOnlyList scenarios, including string specialization. --- .../InteropTypeDefinitionBuilder.IList1.cs | 11 ++++++- ...ropTypeDefinitionBuilder.IReadOnlyList1.cs | 1 + ...hodDefinitionFactory.IReadOnlyList1Impl.cs | 26 +++++++++++----- .../References/InteropReferences.cs | 31 +++++++++++++++++++ 4 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index 4394ad1ae..eb62673e8 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1006,6 +1006,14 @@ public static void ImplType( emitState: emitState, module: module); + // Define the 'IndexOf' method + MethodDefinition indexOfMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.IndexOf( + readOnlyListType: listType, + indexOfMethod: interopReferences.IListAdapter1IndexOf(elementType), + interopReferences: interopReferences, + emitState: emitState, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1018,7 +1026,8 @@ public static void ImplType( vtableMethods: [ getAtMethod, sizeMethod, - getViewMethod]); + getViewMethod, + indexOfMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs index 2823d274c..05986e5a2 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs @@ -446,6 +446,7 @@ public static void ImplType( // Define the 'IndexOf' method MethodDefinition indexOfMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.IndexOf( readOnlyListType: readOnlyListType, + indexOfMethod: interopReferences.IReadOnlyListAdapter1IndexOf(elementType), interopReferences: interopReferences, emitState: emitState, module: module); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs index 07601d4ff..6c2fefc2b 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs @@ -235,11 +235,13 @@ public static MethodDefinition get_Size( /// Creates a for the IndexOf export method. /// /// The for the type. + /// The interface method to invoke on . /// The instance to use. /// The emit state for this invocation. /// The interop module being built. public static MethodDefinition IndexOf( GenericInstanceTypeSignature readOnlyListType, + MemberReference indexOfMethod, InteropReferences interopReferences, InteropGeneratorEmitState emitState, ModuleDefinition module) @@ -250,7 +252,7 @@ public static MethodDefinition IndexOf( // // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] // private static int IndexOf(void* thisPtr, value, uint* index, bool* result) - MethodDefinition indexOfMethod = new( + MethodDefinition indexOfImplMethod = new( name: "IndexOf"u8, attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, signature: MethodSignature.CreateStatic( @@ -278,13 +280,23 @@ public static MethodDefinition IndexOf( CilInstruction ldloc_1_returnHResult = new(Ldloc_1); CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); + MemberReference adapterIndexOfMethod; + // Get the target 'IndexOf' method (we can optimize for 'string' types) - MemberReference adapterIndexOfMethod = elementType.IsTypeOfString() - ? interopReferences.IReadOnlyListAdapterOfStringIndexOf() - : interopReferences.IReadOnlyListAdapter1IndexOf(elementType); + if (elementType.IsTypeOfString()) + { + adapterIndexOfMethod = SignatureComparer.IgnoreVersion.Equals(readOnlyListType.GenericType, interopReferences.IReadOnlyList1) + ? interopReferences.IReadOnlyListAdapterOfStringIndexOf() + : interopReferences.IListAdapterOfStringIndexOf(); + } + else + { + // Otherwise use the provided method directly (it will always be valid) + adapterIndexOfMethod = indexOfMethod; + } // Create a method body for the 'IndexOf' method - indexOfMethod.CilMethodBody = new CilMethodBody() + indexOfImplMethod.CilMethodBody = new CilMethodBody() { LocalVariables = { loc_0_thisObject, loc_1_hresult }, Instructions = @@ -347,11 +359,11 @@ public static MethodDefinition IndexOf( // Track rewriting the two parameters for this method emitState.TrackManagedParameterMethodRewrite( parameterType: parameterType, - method: indexOfMethod, + method: indexOfImplMethod, marker: nop_parameter1Rewrite, parameterIndex: 1); - return indexOfMethod; + return indexOfImplMethod; } #pragma warning disable IDE0017 diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index bace454ae..af37db4fd 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3131,6 +3131,37 @@ public MemberReference IListAdapter1GetView(TypeSignature elementType) parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.IndexOf. + /// + /// The input element type. + public MemberReference IListAdapter1IndexOf(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("IndexOf"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Boolean, + parameterTypes: [ + IList1.MakeGenericReferenceType(elementType), + new GenericParameterSignature(GenericParameterType.Type, 0), + _corLibTypeFactory.UInt32.MakeByReferenceType()])); + } + + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<string>.IndexOf. + /// + public MemberReference IListAdapterOfStringIndexOf() + { + return IListAdapterExtensions + .CreateMemberReference("IndexOf"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Boolean, + parameterTypes: [ + IList1.MakeGenericReferenceType(_corLibTypeFactory.String), + ReadOnlySpanChar, + _corLibTypeFactory.UInt32.MakeByReferenceType()])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.GetAt. /// From 57a1ec9f3060e86d4c973402c357154e8b9d4a87 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 16:30:58 -0800 Subject: [PATCH 08/20] Add SetAt support to IList interop generation Implements the SetAt method for IList interop, including method definition generation, reference resolution, and adapter implementation. This enables setting values at a specific index in generated COM interop for IList. --- .../InteropTypeDefinitionBuilder.IList1.cs | 10 +- ...teropMethodDefinitionFactory.IList1Impl.cs | 96 +++++++++++++++++++ .../References/InteropReferences.cs | 17 ++++ .../Collections/IListAdapter{T}.cs | 23 +++++ 4 files changed, 145 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index eb62673e8..95ce93124 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1014,6 +1014,13 @@ public static void ImplType( emitState: emitState, module: module); + // Define the 'SetAt' method + MethodDefinition setAtMethod = InteropMethodDefinitionFactory.IList1Impl.SetAt( + listType: listType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1027,7 +1034,8 @@ public static void ImplType( getAtMethod, sizeMethod, getViewMethod, - indexOfMethod]); + indexOfMethod, + setAtMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index 8cdcf66fd..d4b66f2bc 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -126,5 +126,101 @@ public static MethodDefinition GetView( return getViewMethod; } + + /// + /// Creates a for the SetAt export method. + /// + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static MethodDefinition SetAt( + GenericInstanceTypeSignature listType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + // Define the 'SetAt' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int SetAt(void* thisPtr, uint index, value) + MethodDefinition setAtMethod = new( + name: "SetAt"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [ + module.CorLibTypeFactory.Void.MakePointerType(), + module.CorLibTypeFactory.UInt32, + elementType.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(listType.Import(module)); + CilLocalVariable loc_1_hresult = new(module.CorLibTypeFactory.Int32); + + // Labels for jumps + CilInstruction ldarg_0_tryStart = new(Ldarg_0); + CilInstruction nop_parameter2Rewrite = new(Nop); + 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 'SetAt' method + setAtMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(listType).Import(module) }, + { Stloc_0 }, + { Ldloc_0 }, + { Ldarg_1 }, + { nop_parameter2Rewrite }, + { Call, interopReferences.IListAdapter1SetAt(elementType).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) + } + } + }; + + // Track rewriting the parameter for this method + emitState.TrackManagedParameterMethodRewrite( + parameterType: elementType, + method: setAtMethod, + marker: nop_parameter2Rewrite, + parameterIndex: 2); + + return setAtMethod; + } } } \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index af37db4fd..56d6579e5 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3131,6 +3131,23 @@ public MemberReference IListAdapter1GetView(TypeSignature elementType) parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.SetAt. + /// + /// The input element type. + public MemberReference IListAdapter1SetAt(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("SetAt"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Void, + parameterTypes: [ + IList1.MakeGenericReferenceType(elementType), + _corLibTypeFactory.UInt32, + new GenericParameterSignature(GenericParameterType.Type, 0)])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.IndexOf. /// diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs index f2cebcb64..86ece2eeb 100644 --- a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs @@ -75,6 +75,29 @@ public static IReadOnlyList GetView(IList list) return list as IReadOnlyList ?? new ReadOnlyCollection(list); } + /// + /// Sets the value at the specified index in the vector. + /// + /// The wrapped instance. + /// The zero-based index at which to set the value. + /// The item to set. + /// + public static void SetAt(IList list, uint index, T value) + { + IReadOnlyListAdapterHelpers.EnsureIndexInValidRange(index, list.Count); + + try + { + list[(int)index] = value; + } + catch (ArgumentOutOfRangeException e) + { + e.HResult = WellKnownErrorCodes.E_BOUNDS; + + throw; + } + } + /// /// Retrieves the index of a specified item in the vector. /// From 7540acf485da7b16dc5c05c637bef9d6d3438469 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 16:31:54 -0800 Subject: [PATCH 09/20] Remove unused variable and reorder IListAdapter1SetAt method Eliminated an unused variable in InteropMethodDefinitionFactory.IList1Impl.cs and moved the IListAdapter1SetAt method in InteropReferences.cs for better code organization. --- ...teropMethodDefinitionFactory.IList1Impl.cs | 1 - .../References/InteropReferences.cs | 34 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index d4b66f2bc..b85228e32 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -170,7 +170,6 @@ public static MethodDefinition SetAt( CilInstruction nop_parameter2Rewrite = new(Nop); 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 'SetAt' method setAtMethod.CilMethodBody = new CilMethodBody() diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 56d6579e5..5d2070c8c 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3131,23 +3131,6 @@ public MemberReference IListAdapter1GetView(TypeSignature elementType) parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); } - /// - /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.SetAt. - /// - /// The input element type. - public MemberReference IListAdapter1SetAt(TypeSignature elementType) - { - return IListAdapter1 - .MakeGenericReferenceType(elementType) - .ToTypeDefOrRef() - .CreateMemberReference("SetAt"u8, MethodSignature.CreateStatic( - returnType: _corLibTypeFactory.Void, - parameterTypes: [ - IList1.MakeGenericReferenceType(elementType), - _corLibTypeFactory.UInt32, - new GenericParameterSignature(GenericParameterType.Type, 0)])); - } - /// /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.IndexOf. /// @@ -3179,6 +3162,23 @@ public MemberReference IListAdapterOfStringIndexOf() _corLibTypeFactory.UInt32.MakeByReferenceType()])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.SetAt. + /// + /// The input element type. + public MemberReference IListAdapter1SetAt(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("SetAt"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Void, + parameterTypes: [ + IList1.MakeGenericReferenceType(elementType), + _corLibTypeFactory.UInt32, + new GenericParameterSignature(GenericParameterType.Type, 0)])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.GetAt. /// From 1747a867577b9f22922ddb020f9fc34a87910cde Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 16:52:33 -0800 Subject: [PATCH 10/20] Add InsertAt support for IList interop Introduces the InsertAt method for IList interop, including its definition in IListAdapter, method generation in InteropMethodDefinitionFactory, and reference handling in InteropReferences. This enables correct handling of the IVector.InsertAt method in generated interop code. --- .../InteropTypeDefinitionBuilder.IList1.cs | 10 ++- ...ethodDefinitionFactory.IEnumerator1Impl.cs | 4 +- ...teropMethodDefinitionFactory.IList1Impl.cs | 71 ++++++++++++++++--- .../References/InteropReferences.cs | 17 +++++ .../Collections/IListAdapter{T}.cs | 25 +++++++ 5 files changed, 115 insertions(+), 12 deletions(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index 95ce93124..84470ec71 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1021,6 +1021,13 @@ public static void ImplType( emitState: emitState, module: module); + // Define the 'InsertAt' method + MethodDefinition insertAtMethod = InteropMethodDefinitionFactory.IList1Impl.InsertAt( + listType: listType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1035,7 +1042,8 @@ public static void ImplType( sizeMethod, getViewMethod, indexOfMethod, - setAtMethod]); + setAtMethod, + insertAtMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IEnumerator1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IEnumerator1Impl.cs index 12462937c..c1ddf2a59 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IEnumerator1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IEnumerator1Impl.cs @@ -269,7 +269,7 @@ public static MethodDefinition GetMany( } /// - /// Creates a for the get_Current export method. + /// Creates a for the get_Current or MoveNext export method. /// /// The name of the method to generate. /// The adapter method to forward the call to. @@ -307,7 +307,7 @@ private static MethodDefinition HasCurrentOrMoveNext( CilInstruction ldloc_0_returnHResult = new(Ldloc_0); CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); - // Create a method body for the 'get_HasCurrent' method + // Create a method body for the method boolMethod.CilMethodBody = new CilMethodBody() { // Declare 1 variable: diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index b85228e32..14d4a64a0 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using AsmResolver; using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; @@ -142,12 +143,64 @@ public static MethodDefinition SetAt( { TypeSignature elementType = listType.TypeArguments[0]; - // Define the 'SetAt' method as follows: + return SetAtOrInsertAt( + methodName: "SetAt"u8, + adapterMethod: interopReferences.IListAdapter1SetAt(elementType), + listType: listType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + } + + /// + /// Creates a for the InsertAt export method. + /// + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static MethodDefinition InsertAt( + GenericInstanceTypeSignature listType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + return SetAtOrInsertAt( + methodName: "InsertAt"u8, + adapterMethod: interopReferences.IListAdapter1InsertAt(elementType), + listType: listType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + } + + /// + /// Creates a for the SetAt or InsertAt export method. + /// + /// The name of the method to generate. + /// The adapter method to forward the call to. + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + private static MethodDefinition SetAtOrInsertAt( + Utf8String methodName, + MemberReference adapterMethod, + GenericInstanceTypeSignature listType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + // Define the 'SetAt' or 'InsertAt' method as follows: // // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - // private static int SetAt(void* thisPtr, uint index, value) - MethodDefinition setAtMethod = new( - name: "SetAt"u8, + // private static int >(void* thisPtr, uint index, value) + MethodDefinition setAtOrInsertAtMethod = new( + name: methodName, attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, signature: MethodSignature.CreateStatic( returnType: module.CorLibTypeFactory.Int32, @@ -171,8 +224,8 @@ public static MethodDefinition SetAt( CilInstruction ldloc_1_returnHResult = new(Ldloc_1); CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); - // Create a method body for the 'SetAt' method - setAtMethod.CilMethodBody = new CilMethodBody() + // Create a method body for the 'SetAt' or 'InsertAt' method + setAtOrInsertAtMethod.CilMethodBody = new CilMethodBody() { LocalVariables = { loc_0_thisObject, loc_1_hresult }, Instructions = @@ -184,7 +237,7 @@ public static MethodDefinition SetAt( { Ldloc_0 }, { Ldarg_1 }, { nop_parameter2Rewrite }, - { Call, interopReferences.IListAdapter1SetAt(elementType).Import(module) }, + { Call, adapterMethod.Import(module) }, { Ldc_I4_0 }, { Stloc_1 }, { Leave_S, ldloc_1_returnHResult.CreateLabel() }, @@ -215,11 +268,11 @@ public static MethodDefinition SetAt( // Track rewriting the parameter for this method emitState.TrackManagedParameterMethodRewrite( parameterType: elementType, - method: setAtMethod, + method: setAtOrInsertAtMethod, marker: nop_parameter2Rewrite, parameterIndex: 2); - return setAtMethod; + return setAtOrInsertAtMethod; } } } \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 5d2070c8c..fbb47f4aa 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3179,6 +3179,23 @@ public MemberReference IListAdapter1SetAt(TypeSignature elementType) new GenericParameterSignature(GenericParameterType.Type, 0)])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.InsertAt. + /// + /// The input element type. + public MemberReference IListAdapter1InsertAt(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("InsertAt"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Void, + parameterTypes: [ + IList1.MakeGenericReferenceType(elementType), + _corLibTypeFactory.UInt32, + new GenericParameterSignature(GenericParameterType.Type, 0)])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.GetAt. /// diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs index 86ece2eeb..17d73b96e 100644 --- a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs @@ -98,6 +98,31 @@ public static void SetAt(IList list, uint index, T value) } } + /// + /// Inserts an item at a specified index in the vector. + /// + /// The wrapped instance. + /// The zero-based index. + /// The item to insert. + /// + public static void InsertAt(IList list, uint index, T value) + { + // Inserting at an index one past the end of the list is equivalent to just + // appending an item, so we need to ensure that we're within '[0, count + 1)'. + IReadOnlyListAdapterHelpers.EnsureIndexInValidRange(index, list.Count + 1); + + try + { + list.Insert((int)index, value); + } + catch (ArgumentOutOfRangeException e) + { + e.HResult = WellKnownErrorCodes.E_BOUNDS; + + throw; + } + } + /// /// Retrieves the index of a specified item in the vector. /// From 4e3227cdefcd0d554f5dec9f9a78cbae620d250e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 16:53:00 -0800 Subject: [PATCH 11/20] Move IndexOf method in IListAdapter Relocated the IndexOf method and its documentation within IListAdapter to improve code organization and maintain logical grouping of related methods. No functional changes were made. --- .../Collections/IListAdapter{T}.cs | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs index 17d73b96e..63ff0bc35 100644 --- a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs @@ -75,6 +75,35 @@ public static IReadOnlyList GetView(IList list) return list as IReadOnlyList ?? new ReadOnlyCollection(list); } + /// + /// Retrieves the index of a specified item in the vector. + /// + /// The wrapped instance. + /// The item to find in the vector. + /// If the item is found, this is the zero-based index of the item; otherwise, this parameter is 0. + /// if the item is found; otherwise, . + /// + public static bool IndexOf(IList list, T value, out uint index) + { + int count = list.Count; + + // Scan the list and look for the target item + for (int i = 0; i < count; i++) + { + if (EqualityComparer.Default.Equals(value, list[i])) + { + index = (uint)i; + + return true; + } + } + + // Same as 'IndexOf' for 'IReadOnlyList', see notes there + index = 0; + + return false; + } + /// /// Sets the value at the specified index in the vector. /// @@ -123,35 +152,6 @@ public static void InsertAt(IList list, uint index, T value) } } - /// - /// Retrieves the index of a specified item in the vector. - /// - /// The wrapped instance. - /// The item to find in the vector. - /// If the item is found, this is the zero-based index of the item; otherwise, this parameter is 0. - /// if the item is found; otherwise, . - /// - public static bool IndexOf(IList list, T value, out uint index) - { - int count = list.Count; - - // Scan the list and look for the target item - for (int i = 0; i < count; i++) - { - if (EqualityComparer.Default.Equals(value, list[i])) - { - index = (uint)i; - - return true; - } - } - - // Same as 'IndexOf' for 'IReadOnlyList', see notes there - index = 0; - - return false; - } - /// /// Retrieves multiple items from the vector view beginning at the given index. /// From 47033d64a70c1aa53a0697b1d5666159efc48383 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 16:59:23 -0800 Subject: [PATCH 12/20] Add RemoveAt support to IList interop Implemented the RemoveAt method for IList interop, including its definition in the generator, method factory, and interop references. Also added the actual RemoveAt implementation in IListAdapter to handle item removal and error translation. --- .../InteropTypeDefinitionBuilder.IList1.cs | 9 +- ...teropMethodDefinitionFactory.IList1Impl.cs | 85 ++++++++++++++++++- .../References/InteropReferences.cs | 16 ++++ .../Collections/IListAdapter{T}.cs | 22 +++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index 84470ec71..1fa9c7318 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1028,6 +1028,12 @@ public static void ImplType( emitState: emitState, module: module); + // Define the 'RemoveAt' method + MethodDefinition removeAtMethod = InteropMethodDefinitionFactory.IList1Impl.RemoveAt( + listType: listType, + interopReferences: interopReferences, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1043,7 +1049,8 @@ public static void ImplType( getViewMethod, indexOfMethod, setAtMethod, - insertAtMethod]); + insertAtMethod, + removeAtMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index 14d4a64a0..1b1a60fe7 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -176,6 +176,89 @@ public static MethodDefinition InsertAt( module: module); } + /// + /// Creates a for the RemoveAt export method. + /// + /// The for the type. + /// The instance to use. + /// The interop module being built. + public static MethodDefinition RemoveAt( + GenericInstanceTypeSignature listType, + InteropReferences interopReferences, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + // Define the 'RemoveAt' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int RemoveAt(void* thisPtr, uint index) + MethodDefinition removeAtMethod = new( + name: "RemoveAt"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [ + module.CorLibTypeFactory.Void.MakePointerType(), + module.CorLibTypeFactory.UInt32])) + { + CustomAttributes = { InteropCustomAttributeFactory.UnmanagedCallersOnly(interopReferences, module) } + }; + + // Declare the local variables: + // [0]: '' (for 'thisObject') + // [1]: 'int' (the 'HRESULT' to return) + CilLocalVariable loc_0_thisObject = new(listType.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 'RemoveAt' method + removeAtMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(listType).Import(module) }, + { Stloc_0 }, + { Ldloc_0 }, + { Ldarg_1 }, + { Call, interopReferences.IListAdapter1RemoveAt(elementType).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 removeAtMethod; + } + /// /// Creates a for the SetAt or InsertAt export method. /// @@ -198,7 +281,7 @@ private static MethodDefinition SetAtOrInsertAt( // Define the 'SetAt' or 'InsertAt' method as follows: // // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - // private static int >(void* thisPtr, uint index, value) + // private static int (void* thisPtr, uint index, value) MethodDefinition setAtOrInsertAtMethod = new( name: methodName, attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index fbb47f4aa..99f55dfbd 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3196,6 +3196,22 @@ public MemberReference IListAdapter1InsertAt(TypeSignature elementType) new GenericParameterSignature(GenericParameterType.Type, 0)])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.RemoveAt. + /// + /// The input element type. + public MemberReference IListAdapter1RemoveAt(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("RemoveAt"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Void, + parameterTypes: [ + IList1.MakeGenericReferenceType(elementType), + _corLibTypeFactory.UInt32])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.GetAt. /// diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs index 63ff0bc35..4c3e22d99 100644 --- a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs @@ -152,6 +152,28 @@ public static void InsertAt(IList list, uint index, T value) } } + /// + /// Removes the item at the specified index in the vector. + /// + /// The wrapped instance. + /// The zero-based index of the vector item to remove. + /// + public static void RemoveAt(IList list, uint index) + { + IReadOnlyListAdapterHelpers.EnsureIndexInValidRange(index, list.Count); + + try + { + list.RemoveAt((int)index); + } + catch (ArgumentOutOfRangeException e) + { + e.HResult = WellKnownErrorCodes.E_BOUNDS; + + throw; + } + } + /// /// Retrieves multiple items from the vector view beginning at the given index. /// From 78ae3a127f58b86ca33059e7c806bd44d6b2293d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 21:15:08 -0800 Subject: [PATCH 13/20] Add support for IList.Append interop method Introduces the 'Append' method for IList in the interop generator. Adds method definition, reference, and implementation logic to support exporting the Append method for COM interop scenarios. --- .../InteropTypeDefinitionBuilder.IList1.cs | 10 +- ...teropMethodDefinitionFactory.IList1Impl.cs | 93 +++++++++++++++++++ .../References/InteropReferences.cs | 14 +++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index 1fa9c7318..d843cea74 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1034,6 +1034,13 @@ public static void ImplType( interopReferences: interopReferences, module: module); + // Define the 'Append' method + MethodDefinition appendMethod = InteropMethodDefinitionFactory.IList1Impl.Append( + listType: listType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1050,7 +1057,8 @@ public static void ImplType( indexOfMethod, setAtMethod, insertAtMethod, - removeAtMethod]); + removeAtMethod, + appendMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index 1b1a60fe7..fdf2125bc 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -259,6 +259,99 @@ public static MethodDefinition RemoveAt( return removeAtMethod; } + /// + /// Creates a for the Append export method. + /// + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static MethodDefinition Append( + GenericInstanceTypeSignature listType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + // Define the 'Append' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int Append(void* thisPtr, value) + MethodDefinition appendMethod = new( + name: "Append"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [ + module.CorLibTypeFactory.Void.MakePointerType(), + elementType.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(listType.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)); + + // Create a method body for the 'Append' method + appendMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(listType).Import(module) }, + { Stloc_0 }, + { Ldloc_0 }, + { nop_parameter1Rewrite }, + { Callvirt, interopReferences.IList1Append(elementType).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) + } + } + }; + + // Track rewriting the parameter for this method + emitState.TrackManagedParameterMethodRewrite( + parameterType: elementType, + method: appendMethod, + marker: nop_parameter1Rewrite, + parameterIndex: 1); + + return appendMethod; + } + /// /// Creates a for the SetAt or InsertAt export method. /// diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 99f55dfbd..54549aeef 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -2904,6 +2904,20 @@ public MemberReference IList1Insert(TypeSignature elementType) new GenericParameterSignature(GenericParameterType.Type, 0)])); } + /// + /// Gets the for . + /// + /// The input element type. + public MemberReference IList1Append(TypeSignature elementType) + { + return IList1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("Append"u8, MethodSignature.CreateInstance( + returnType: _corLibTypeFactory.Void, + parameterTypes: [new GenericParameterSignature(GenericParameterType.Type, 0)])); + } + /// /// Gets the for . /// From 8bc00fe95afc9289c34e4be9fbe38b538ce88231 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 21:25:28 -0800 Subject: [PATCH 14/20] Add RemoveAtEnd support for IList interop Implements the RemoveAtEnd method for IList interop scenarios, including generator, factory, references, and adapter logic. This enables proper handling of the IVector.RemoveAtEnd operation in Windows Runtime interop, with error handling for empty collections. --- .../InteropTypeDefinitionBuilder.IList1.cs | 9 ++- ...teropMethodDefinitionFactory.IList1Impl.cs | 80 +++++++++++++++++++ .../References/InteropReferences.cs | 14 ++++ .../Collections/IListAdapter{T}.cs | 40 ++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index d843cea74..a5cbb1be8 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1041,6 +1041,12 @@ public static void ImplType( emitState: emitState, module: module); + // Define the 'RemoveAtEnd' method + MethodDefinition removeAtEndMethod = InteropMethodDefinitionFactory.IList1Impl.RemoveAtEnd( + listType: listType, + interopReferences: interopReferences, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1058,7 +1064,8 @@ public static void ImplType( setAtMethod, insertAtMethod, removeAtMethod, - appendMethod]); + appendMethod, + removeAtEndMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index fdf2125bc..f9f123de5 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -352,6 +352,86 @@ public static MethodDefinition Append( return appendMethod; } + /// + /// Creates a for the RemoveAtEnd export method. + /// + /// The for the type. + /// The instance to use. + /// The interop module being built. + public static MethodDefinition RemoveAtEnd( + GenericInstanceTypeSignature listType, + InteropReferences interopReferences, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + // Define the 'RemoveAtEnd' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int RemoveAtEnd(void* thisPtr) + MethodDefinition removeAtEndMethod = new( + name: "RemoveAtEnd"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(listType.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 'RemoveAtEnd' method + removeAtEndMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_hresult }, + Instructions = + { + // '.try' code + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(listType).Import(module) }, + { Stloc_0 }, + { Ldloc_0 }, + { Call, interopReferences.IListAdapter1RemoveAtEnd(elementType).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 removeAtEndMethod; + } + /// /// Creates a for the SetAt or InsertAt export method. /// diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 54549aeef..096df4913 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3226,6 +3226,20 @@ public MemberReference IListAdapter1RemoveAt(TypeSignature elementType) _corLibTypeFactory.UInt32])); } + /// + /// Gets the for WindowsRuntime.InteropServices.IListAdapter<T>.RemoveAtEnd. + /// + /// The input element type. + public MemberReference IListAdapter1RemoveAtEnd(TypeSignature elementType) + { + return IListAdapter1 + .MakeGenericReferenceType(elementType) + .ToTypeDefOrRef() + .CreateMemberReference("RemoveAtEnd"u8, MethodSignature.CreateStatic( + returnType: _corLibTypeFactory.Void, + parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); + } + /// /// Gets the for WindowsRuntime.InteropServices.IReadOnlyListAdapter<T>.GetAt. /// diff --git a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs index 4c3e22d99..497d1dd36 100644 --- a/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs +++ b/src/WinRT.Runtime2/InteropServices/Collections/IListAdapter{T}.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace WindowsRuntime.InteropServices; @@ -174,6 +175,45 @@ public static void RemoveAt(IList list, uint index) } } + /// + /// Removes the last item from the vector. + /// + /// The wrapped instance. + /// + public static void RemoveAtEnd(IList list) + { + // Manually hoist the count to avoid doing an interface dispatch call twice. + // We don't need to protect against mutation here: the actual list type would + // already either handle this, or the following 'RemoveAt' call might throw. + int count = list.Count; + + // Check that the list isn't empty, as that would of course be invalid + if (count == 0) + { + [DoesNotReturn] + static void ThrowInvalidOperationException() + { + throw new InvalidOperationException("InvalidOperation_CannotRemoveLastFromEmptyCollection") + { + HResult = WellKnownErrorCodes.E_BOUNDS + }; + } + + ThrowInvalidOperationException(); + } + + try + { + list.RemoveAt(count - 1); + } + catch (ArgumentOutOfRangeException e) + { + e.HResult = WellKnownErrorCodes.E_BOUNDS; + + throw; + } + } + /// /// Retrieves multiple items from the vector view beginning at the given index. /// From 4e0cc226154bc1f532140e7baac76b686c6cd2b2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 21:34:47 -0800 Subject: [PATCH 15/20] Add support for IList.Clear interop method Implemented the 'Clear' method for IList interop by defining a new method in InteropMethodDefinitionFactory.IList1Impl and wiring it up in InteropTypeDefinitionBuilder. This enables COM interop consumers to call the Clear method on IList implementations. --- .../InteropTypeDefinitionBuilder.IList1.cs | 9 ++- ...teropMethodDefinitionFactory.IList1Impl.cs | 80 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index a5cbb1be8..f3bf7371d 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1047,6 +1047,12 @@ public static void ImplType( interopReferences: interopReferences, module: module); + // Define the 'Clear' method + MethodDefinition clearMethod = InteropMethodDefinitionFactory.IList1Impl.Clear( + listType: listType, + interopReferences: interopReferences, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1065,7 +1071,8 @@ public static void ImplType( insertAtMethod, removeAtMethod, appendMethod, - removeAtEndMethod]); + removeAtEndMethod, + clearMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index f9f123de5..921aa8c73 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -432,6 +432,86 @@ public static MethodDefinition RemoveAtEnd( return removeAtEndMethod; } + /// + /// 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 listType, + InteropReferences interopReferences, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + + // 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(listType.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(listType).Import(module) }, + { Stloc_0 }, + { Ldloc_0 }, + { Call, interopReferences.ICollection1Clear(elementType).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; + } + /// /// Creates a for the SetAt or InsertAt export method. /// From 6724b05d1028ecc192793099d6aacccb069075d1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 21:37:45 -0800 Subject: [PATCH 16/20] Add GetMany method support for IList interop Introduces the GetMany method to IList interop type definitions, aligning its implementation with IReadOnlyList. Updates method signatures and calls to pass the appropriate getAtMethod reference, enabling consistent method generation for both IList and IReadOnlyList interfaces. --- .../Builders/InteropTypeDefinitionBuilder.IList1.cs | 11 ++++++++++- .../InteropTypeDefinitionBuilder.IReadOnlyList1.cs | 1 + ...teropMethodDefinitionFactory.IReadOnlyList1Impl.cs | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index f3bf7371d..9828f046b 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1053,6 +1053,14 @@ public static void ImplType( interopReferences: interopReferences, module: module); + // Define the 'GetMany' method + MethodDefinition getManyMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetMany( + readOnlyListType: listType, + getAtMethod: interopReferences.IListAdapter1GetAt(elementType), + interopReferences: interopReferences, + emitState: emitState, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1072,7 +1080,8 @@ public static void ImplType( removeAtMethod, appendMethod, removeAtEndMethod, - clearMethod]); + clearMethod, + getManyMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs index 05986e5a2..13898edb1 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IReadOnlyList1.cs @@ -454,6 +454,7 @@ public static void ImplType( // Define the 'GetMany' method MethodDefinition getManyMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetMany( readOnlyListType: readOnlyListType, + getAtMethod: interopReferences.IReadOnlyListAdapter1GetAt(elementType), interopReferences: interopReferences, emitState: emitState, module: module); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs index 6c2fefc2b..82a02a032 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IReadOnlyList1Impl.cs @@ -371,11 +371,16 @@ public static MethodDefinition IndexOf( /// Creates a for the GetMany export method. /// /// The for the type. + /// The interface method to invoke on . /// The instance to use. /// The emit state for this invocation. /// The interop module being built. + /// /// + /// This method can also be used to define the GetMany method for interfaces. + /// public static MethodDefinition GetMany( GenericInstanceTypeSignature readOnlyListType, + MemberReference getAtMethod, InteropReferences interopReferences, InteropGeneratorEmitState emitState, ModuleDefinition module) From a34bb6a7f96351869fe2d9356bd371326a277710 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 29 Dec 2025 21:56:30 -0800 Subject: [PATCH 17/20] Add support for managed value method rewrites Introduces the ManagedValue method rewrite type, including its model, factory logic, and emit state tracking. Updates the interop generator to handle marshalling of managed values, enabling more flexible and accurate IL generation for managed parameters. --- ...opMethodRewriteFactory.ManagedParameter.cs | 12 +- ...nteropMethodRewriteFactory.ManagedValue.cs | 103 ++++++++++++++++++ .../Generation/InteropGenerator.Emit.cs | 11 ++ .../Generation/InteropGeneratorEmitState.cs | 21 ++++ .../MethodRewriteInfo.ManagedValue.cs | 24 ++++ 5 files changed, 161 insertions(+), 10 deletions(-) create mode 100644 src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs create mode 100644 src/WinRT.Interop.Generator/Models/MethodRewriteInfo/MethodRewriteInfo.ManagedValue.cs diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs index 3feba88bb..b878b08d9 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs @@ -69,9 +69,10 @@ public static void RewriteMethod( throw WellKnownInteropExceptions.MethodRewriteSourceParameterTypeMismatchError(source.ParameterType, parameterType, method); } + // See comments in the marshalling code for 'ManagedValue' for additional details on the code below. + // The two are identical, the only difference is this method also loads the parameters on the stack. if (parameterType.IsValueType) { - // If the return type is blittable, we can just load it directly it directly (simplest case) if (parameterType.IsBlittable(interopReferences)) { body.Instructions.ReferenceReplaceRange(marker, CilInstruction.CreateLdarg(parameterIndex)); @@ -80,26 +81,20 @@ public static void RewriteMethod( { InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - // For 'Nullable' parameters (i.e. we have an 'IReference' interface pointer), we unbox the underlying type body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, marshallerType.UnboxToManaged().Import(module))]); } else if (SignatureComparer.IgnoreVersion.Equals(parameterType, interopReferences.ReadOnlySpanChar)) { - // When marshalling 'ReadOnlySpan' values, we also use 'HStringMarshaller', but without materializing the 'string' object body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, interopReferences.HStringMarshallerConvertToManagedUnsafe.Import(module))]); } else { - // The last case handles all other value types. It doesn't matter if they possibly hold some unmanaged - // resources, as they're only being used as parameters. That means the caller is responsible for disposal. - // This case can also handle 'KeyValuePair<,>' instantiations, which are just marshalled normally too. InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - // We can directly call the marshaller and return it, no 'try/finally' complexity is needed body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))]); @@ -107,17 +102,14 @@ public static void RewriteMethod( } else if (parameterType.IsTypeOfString()) { - // When marshalling 'string' values, we must use 'HStringMarshaller' (the ABI type is not actually a COM object) body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, interopReferences.HStringMarshallerConvertToManaged.Import(module))]); } else { - // Get the marshaller type for all other reference types (including generics) InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - // Marshal the value normally (the caller will own the native resource) body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))]); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs new file mode 100644 index 000000000..c6555517a --- /dev/null +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Collections; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; +using WindowsRuntime.InteropGenerator.Errors; +using WindowsRuntime.InteropGenerator.Generation; +using WindowsRuntime.InteropGenerator.References; +using WindowsRuntime.InteropGenerator.Resolvers; +using static AsmResolver.PE.DotNet.Cil.CilOpCodes; + +namespace WindowsRuntime.InteropGenerator.Factories; + +/// +/// A factory to rewrite interop method definitons, and add marshalling code as needed. +/// +internal static partial class InteropMethodRewriteFactory +{ + /// + /// Contains the logic for marshalling managed values (i.e. parameters that are passed to managed methods, already on the stack). + /// + public static class ManagedValue + { + /// + /// Performs two-pass code generation on a target method to marshal an unmanaged parameter. + /// + /// The parameter type that needs to be marshalled. + /// The target method to perform two-pass code generation on. + /// The target IL instruction to replace with the right set of specialized instructions. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static void RewriteMethod( + TypeSignature parameterType, + MethodDefinition method, + CilInstruction marker, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + // Validate that we do have some IL body for the input method (this should always be the case) + if (method.CilMethodBody is not CilMethodBody body) + { + throw WellKnownInteropExceptions.MethodRewriteMissingBodyError(method); + } + + // If we didn't find the marker, it means the target method is either invalid + if (!body.Instructions.ReferenceContains(marker)) + { + throw WellKnownInteropExceptions.MethodRewriteMarkerInstructionNotFoundError(marker, method); + } + + if (parameterType.IsValueType) + { + // If the return type is blittable, we have nothing else to do (the value is already loaded) + if (parameterType.IsBlittable(interopReferences)) + { + return; + } + + // Handle the other possible value types + if (parameterType.IsConstructedNullableValueType(interopReferences)) + { + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); + + // For 'Nullable' parameters (i.e. we have an 'IReference' interface pointer), we unbox the underlying type + body.Instructions.ReferenceReplaceRange(marker, new CilInstruction(Call, marshallerType.UnboxToManaged().Import(module))); + } + else if (SignatureComparer.IgnoreVersion.Equals(parameterType, interopReferences.ReadOnlySpanChar)) + { + // When marshalling 'ReadOnlySpan' values, we also use 'HStringMarshaller', but without materializing the 'string' object + body.Instructions.ReferenceReplaceRange(marker, new CilInstruction(Call, interopReferences.HStringMarshallerConvertToManagedUnsafe.Import(module))); + } + else + { + // The last case handles all other value types. It doesn't matter if they possibly hold some unmanaged + // resources, as they're only being used as parameters. That means the caller is responsible for disposal. + // This case can also handle 'KeyValuePair<,>' instantiations, which are just marshalled normally too. + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); + + // We can directly call the marshaller and return it, no 'try/finally' complexity is needed + body.Instructions.ReferenceReplaceRange(marker, new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))); + } + } + else if (parameterType.IsTypeOfString()) + { + // When marshalling 'string' values, we must use 'HStringMarshaller' (the ABI type is not actually a COM object) + body.Instructions.ReferenceReplaceRange(marker, new CilInstruction(Call, interopReferences.HStringMarshallerConvertToManaged.Import(module))); + } + else + { + // Get the marshaller type for all other reference types (including generics) + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); + + // Marshal the value normally (the caller will own the native resource) + body.Instructions.ReferenceReplaceRange(marker, new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))); + } + } + } +} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs index d45f21950..4a92f1f3c 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs @@ -2040,6 +2040,17 @@ private static void RewriteMethodDefinitions( module: module); break; + // Rewrite managed values + case MethodRewriteInfo.ManagedValue managedValueInfo: + InteropMethodRewriteFactory.ManagedValue.RewriteMethod( + parameterType: managedValueInfo.Type, + method: managedValueInfo.Method, + marker: managedValueInfo.Marker, + interopReferences: interopReferences, + emitState: emitState, + module: module); + break; + // Rewrite managed parameters case MethodRewriteInfo.ManagedParameter managedParameterInfo: InteropMethodRewriteFactory.ManagedParameter.RewriteMethod( diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorEmitState.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorEmitState.cs index 6718ea006..1009530b2 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorEmitState.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorEmitState.cs @@ -156,6 +156,27 @@ public void TrackManagedParameterMethodRewrite( }); } + /// + /// Tracks a method rewrite that involves marshalling a managed value in the specified method. + /// + /// + /// + /// + public void TrackManagedValueMethodRewrite( + TypeSignature parameterType, + MethodDefinition method, + CilInstruction marker) + { + ThrowIfReadOnly(); + + _methodRewriteInfos.Add(new MethodRewriteInfo.ManagedValue + { + Type = parameterType, + Method = method, + Marker = marker + }); + } + /// /// Tracks a method rewrite that involves loading a native parameter in the specified method. /// diff --git a/src/WinRT.Interop.Generator/Models/MethodRewriteInfo/MethodRewriteInfo.ManagedValue.cs b/src/WinRT.Interop.Generator/Models/MethodRewriteInfo/MethodRewriteInfo.ManagedValue.cs new file mode 100644 index 000000000..2987cb263 --- /dev/null +++ b/src/WinRT.Interop.Generator/Models/MethodRewriteInfo/MethodRewriteInfo.ManagedValue.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.InteropGenerator.Models; + +/// +internal partial class MethodRewriteInfo +{ + /// + /// Contains info for a target method for two-pass IL generation, for a managed value. + /// + /// + public sealed class ManagedValue : MethodRewriteInfo + { + /// + public override int CompareTo(MethodRewriteInfo? other) + { + // 'ManagedValue' objects have no additional state, so just compare with the base state + return ReferenceEquals(this, other) + ? 0 + : CompareByMethodRewriteInfo(other); + } + } +} From 260e669c2c9eec10eb45571351748f95eb997c9e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 30 Dec 2025 13:58:18 -0800 Subject: [PATCH 18/20] Add CreateLdind method to CilInstructionExtensions Introduces the CreateLdind method for generating instructions to load values indirectly based on the type signature. This complements the existing CreateStind method and improves support for indirect value loading in IL code generation. --- .../Extensions/CilInstructionExtensions.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs b/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs index 140b96d6c..e8fcc6ef4 100644 --- a/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs @@ -98,8 +98,38 @@ public static CilInstruction CreateStind(TypeSignature type, ModuleDefinition mo ElementType.R4 => new CilInstruction(Stind_R4), ElementType.R8 => new CilInstruction(Stind_R8), ElementType.ValueType when type.Resolve() is { IsClass: true, IsEnum: true } => new CilInstruction(Stind_I4), + ElementType.I => new CilInstruction(Stind_I), _ => new CilInstruction(Stobj, type.Import(module).ToTypeDefOrRef()), }; } + + /// + /// Create a new instruction loading a value indirectly from a target location. + /// + /// The type of value to load. + /// The in use. + /// The instruction. + [SuppressMessage("Style", "IDE0072", Justification = "We use 'ldobj' for all other possible types.")] + public static CilInstruction CreateLdind(TypeSignature type, ModuleDefinition module) + { + return type.ElementType switch + { + ElementType.Boolean => new CilInstruction(Ldind_I1), + ElementType.Char => new CilInstruction(Ldind_I2), + ElementType.I1 => new CilInstruction(Ldind_I1), + ElementType.U1 => new CilInstruction(Ldind_I1), + ElementType.I2 => new CilInstruction(Ldind_I2), + ElementType.U2 => new CilInstruction(Ldind_I2), + ElementType.I4 => new CilInstruction(Ldind_I4), + ElementType.U4 => new CilInstruction(Ldind_I4), + ElementType.I8 => new CilInstruction(Ldind_I8), + ElementType.U8 => new CilInstruction(Ldind_I8), + ElementType.R4 => new CilInstruction(Ldind_R4), + ElementType.R8 => new CilInstruction(Ldind_R8), + ElementType.ValueType when type.Resolve() is { IsClass: true, IsEnum: true } => new CilInstruction(Ldind_I4), + ElementType.I => new CilInstruction(Ldind_I), + _ => new CilInstruction(Ldobj, type.Import(module).ToTypeDefOrRef()), + }; + } } } \ No newline at end of file From f754540c9ed58288491e594b6f2880453e9cbb7f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 30 Dec 2025 13:58:50 -0800 Subject: [PATCH 19/20] Add ReplaceAll method to IList1 interop implementation Introduces a new ReplaceAll method for IList1 interop, including its definition, method body, and integration into the type builder. Also updates method calls to use ICollection1.Add instead of IList1.Append and removes the unused IList1Append reference. --- .../InteropTypeDefinitionBuilder.IList1.cs | 10 +- ...teropMethodDefinitionFactory.IList1Impl.cs | 150 +++++++++++++++++- ...nteropMethodRewriteFactory.ManagedValue.cs | 1 - .../References/InteropReferences.cs | 14 -- 4 files changed, 158 insertions(+), 17 deletions(-) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs index 9828f046b..e3620df85 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IList1.cs @@ -1061,6 +1061,13 @@ public static void ImplType( emitState: emitState, module: module); + // Define the 'ReplaceAll' method + MethodDefinition replaceAllMethod = InteropMethodDefinitionFactory.IList1Impl.ReplaceAll( + listType: listType, + interopReferences: interopReferences, + emitState: emitState, + module: module); + Impl( interfaceType: ComInterfaceType.InterfaceIsIInspectable, ns: InteropUtf8NameFactory.TypeNamespace(listType), @@ -1081,7 +1088,8 @@ public static void ImplType( appendMethod, removeAtEndMethod, clearMethod, - getManyMethod]); + getManyMethod, + replaceAllMethod]); // Track the type (it may be needed by COM interface entries for user-defined types) emitState.TrackTypeDefinition(implType, listType, "Impl"); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs index 921aa8c73..fc7ee5629 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodDefinitionFactory.IList1Impl.cs @@ -314,7 +314,7 @@ public static MethodDefinition Append( { Stloc_0 }, { Ldloc_0 }, { nop_parameter1Rewrite }, - { Callvirt, interopReferences.IList1Append(elementType).Import(module) }, + { Callvirt, interopReferences.ICollection1Add(elementType).Import(module) }, { Ldc_I4_0 }, { Stloc_1 }, { Leave_S, ldloc_1_returnHResult.CreateLabel() }, @@ -512,6 +512,154 @@ public static MethodDefinition Clear( return clearMethod; } + /// + /// Creates a for the ReplaceAll export method. + /// + /// The for the type. + /// The instance to use. + /// The emit state for this invocation. + /// The interop module being built. + public static MethodDefinition ReplaceAll( + GenericInstanceTypeSignature listType, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState, + ModuleDefinition module) + { + TypeSignature elementType = listType.TypeArguments[0]; + TypeSignature elementAbiType = elementType.GetAbiType(interopReferences); + + // Define the 'ReplaceAll' method as follows: + // + // [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + // private static int ReplaceAll(void* thisPtr, uint size, * items) + MethodDefinition replaceAllMethod = new( + name: "ReplaceAll"u8, + attributes: MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, + signature: MethodSignature.CreateStatic( + returnType: module.CorLibTypeFactory.Int32, + parameterTypes: [ + module.CorLibTypeFactory.Void.MakePointerType(), + module.CorLibTypeFactory.UInt32, + elementAbiType.Import(module).MakePointerType()])) + { + CustomAttributes = { InteropCustomAttributeFactory.UnmanagedCallersOnly(interopReferences, module) } + }; + + // Declare the local variables: + // [0]: '' (for 'thisObject') + // [1]: 'uint' (for the 'i' loop variable) + // [2]: 'int' (the 'HRESULT' to return) + CilLocalVariable loc_0_thisObject = new(listType.Import(module)); + CilLocalVariable loc_1_i = new(module.CorLibTypeFactory.UInt32); + CilLocalVariable loc_2_hresult = new(module.CorLibTypeFactory.Int32); + + // Labels for jumps + CilInstruction ldarg_2_nullCheck = new(Ldarg_2); + CilInstruction nop_beforeTry = new(Nop); + CilInstruction ldarg_0_tryStart = new(Ldarg_0); + CilInstruction ldloc_1_loopCheck = new(Ldloc_1); + CilInstruction ldloc_0_loopStart = new(Ldloc_0); + CilInstruction nop_convertToManaged = new(Nop); + CilInstruction ldloc_2_returnHResult = new(Ldloc_2); + CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module)); + + // Create a method body for the 'ReplaceAll' method + replaceAllMethod.CilMethodBody = new CilMethodBody() + { + LocalVariables = { loc_0_thisObject, loc_1_i, loc_2_hresult }, + Instructions = + { + // Return 'S_OK' if the size is '0' + { Ldarg_1 }, + { Brtrue_S, ldarg_2_nullCheck.CreateLabel() }, + { Ldc_I4_0 }, + { Ret }, + + // Return 'E_POINTER' if the array is 'null' + { ldarg_2_nullCheck }, + { Ldc_I4_0 }, + { Conv_U }, + { Bne_Un_S, nop_beforeTry.CreateLabel() }, + { Ldc_I4, unchecked((int)0x80004003) }, + { Ret }, + { nop_beforeTry }, + + // '.try' code to load the list + { ldarg_0_tryStart }, + { Call, interopReferences.ComInterfaceDispatchGetInstance.MakeGenericInstanceMethod(listType).Import(module) }, + { Stloc_0 }, + + // list.Clear(); + { Ldloc_0 }, + { Callvirt, interopReferences.ICollection1Clear(elementType).Import(module) }, + + // int i = 0; + { Ldc_I4_0 }, + { Stloc_1 }, + { Br_S, ldloc_1_loopCheck.CreateLabel() }, + + // list.Add((items[i])); + { ldloc_0_loopStart }, + { Ldarg_2 }, + { Ldloc_1 }, + { Conv_U8 }, + { Sizeof, elementAbiType.Import(module).ToTypeDefOrRef() }, + { Conv_I8 }, + { Mul }, + { Conv_I }, + { Add }, + { CilInstruction.CreateLdind(elementAbiType, module) }, + { nop_convertToManaged }, + { Callvirt, interopReferences.ICollection1Add(elementType).Import(module) }, + + // i++; + { Ldloc_1 }, + { Ldc_I4_1 }, + { Add }, + { Stloc_1 }, + + // if (i < size) goto LoopStart; + { ldloc_1_loopCheck }, + { Ldarg_1 }, + { Blt_Un_S, ldloc_0_loopStart.CreateLabel() }, + + // return S_OK + { Ldc_I4_0 }, + { Stloc_2 }, + { Leave_S, ldloc_2_returnHResult.CreateLabel() }, + + // '.catch' code + { call_catchStartMarshalException }, + { Stloc_2 }, + { Leave_S, ldloc_2_returnHResult.CreateLabel() }, + + // Return the 'HRESULT' from location [1] + { ldloc_2_returnHResult }, + { Ret } + }, + ExceptionHandlers = + { + new CilExceptionHandler + { + HandlerType = CilExceptionHandlerType.Exception, + TryStart = ldarg_0_tryStart.CreateLabel(), + TryEnd = call_catchStartMarshalException.CreateLabel(), + HandlerStart = call_catchStartMarshalException.CreateLabel(), + HandlerEnd = ldloc_2_returnHResult.CreateLabel(), + ExceptionType = interopReferences.Exception.Import(module) + } + } + }; + + // Track rewriting each item for this method + emitState.TrackManagedValueMethodRewrite( + parameterType: elementType, + method: replaceAllMethod, + marker: nop_convertToManaged); + + return replaceAllMethod; + } + /// /// Creates a for the SetAt or InsertAt export method. /// diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs index c6555517a..d4d0fe9b1 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedValue.cs @@ -3,7 +3,6 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; -using AsmResolver.DotNet.Collections; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Cil; using WindowsRuntime.InteropGenerator.Errors; diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 096df4913..eb97ffed1 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -2904,20 +2904,6 @@ public MemberReference IList1Insert(TypeSignature elementType) new GenericParameterSignature(GenericParameterType.Type, 0)])); } - /// - /// Gets the for . - /// - /// The input element type. - public MemberReference IList1Append(TypeSignature elementType) - { - return IList1 - .MakeGenericReferenceType(elementType) - .ToTypeDefOrRef() - .CreateMemberReference("Append"u8, MethodSignature.CreateInstance( - returnType: _corLibTypeFactory.Void, - parameterTypes: [new GenericParameterSignature(GenericParameterType.Type, 0)])); - } - /// /// Gets the for . /// From 30644718f732a8d4e767d5ccde6fedcc964296b4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 2 Jan 2026 13:52:44 -0800 Subject: [PATCH 20/20] Refactor generic parameter usage in method signatures Replaces usage of 'elementType' with explicit 'GenericParameterSignature(GenericParameterType.Type, 0)' in IList1 and IReadOnlyList1 generic reference type creation for method signatures. This ensures consistent and correct handling of generic parameters in interop reference methods. --- .../References/InteropReferences.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index eb97ffed1..643651317 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -3099,7 +3099,7 @@ public MemberReference IListAdapter1GetAt(TypeSignature elementType) .CreateMemberReference("GetAt"u8, MethodSignature.CreateStatic( returnType: new GenericParameterSignature(GenericParameterType.Type, 0), parameterTypes: [ - IList1.MakeGenericReferenceType(elementType), + IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), _corLibTypeFactory.UInt32])); } @@ -3114,7 +3114,7 @@ public MemberReference IListAdapter1Size(TypeSignature elementType) .ToTypeDefOrRef() .CreateMemberReference("Size"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.UInt32, - parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); + parameterTypes: [IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0))])); } /// @@ -3128,7 +3128,7 @@ public MemberReference IListAdapter1GetView(TypeSignature elementType) .ToTypeDefOrRef() .CreateMemberReference("GetView"u8, MethodSignature.CreateStatic( returnType: IReadOnlyList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), - parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); + parameterTypes: [IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0))])); } /// @@ -3143,7 +3143,7 @@ public MemberReference IListAdapter1IndexOf(TypeSignature elementType) .CreateMemberReference("IndexOf"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.Boolean, parameterTypes: [ - IList1.MakeGenericReferenceType(elementType), + IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), new GenericParameterSignature(GenericParameterType.Type, 0), _corLibTypeFactory.UInt32.MakeByReferenceType()])); } @@ -3174,7 +3174,7 @@ public MemberReference IListAdapter1SetAt(TypeSignature elementType) .CreateMemberReference("SetAt"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.Void, parameterTypes: [ - IList1.MakeGenericReferenceType(elementType), + IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), _corLibTypeFactory.UInt32, new GenericParameterSignature(GenericParameterType.Type, 0)])); } @@ -3191,7 +3191,7 @@ public MemberReference IListAdapter1InsertAt(TypeSignature elementType) .CreateMemberReference("InsertAt"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.Void, parameterTypes: [ - IList1.MakeGenericReferenceType(elementType), + IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), _corLibTypeFactory.UInt32, new GenericParameterSignature(GenericParameterType.Type, 0)])); } @@ -3208,7 +3208,7 @@ public MemberReference IListAdapter1RemoveAt(TypeSignature elementType) .CreateMemberReference("RemoveAt"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.Void, parameterTypes: [ - IList1.MakeGenericReferenceType(elementType), + IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), _corLibTypeFactory.UInt32])); } @@ -3223,7 +3223,7 @@ public MemberReference IListAdapter1RemoveAtEnd(TypeSignature elementType) .ToTypeDefOrRef() .CreateMemberReference("RemoveAtEnd"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.Void, - parameterTypes: [IList1.MakeGenericReferenceType(elementType)])); + parameterTypes: [IList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0))])); } /// @@ -3238,7 +3238,7 @@ public MemberReference IReadOnlyListAdapter1GetAt(TypeSignature elementType) .CreateMemberReference("GetAt"u8, MethodSignature.CreateStatic( returnType: new GenericParameterSignature(GenericParameterType.Type, 0), parameterTypes: [ - IReadOnlyList1.MakeGenericReferenceType(elementType), + IReadOnlyList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), _corLibTypeFactory.UInt32])); } @@ -3253,7 +3253,7 @@ public MemberReference IReadOnlyListAdapter1Size(TypeSignature elementType) .ToTypeDefOrRef() .CreateMemberReference("Size"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.UInt32, - parameterTypes: [IReadOnlyList1.MakeGenericReferenceType(elementType)])); + parameterTypes: [IReadOnlyList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0))])); } /// @@ -3268,7 +3268,7 @@ public MemberReference IReadOnlyListAdapter1IndexOf(TypeSignature elementType) .CreateMemberReference("IndexOf"u8, MethodSignature.CreateStatic( returnType: _corLibTypeFactory.Boolean, parameterTypes: [ - IReadOnlyList1.MakeGenericReferenceType(elementType), + IReadOnlyList1.MakeGenericReferenceType(new GenericParameterSignature(GenericParameterType.Type, 0)), new GenericParameterSignature(GenericParameterType.Type, 0), _corLibTypeFactory.UInt32.MakeByReferenceType()])); }