From 4f1eae83274ebffa44718ef9a5ac87e95b86eae4 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Sat, 24 Oct 2026 09:05:26 -0700 Subject: [PATCH] Fix IEnumerator adapter initialization for COM RCWs (#136392) Initialize COM enumerator adapters lazily per RCW instead of during interface resolution, which cached dispatch can bypass. Reuse the existing adapter cache. Add regression coverage for repeated fresh RCWs, all three enumerator entry points, empty/nonempty collections, and reset after exhaustion. Fixes dotnet/runtime#136118 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ee70b0a5-4f16-4838-9d10-fe9924d6864c --- .../src/System/__ComObject.cs | 31 +++------- .../Interop/COM/Dynamic/CollectionTest.cs | 56 +++++++++++++++++++ 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/__ComObject.cs b/src/coreclr/System.Private.CoreLib/src/System/__ComObject.cs index 464ebfaff83268..d522a93b475765 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/__ComObject.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/__ComObject.cs @@ -217,24 +217,6 @@ RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTy } else if (interfaceType.Equals(typeof(IEnumerator).TypeHandle)) { - object? enumVariantImpl = GetData(typeof(IEnumeratorOverEnumVARIANTImpl)); - if (enumVariantImpl is null) - { - IntPtr enumVariantPtr = IntPtr.Zero; - try - { IEnumVARIANT enumVariant = (IEnumVARIANT)this; - enumVariantPtr = Marshal.GetIUnknownForObject(enumVariant); - enumVariantImpl = (IEnumerator)EnumeratorToEnumVariantMarshaler.GetInstance(null).MarshalNativeToManaged(enumVariantPtr); - } - finally - { - if (enumVariantPtr != IntPtr.Zero) - { - Marshal.Release(enumVariantPtr); - } - } - SetData(typeof(IEnumeratorOverEnumVARIANTImpl), enumVariantImpl); - } return typeof(IEnumeratorOverEnumVARIANTImpl).TypeHandle; } @@ -313,24 +295,25 @@ private interface ICustomAdapterOverDispatchImpl : ICustomAdapter [DynamicInterfaceCastableImplementation] private interface IEnumeratorOverEnumVARIANTImpl : IEnumerator { + private static IEnumerator GetEnumerator(__ComObject obj) => + ComDataHelpers.GetOrCreateManagedViewFromComData( + obj, static enumVariant => new EnumeratorViewOfEnumVariant(enumVariant)); + bool IEnumerator.MoveNext() { - __ComObject co = (__ComObject)this; - return ((IEnumerator)co.GetData(typeof(IEnumeratorOverEnumVARIANTImpl))!).MoveNext(); + return GetEnumerator((__ComObject)this).MoveNext(); } void IEnumerator.Reset() { - __ComObject co = (__ComObject)this; - ((IEnumerator)co.GetData(typeof(IEnumeratorOverEnumVARIANTImpl))!).Reset(); + GetEnumerator((__ComObject)this).Reset(); } object IEnumerator.Current { get { - __ComObject co = (__ComObject)this; - return ((IEnumerator)co.GetData(typeof(IEnumeratorOverEnumVARIANTImpl))!).Current; + return GetEnumerator((__ComObject)this).Current; } } } diff --git a/src/tests/Interop/COM/Dynamic/CollectionTest.cs b/src/tests/Interop/COM/Dynamic/CollectionTest.cs index b917bae455b6a0..2e5ad9680ead28 100644 --- a/src/tests/Interop/COM/Dynamic/CollectionTest.cs +++ b/src/tests/Interop/COM/Dynamic/CollectionTest.cs @@ -4,7 +4,9 @@ namespace Dynamic { using System; + using System.Collections; using System.Collections.Generic; + using System.Runtime.InteropServices; using Xunit; internal class CollectionTest @@ -81,6 +83,8 @@ private void CustomCollection() Assert.Equal(array[2], obj.Item(2)); Assert.Equal(array.Length, obj.Count); + CustomEnumerator(array); + Console.WriteLine(" + Enumerate collection"); List list = new List(); @@ -135,6 +139,58 @@ private void CustomCollection() Console.WriteLine(" + Clear collection"); obj.Clear(); Assert.Equal(0, obj.Count); + CustomEnumerator(System.Array.Empty()); + } + + private void CustomEnumerator(string[] expected) + { + Console.WriteLine(" + Cast fresh COM enumerators to IEnumerator"); + foreach (string firstMember in new[] { nameof(IEnumerator.MoveNext), nameof(IEnumerator.Reset), nameof(IEnumerator.Current) }) + { + Console.WriteLine($" First member: {firstMember}, items: {expected.Length}"); + object previous = null; + for (int pass = 0; pass < 3; pass++) + { + object enumVariant = obj._NewEnum; + Assert.True(Marshal.IsComObject(enumVariant)); + Assert.NotSame(previous, enumVariant); + previous = enumVariant; + IEnumerator enumerator = (IEnumerator)enumVariant; + List actual = new List(); + + switch (firstMember) + { + case nameof(IEnumerator.MoveNext): + Assert.Equal(expected.Length != 0, enumerator.MoveNext()); + if (expected.Length != 0) + { + actual.Add((string)enumerator.Current); + } + break; + case nameof(IEnumerator.Reset): + enumerator.Reset(); + break; + case nameof(IEnumerator.Current): + Assert.Null(enumerator.Current); + break; + } + + while (enumerator.MoveNext()) + { + actual.Add((string)enumerator.Current); + } + AssertExtensions.CollectionEqual(expected, actual); + Assert.False(enumerator.MoveNext()); + + actual.Clear(); + enumerator.Reset(); + while (enumerator.MoveNext()) + { + actual.Add((string)enumerator.Current); + } + AssertExtensions.CollectionEqual(expected, actual); + } + } } private void IndexChain()