From 89965351dc6ea233a26188dff616bcfc3093a6ef Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 8 Apr 2026 13:37:25 -0700 Subject: [PATCH 01/41] Move native size calculation into static constructor of structure/layoutclass marshaler to enable consistent shape for array element marshalers. --- .../InteropServices/Marshal.CoreCLR.cs | 20 +++--- .../src/System/StubHelpers.cs | 65 ++++++++++++------- src/coreclr/vm/ilmarshalers.cpp | 3 - 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs index bdbe4efcbe144c..ffeb7092bf0fa4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs @@ -240,9 +240,9 @@ internal sealed class LayoutTypeMarshalerMethods private static MemberInfo ConvertToManagedMethod => field ??= typeof(BoxedLayoutTypeMarshaler<>).GetMethod(nameof(BoxedLayoutTypeMarshaler.ConvertToManaged), BindingFlags.Public | BindingFlags.Static)!; private static MemberInfo FreeMethod => field ??= typeof(BoxedLayoutTypeMarshaler<>).GetMethod(nameof(BoxedLayoutTypeMarshaler.Free), BindingFlags.Public | BindingFlags.Static)!; - private unsafe delegate void ConvertToUnmanagedDelegate(object obj, byte* native, int nativeSize, ref CleanupWorkListElement? cleanupWorkList); + private unsafe delegate void ConvertToUnmanagedDelegate(object obj, byte* native, ref CleanupWorkListElement? cleanupWorkList); private unsafe delegate void ConvertToManagedDelegate(object obj, byte* native, ref CleanupWorkListElement? cleanupWorkList); - private unsafe delegate void FreeDelegate(object? obj, byte* native, int nativeSize, ref CleanupWorkListElement? cleanupWorkList); + private unsafe delegate void FreeDelegate(object? obj, byte* native, ref CleanupWorkListElement? cleanupWorkList); private readonly ConvertToUnmanagedDelegate _convertToUnmanaged; private readonly ConvertToManagedDelegate _convertToManaged; @@ -260,14 +260,14 @@ public unsafe void ConvertToManaged(object obj, byte* native, ref CleanupWorkLis _convertToManaged(obj, native, ref cleanupWorkList); } - public unsafe void ConvertToUnmanaged(object obj, byte* native, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public unsafe void ConvertToUnmanaged(object obj, byte* native, ref CleanupWorkListElement? cleanupWorkList) { - _convertToUnmanaged(obj, native, nativeSize, ref cleanupWorkList); + _convertToUnmanaged(obj, native, ref cleanupWorkList); } - public unsafe void Free(object? obj, byte* native, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public unsafe void Free(object? obj, byte* native, ref CleanupWorkListElement? cleanupWorkList) { - _free(obj, native, nativeSize, ref cleanupWorkList); + _free(obj, native, ref cleanupWorkList); } private static readonly ConditionalWeakTable s_marshalerCache = []; @@ -319,10 +319,10 @@ public static unsafe void StructureToPtr(object structure, IntPtr ptr, bool fDel if (fDeleteOld) { - methods.Free(structure, (byte*)ptr, size, ref Unsafe.NullRef()); + methods.Free(structure, (byte*)ptr, ref Unsafe.NullRef()); } - methods.ConvertToUnmanaged(structure, (byte*)ptr, size, ref Unsafe.NullRef()); + methods.ConvertToUnmanaged(structure, (byte*)ptr, ref Unsafe.NullRef()); } /// @@ -366,14 +366,14 @@ public static unsafe void DestroyStructure(IntPtr ptr, Type structuretype) if (rt.IsGenericType) throw new ArgumentException(SR.Argument_NeedNonGenericType, nameof(structuretype)); - if (!HasLayout(new QCallTypeHandle(ref rt), out bool isBlittable, out int size)) + if (!HasLayout(new QCallTypeHandle(ref rt), out bool isBlittable, out _)) throw new ArgumentException(SR.Argument_MustHaveLayoutOrBeBlittable, nameof(structuretype)); if (!isBlittable) { LayoutTypeMarshalerMethods methods = LayoutTypeMarshalerMethods.GetMarshalMethodsForType(structuretype); - methods.Free(null, (byte*)ptr, size, ref Unsafe.NullRef()); + methods.Free(null, (byte*)ptr, ref Unsafe.NullRef()); } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index a8fd4e2337819a..381abe069f47fb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1402,13 +1402,21 @@ internal static class MarshalOperation internal static unsafe class StructureMarshaler where T : notnull { + private static readonly int s_nativeSize; + + static StructureMarshaler() + { + Debug.Assert(typeof(T).IsValueType, "StructureMarshaler can only be used for value types"); + RuntimeTypeHandle th = typeof(T).TypeHandle; + bool hasLayout = Marshal.HasLayout(new QCallTypeHandle(ref th), out bool isBlittable, out int _nativeSize); + Debug.Assert(hasLayout, "Non-layout classes should not use the layout class marshaler."); + } + [Conditional("DEBUG")] private static void Validate() { - Debug.Assert(typeof(T).IsValueType, "StructureMarshaler can only be used for value types"); RuntimeType type = (RuntimeType)typeof(T); - bool hasLayout = Marshal.HasLayout(new QCallTypeHandle(ref type), out bool isBlittable, out int _); - Debug.Assert(hasLayout, "Non-layout structs should not be marshalable"); + _ = Marshal.HasLayout(new QCallTypeHandle(ref type), out bool isBlittable, out int _); Debug.Assert(isBlittable, "Non-blittable structs should have a custom IL body generated with the marshaling logic."); } @@ -1420,18 +1428,18 @@ private static void ConvertToUnmanagedCore(ref T managed, byte* unmanaged, ref C SpanHelpers.Memmove(ref *unmanaged, ref Unsafe.As(ref managed), (nuint)sizeof(T)); } - public static void ConvertToUnmanaged(ref T managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public static void ConvertToUnmanaged(ref T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { try { - NativeMemory.Clear(unmanaged, (nuint)nativeSize); + NativeMemory.Clear(unmanaged, (nuint)s_nativeSize); ConvertToUnmanagedCore(ref managed, unmanaged, ref cleanupWorkList); } catch (Exception) { // If Free throws an exception (which it shouldn't as it can leak) // let that exception supercede the exception from ConvertToUnmanagedCore. - Free(ref managed, unmanaged, nativeSize, ref cleanupWorkList); + Free(ref managed, unmanaged, ref cleanupWorkList); throw; } } @@ -1455,10 +1463,10 @@ private static void FreeCore(ref T managed, byte* unmanaged, ref CleanupWorkList _ = ref cleanupWorkList; } - public static void Free(ref T managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public static void Free(ref T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { FreeCore(ref managed, unmanaged, ref cleanupWorkList); - NativeMemory.Clear(unmanaged, (nuint)nativeSize); + NativeMemory.Clear(unmanaged, (nuint)s_nativeSize); } } @@ -1473,7 +1481,7 @@ private static class Methods private static readonly delegate* _convertToManaged; private static readonly delegate* _free; - private static readonly nuint s_nativeSizeForBlittableTypes; + private static readonly nuint s_nativeSize; #pragma warning disable CA1810 // Static constructor is required to initialize with the out parameters static Methods() @@ -1481,16 +1489,15 @@ static Methods() RuntimeTypeHandle th = typeof(T).TypeHandle; bool hasLayout = Marshal.HasLayout(new QCallTypeHandle(ref th), out bool isBlittable, out int nativeSize); Debug.Assert(hasLayout, "Non-layout classes should not use the layout class marshaler."); + s_nativeSize = (nuint)nativeSize; if (isBlittable) { - s_nativeSizeForBlittableTypes = (nuint)nativeSize; _convertToUnmanaged = &BlittableConvertToUnmanaged; _convertToManaged = &BlittableConvertToManaged; _free = &BlittableFree; } else { - s_nativeSizeForBlittableTypes = 0; StubHelpers.CreateLayoutClassMarshalStubs(new QCallTypeHandle(ref th), out _convertToUnmanaged, out _convertToManaged, out _free); } } @@ -1498,12 +1505,12 @@ static Methods() private static void BlittableConvertToUnmanaged(ref byte managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { - SpanHelpers.Memmove(ref *unmanaged, ref managed, s_nativeSizeForBlittableTypes); + SpanHelpers.Memmove(ref *unmanaged, ref managed, s_nativeSize); } private static void BlittableConvertToManaged(ref byte managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { - SpanHelpers.Memmove(ref managed, ref *unmanaged, s_nativeSizeForBlittableTypes); + SpanHelpers.Memmove(ref managed, ref *unmanaged, s_nativeSize); } private static void BlittableFree(ref byte managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) @@ -1516,6 +1523,8 @@ private static void BlittableFree(ref byte managed, byte* unmanaged, ref Cleanup internal static delegate* ConvertToManaged => _convertToManaged; internal static delegate* Free => _free; + + internal static nuint NativeSize => s_nativeSize; } private static void ConvertToUnmanagedCore(T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) @@ -1536,18 +1545,18 @@ static void CallConvertToUnmanaged(ref byte managed, byte* unmanaged, ref Cleanu } } - public static void ConvertToUnmanaged(T managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public static void ConvertToUnmanaged(T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { try { - NativeMemory.Clear(unmanaged, (nuint)nativeSize); + NativeMemory.Clear(unmanaged, NativeSize); ConvertToUnmanagedCore(managed, unmanaged, ref cleanupWorkList); } catch (Exception) { // If Free throws an exception (which it shouldn't as it can leak) // let that exception supercede the exception from ConvertToUnmanagedCore. - Free(managed, unmanaged, nativeSize, ref cleanupWorkList); + Free(managed, unmanaged, ref cleanupWorkList); throw; } } @@ -1595,25 +1604,31 @@ static void CallFree(T? managed, byte* unmanaged, ref CleanupWorkListElement? cl } } - public static void Free(T? managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public static void Free(T? managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { FreeCore(managed, unmanaged, ref cleanupWorkList); - NativeMemory.Clear(unmanaged, (nuint)nativeSize); + NativeMemory.Clear(unmanaged, NativeSize); + } + + private static nuint NativeSize + { + [MethodImpl(MethodImplOptions.NoInlining)] + get => Methods.NativeSize; } } // Marshaller for layout classes and boxed structs. internal static unsafe class BoxedLayoutTypeMarshaler where T : notnull { - public static void ConvertToUnmanaged(object managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public static void ConvertToUnmanaged(object managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { if (typeof(T).IsValueType) { - StructureMarshaler.ConvertToUnmanaged(ref Unsafe.As(ref managed.GetRawData()), unmanaged, nativeSize, ref cleanupWorkList); + StructureMarshaler.ConvertToUnmanaged(ref Unsafe.As(ref managed.GetRawData()), unmanaged, ref cleanupWorkList); } else { - LayoutClassMarshaler.ConvertToUnmanaged(Unsafe.As(ref managed), unmanaged, nativeSize, ref cleanupWorkList); + LayoutClassMarshaler.ConvertToUnmanaged(Unsafe.As(ref managed), unmanaged, ref cleanupWorkList); } } @@ -1629,7 +1644,7 @@ public static void ConvertToManaged(object managed, byte* unmanaged, ref Cleanup } } - public static void Free(object? managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList) + public static void Free(object? managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList) { if (typeof(T).IsValueType) { @@ -1640,11 +1655,11 @@ public static void Free(object? managed, byte* unmanaged, int nativeSize, ref Cl managedRef = ref managed.GetRawData(); } - StructureMarshaler.Free(ref Unsafe.As(ref managedRef), unmanaged, nativeSize, ref cleanupWorkList); + StructureMarshaler.Free(ref Unsafe.As(ref managedRef), unmanaged, ref cleanupWorkList); } else { - LayoutClassMarshaler.Free(Unsafe.As(ref managed), unmanaged, nativeSize, ref cleanupWorkList); + LayoutClassMarshaler.Free(Unsafe.As(ref managed), unmanaged, ref cleanupWorkList); } } } @@ -1958,7 +1973,7 @@ internal static unsafe void LayoutTypeConvertToUnmanaged(object obj, byte* pNati Marshal.LayoutTypeMarshalerMethods methods = Marshal.LayoutTypeMarshalerMethods.GetMarshalMethodsForType(type); - methods.ConvertToUnmanaged(obj, pNative, size, ref pCleanupWorkList); + methods.ConvertToUnmanaged(obj, pNative, ref pCleanupWorkList); } [UnmanagedCallersOnly] diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 3676e78abd041d..6a3d0445cdcada 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -1127,7 +1127,6 @@ void ILValueClassMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEm EmitLoadManagedHomeAddr(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); - pslILEmit->EmitLDC(m_pargs->m_pMT->GetNativeLayoutInfo()->GetSize()); EmitLoadCleanupWorkList(pslILEmit); pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__STRUCTURE_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 4, 0); @@ -2533,7 +2532,6 @@ void ILLayoutClassMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILE EmitLoadManagedValue(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); - pslILEmit->EmitLDC(m_pargs->m_pMT->GetNativeLayoutInfo()->GetSize()); EmitLoadCleanupWorkList(pslILEmit); pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 4, 0); @@ -2566,7 +2564,6 @@ void ILLayoutClassMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) EmitLoadManagedValue(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); - pslILEmit->EmitLDC(m_pargs->m_pMT->GetNativeLayoutInfo()->GetSize()); EmitLoadCleanupWorkList(pslILEmit); pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__FREE, m_pargs->m_pMT)), 4, 0); From 4f4f322f737d4e8cbbe02ecd33473b7ed4834aa3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 8 Apr 2026 17:03:22 -0700 Subject: [PATCH 02/41] Move native array marshaling to managed IArrayElementMarshaler implementations Replaces the native olevariant.cpp-based array element marshaling with managed generic IArrayElementMarshaler implementations called from ILNativeArrayMarshaler via instantiated StubHelpers methods. New managed marshaler types in StubHelpers.cs: - DateMarshaler (implements IArrayElementMarshaler) - AnsiCharArrayElementMarshaler - LPSTRArrayElementMarshaler - CurrencyArrayElementMarshaler (COM only) - BSTRArrayElementMarshaler (COM only) - InterfaceArrayElementMarshaler (COM only) - TypedInterfaceArrayElementMarshaler (COM only) - HeterogeneousInterfaceArrayElementMarshaler (COM only) - VariantArrayElementMarshaler (COM only) ILNativeArrayMarshaler now inherits from ILMarshaler directly and emits calls to generic managed helpers (ConvertArrayContentsToUnmanaged, ConvertArrayContentsToManaged, FreeArrayContents, ConvertArraySpaceToNative, ConvertArraySpaceToManaged, ClearArrayNative) instead of using QCalls through MngdNativeArrayMarshaler. Uses StructureMarshaler for blittable primitive types and non-blittable record types. Boolean flags are encoded as IMarshalerOption generic type parameters. --- .../src/System/StubHelpers.cs | 474 +++++++++++++++++- src/coreclr/vm/corelib.h | 22 + src/coreclr/vm/ilmarshalers.cpp | 287 ++++++++--- src/coreclr/vm/ilmarshalers.h | 44 +- 4 files changed, 751 insertions(+), 76 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 381abe069f47fb..16522e252eac8c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; @@ -591,7 +592,7 @@ internal static void ThrowCriticalHandleFieldChanged() } } - internal static class DateMarshaler + internal static class DateMarshaler : IArrayElementMarshaler { internal static double ConvertToNative(DateTime managedDate) { @@ -602,6 +603,22 @@ internal static long ConvertToManaged(double nativeDate) { return DateTime.DoubleDateToTicks(nativeDate); } + + static unsafe void IArrayElementMarshaler.ConvertToUnmanaged(ref DateTime managed, byte* unmanaged) + { + Unsafe.WriteUnaligned(unmanaged, ConvertToNative(managed)); + } + + static unsafe void IArrayElementMarshaler.ConvertToManaged(ref DateTime managed, byte* unmanaged) + { + managed = new DateTime(ConvertToManaged(Unsafe.ReadUnaligned(unmanaged))); + } + + static unsafe void IArrayElementMarshaler.Free(byte* unmanaged) + { + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(double); } // class DateMarshaler #if FEATURE_COMINTEROP @@ -1392,6 +1409,15 @@ internal void ClearNative(IntPtr pNativeHome) } } // struct AsAnyMarshaler + internal interface IArrayElementMarshaler + { + static abstract unsafe void ConvertToUnmanaged(ref T managed, byte* unmanaged); + static abstract unsafe void ConvertToManaged(ref T managed, byte* unmanaged); + static abstract unsafe void Free(byte* unmanaged); + + static abstract nuint UnmanagedSize { get; } + } + // Constants for direction argument of struct marshalling stub. internal static class MarshalOperation { @@ -1400,15 +1426,32 @@ internal static class MarshalOperation internal const int Free = 2; } - internal static unsafe class StructureMarshaler where T : notnull + internal sealed unsafe class StructureMarshaler : IArrayElementMarshaler where T : notnull { + static unsafe void IArrayElementMarshaler.ConvertToManaged(ref T managed, byte* unmanaged) + { + ConvertToManaged(ref managed, unmanaged, ref Unsafe.NullRef()); + } + + static unsafe void IArrayElementMarshaler.ConvertToUnmanaged(ref T managed, byte* unmanaged) + { + ConvertToUnmanaged(ref managed, unmanaged, ref Unsafe.NullRef()); + } + + static unsafe void IArrayElementMarshaler.Free(byte* unmanaged) + { + Free(ref Unsafe.NullRef(), unmanaged, ref Unsafe.NullRef()); + } + + static nuint IArrayElementMarshaler.UnmanagedSize => (nuint)s_nativeSize; + private static readonly int s_nativeSize; static StructureMarshaler() { Debug.Assert(typeof(T).IsValueType, "StructureMarshaler can only be used for value types"); RuntimeTypeHandle th = typeof(T).TypeHandle; - bool hasLayout = Marshal.HasLayout(new QCallTypeHandle(ref th), out bool isBlittable, out int _nativeSize); + bool hasLayout = Marshal.HasLayout(new QCallTypeHandle(ref th), out bool _, out s_nativeSize); Debug.Assert(hasLayout, "Non-layout classes should not use the layout class marshaler."); } @@ -1470,7 +1513,7 @@ public static void Free(ref T managed, byte* unmanaged, ref CleanupWorkListEleme } } - internal static unsafe class LayoutClassMarshaler where T : notnull + internal sealed unsafe class LayoutClassMarshaler : IArrayElementMarshaler where T : notnull { // We use a nested Methods class with properties that unwrap the TypeInitializationException // to ensure that users see a TypeLoadException if the type has a recursive native layout. @@ -1615,6 +1658,23 @@ private static nuint NativeSize [MethodImpl(MethodImplOptions.NoInlining)] get => Methods.NativeSize; } + + static unsafe void IArrayElementMarshaler.ConvertToManaged(ref T managed, byte* unmanaged) + { + ConvertToManaged(managed, unmanaged, ref Unsafe.NullRef()); + } + + static unsafe void IArrayElementMarshaler.ConvertToUnmanaged(ref T managed, byte* unmanaged) + { + ConvertToUnmanaged(managed, unmanaged, ref Unsafe.NullRef()); + } + + static unsafe void IArrayElementMarshaler.Free(byte* unmanaged) + { + Free(default, unmanaged, ref Unsafe.NullRef()); + } + + static nuint IArrayElementMarshaler.UnmanagedSize => NativeSize; } // Marshaller for layout classes and boxed structs. @@ -1664,6 +1724,349 @@ public static void Free(object? managed, byte* unmanaged, ref CleanupWorkListEle } } + internal sealed class VariantBoolMarshaler : IArrayElementMarshaler + { + private const ushort VARIANT_TRUE = unchecked((ushort)-1); + private const ushort VARIANT_FALSE = 0; + public static unsafe void ConvertToUnmanaged(ref bool managed, byte* unmanaged) + { + *(ushort*)unmanaged = managed ? VARIANT_TRUE : VARIANT_FALSE; + } + + public static unsafe void ConvertToManaged(ref bool managed, byte* unmanaged) + { + managed = (*(ushort*)unmanaged) != VARIANT_FALSE; + } + + public static unsafe void Free(byte* unmanaged) + { + _ = unmanaged; + // Nothing to free for VARIANT_BOOL. + } + + static nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(short); + } + + internal sealed class BoolMarshaler : IArrayElementMarshaler where TUnmanaged : unmanaged, INumberBase + { + public static unsafe void ConvertToUnmanaged(ref bool managed, byte* unmanaged) + { + TUnmanaged value = managed ? TUnmanaged.One : TUnmanaged.Zero; + Unsafe.WriteUnaligned(unmanaged, value); + } + + public static unsafe void ConvertToManaged(ref bool managed, byte* unmanaged) + { + TUnmanaged value = Unsafe.ReadUnaligned(unmanaged); + managed = !value.Equals(TUnmanaged.Zero); + } + + public static unsafe void Free(byte* unmanaged) + { + _ = unmanaged; + // Nothing to free for boolean values. + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(TUnmanaged); + } + + internal sealed class LPWSTRMarshaler : IArrayElementMarshaler + { + public static unsafe void ConvertToUnmanaged(ref string? managed, byte* unmanaged) + { + IntPtr native = IntPtr.Zero; + + if (managed is not null) + { + int allocSize = (managed.Length + 1) * sizeof(char); + native = Marshal.AllocCoTaskMem(allocSize); + string.InternalCopy(managed, native, allocSize); + } + + *(IntPtr*)unmanaged = native; + } + + public static unsafe void ConvertToManaged(ref string? managed, byte* unmanaged) + { + IntPtr native = *(IntPtr*)unmanaged; + if (native == IntPtr.Zero) + { + managed = null; + } + else + { + StubHelpers.CheckStringLength((uint)string.wcslen((char*)native)); + managed = new string((char*)native); + } + } + + public static unsafe void Free(byte* unmanaged) + { + IntPtr pNativeHome = *(IntPtr*)unmanaged; + if (pNativeHome != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(pNativeHome); + } + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + } + + internal sealed class AnsiCharArrayElementMarshaler : IArrayElementMarshaler + where TBestFit : IMarshalerOption + where TThrowOnUnmappable : IMarshalerOption + { + public static unsafe void ConvertToUnmanaged(ref char managed, byte* unmanaged) + { + *unmanaged = AnsiCharMarshaler.ConvertToNative(managed, TBestFit.Enabled, TThrowOnUnmappable.Enabled); + } + + public static unsafe void ConvertToManaged(ref char managed, byte* unmanaged) + { + managed = AnsiCharMarshaler.ConvertToManaged(*unmanaged); + } + + public static unsafe void Free(byte* unmanaged) + { + } + + static nuint IArrayElementMarshaler.UnmanagedSize => (nuint)Marshal.SystemMaxDBCSCharSize; + } + + internal sealed class LPSTRArrayElementMarshaler : IArrayElementMarshaler + where TBestFit : IMarshalerOption + where TThrowOnUnmappable : IMarshalerOption + { + public static unsafe void ConvertToUnmanaged(ref string? managed, byte* unmanaged) + { + int flags = (TBestFit.Enabled ? 0xFF : 0) | (TThrowOnUnmappable.Enabled ? 0xFF00 : 0); + *(IntPtr*)unmanaged = CSTRMarshaler.ConvertToNative(flags, managed, IntPtr.Zero); + } + + public static unsafe void ConvertToManaged(ref string? managed, byte* unmanaged) + { + managed = CSTRMarshaler.ConvertToManaged(*(IntPtr*)unmanaged); + } + + public static unsafe void Free(byte* unmanaged) + { + IntPtr ptr = *(IntPtr*)unmanaged; + if (ptr != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(ptr); + } + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + } + + internal sealed class CurrencyArrayElementMarshaler : IArrayElementMarshaler + { + public static unsafe void ConvertToUnmanaged(ref decimal managed, byte* unmanaged) + { + *(Currency*)unmanaged = new Currency(managed); + } + + public static unsafe void ConvertToManaged(ref decimal managed, byte* unmanaged) + { + managed = new decimal(*(Currency*)unmanaged); + } + + public static unsafe void Free(byte* unmanaged) + { + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(Currency); + } + + internal sealed class BSTRArrayElementMarshaler : IArrayElementMarshaler + { + public static unsafe void ConvertToUnmanaged(ref string? managed, byte* unmanaged) + { + *(IntPtr*)unmanaged = BSTRMarshaler.ConvertToNative(managed, IntPtr.Zero); + } + + public static unsafe void ConvertToManaged(ref string? managed, byte* unmanaged) + { + managed = BSTRMarshaler.ConvertToManaged(*(IntPtr*)unmanaged); + } + + public static unsafe void Free(byte* unmanaged) + { + IntPtr bstr = *(IntPtr*)unmanaged; + if (bstr != IntPtr.Zero) + { + BSTRMarshaler.ClearNative(bstr); + } + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + } + + [SupportedOSPlatform("windows")] + internal sealed class InterfaceArrayElementMarshaler : IArrayElementMarshaler + where TIsDispatch : IMarshalerOption + { + public static unsafe void ConvertToUnmanaged(ref object? managed, byte* unmanaged) + { + if (managed is null) + { + *(IntPtr*)unmanaged = IntPtr.Zero; + } + else if (TIsDispatch.Enabled) + { + *(IntPtr*)unmanaged = Marshal.GetIDispatchForObject(managed); + } + else + { + *(IntPtr*)unmanaged = Marshal.GetIUnknownForObject(managed); + } + } + + public static unsafe void ConvertToManaged(ref object? managed, byte* unmanaged) + { + IntPtr pUnk = *(IntPtr*)unmanaged; + if (pUnk == IntPtr.Zero) + { + managed = null; + } + else + { + managed = Marshal.GetObjectForIUnknown(pUnk); + } + } + + public static unsafe void Free(byte* unmanaged) + { + IntPtr pUnk = *(IntPtr*)unmanaged; + if (pUnk != IntPtr.Zero) + { + Marshal.Release(pUnk); + } + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + } + + [SupportedOSPlatform("windows")] + internal sealed class TypedInterfaceArrayElementMarshaler : IArrayElementMarshaler + where T : class + { + public static unsafe void ConvertToUnmanaged(ref T? managed, byte* unmanaged) + { + if (managed is null) + { + *(IntPtr*)unmanaged = IntPtr.Zero; + } + else + { + *(IntPtr*)unmanaged = Marshal.GetComInterfaceForObject(managed, typeof(T)); + } + } + + public static unsafe void ConvertToManaged(ref T? managed, byte* unmanaged) + { + IntPtr pUnk = *(IntPtr*)unmanaged; + if (pUnk == IntPtr.Zero) + { + managed = null; + } + else + { + managed = (T)Marshal.GetObjectForIUnknown(pUnk); + } + } + + public static unsafe void Free(byte* unmanaged) + { + IntPtr pUnk = *(IntPtr*)unmanaged; + if (pUnk != IntPtr.Zero) + { + Marshal.Release(pUnk); + } + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + } + + [SupportedOSPlatform("windows")] + internal sealed class HeterogeneousInterfaceArrayElementMarshaler : IArrayElementMarshaler + { + public static unsafe void ConvertToUnmanaged(ref object? managed, byte* unmanaged) + { + if (managed is null) + { + *(IntPtr*)unmanaged = IntPtr.Zero; + } + else + { + // Resolve the default COM interface for each element based on its runtime type. + // This matches the heterogeneous path in MarshalInterfaceArrayComToOleHelper + // where GetDefaultInterfaceMTForClass is called per-element. + *(IntPtr*)unmanaged = Marshal.GetComInterfaceForObject(managed, managed.GetType()); + } + } + + public static unsafe void ConvertToManaged(ref object? managed, byte* unmanaged) + { + IntPtr pUnk = *(IntPtr*)unmanaged; + if (pUnk == IntPtr.Zero) + { + managed = null; + } + else + { + managed = Marshal.GetObjectForIUnknown(pUnk); + } + } + + public static unsafe void Free(byte* unmanaged) + { + IntPtr pUnk = *(IntPtr*)unmanaged; + if (pUnk != IntPtr.Zero) + { + Marshal.Release(pUnk); + } + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + } + + internal sealed class VariantArrayElementMarshaler : IArrayElementMarshaler + { + public static unsafe void ConvertToUnmanaged(ref object? managed, byte* unmanaged) + { + ObjectMarshaler.ConvertToNative(managed!, (IntPtr)unmanaged); + } + + public static unsafe void ConvertToManaged(ref object? managed, byte* unmanaged) + { + managed = ObjectMarshaler.ConvertToManaged((IntPtr)unmanaged); + } + + public static unsafe void Free(byte* unmanaged) + { + ObjectMarshaler.ClearNative((IntPtr)unmanaged); + } + + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(ComVariant); + } + + internal interface IMarshalerOption + { + static abstract bool Enabled { get; } + + public sealed class EnabledOption : IMarshalerOption + { + public static bool Enabled => true; + } + + public sealed class DisabledOption : IMarshalerOption + { + public static bool Enabled => false; + } + } + internal abstract class CleanupWorkListElement { private CleanupWorkListElement? m_Next; @@ -2020,6 +2423,69 @@ internal static unsafe void LayoutTypeConvertToManaged(object* obj, byte* pNativ } } + internal static unsafe void ConvertArrayContentsToUnmanaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + { + for (int i = 0; i < managed.Length; i++) + { + TMarshaler.ConvertToUnmanaged(ref managed[i], pNative); + pNative += TMarshaler.UnmanagedSize; + } + } + + internal static unsafe void ConvertArrayContentsToManaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + { + for (int i = 0; i < managed.Length; i++) + { + TMarshaler.ConvertToManaged(ref managed[i], pNative); + pNative += TMarshaler.UnmanagedSize; + } + } + + internal static unsafe void FreeArrayContents(byte* pNative, int length) where TMarshaler : IArrayElementMarshaler + { + for (int i = 0; i < length; i++) + { + TMarshaler.Free(pNative); + pNative += TMarshaler.UnmanagedSize; + } + } + + internal static unsafe byte* ConvertArraySpaceToNative(T[]? managed) + where TMarshaler : IArrayElementMarshaler + { + if (managed is null) + { + return null; + } + else + { + return (byte*)Marshal.AllocCoTaskMem(checked(managed.Length * (int)TMarshaler.UnmanagedSize)); + } + } + + internal static unsafe T[]? ConvertArraySpaceToManaged(byte* pNativeHome, int cElements) + where TMarshaler : IArrayElementMarshaler + { + if (pNativeHome == null) + { + return null; + } + else + { + return new T[cElements]; + } + } + + internal static unsafe void ClearArrayNative(byte* pNativeHome, int cElements) + where TMarshaler : IArrayElementMarshaler + { + if (pNativeHome != null) + { + FreeArrayContents(pNativeHome, cElements); + Marshal.FreeCoTaskMem((IntPtr)pNativeHome); + } + } + private static readonly MemberInfo StructureMarshalerConvertToUnmanaged = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.ConvertToUnmanaged))!; private static readonly MemberInfo StructureMarshalerConvertToManaged = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.ConvertToManaged))!; private static readonly MemberInfo StructureMarshalerFree = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.Free))!; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index dcab7536023135..16cca4603c744d 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1076,6 +1076,13 @@ DEFINE_METHOD(STUBHELPERS, NONBLITTABLE_STRUCTURE_ARRAY_CONVERT_TO_UNMA DEFINE_METHOD(STUBHELPERS, NONBLITTABLE_STRUCTURE_ARRAY_CONVERT_TO_MANAGED, NonBlittableStructureArrayConvertToManaged, NoSig) DEFINE_METHOD(STUBHELPERS, NONBLITTABLE_STRUCTURE_ARRAY_FREE, NonBlittableStructureArrayFree, NoSig) +DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ConvertArrayContentsToUnmanaged, NoSig) +DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_MANAGED, ConvertArrayContentsToManaged, NoSig) +DEFINE_METHOD(STUBHELPERS, FREE_ARRAY_CONTENTS, FreeArrayContents, NoSig) +DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_NATIVE, ConvertArraySpaceToNative, NoSig) +DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_MANAGED, ConvertArraySpaceToManaged, NoSig) +DEFINE_METHOD(STUBHELPERS, CLEAR_ARRAY_NATIVE, ClearArrayNative, NoSig) + #ifdef FEATURE_COMINTEROP DEFINE_CLASS(IDISPATCHHELPERS, Interop, IDispatchHelpers) DEFINE_METHOD(IDISPATCHHELPERS, GET_DISPATCH_EX_PROPERTY_FLAGS, GetDispatchExPropertyFlags, SM_PtrPropertyInfo_PtrException_RetInt) @@ -1250,6 +1257,21 @@ DEFINE_METHOD(BOXEDLAYOUTTYPE_MARSHALER, FREE, Free, DEFINE_CLASS(COMVARIANT, Marshalling, ComVariant) +DEFINE_CLASS(VARIANT_BOOL_MARSHALER, StubHelpers, VariantBoolMarshaler) +DEFINE_CLASS(BOOL_MARSHALER, StubHelpers, BoolMarshaler`1) +DEFINE_CLASS(LPWSTR_MARSHALER, StubHelpers, LPWSTRMarshaler) +DEFINE_CLASS(ANSICHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, AnsiCharArrayElementMarshaler`2) +DEFINE_CLASS(LPSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, LPSTRArrayElementMarshaler`2) +#ifdef FEATURE_COMINTEROP +DEFINE_CLASS(CURRENCY_ARRAY_ELEMENT_MARSHALER, StubHelpers, CurrencyArrayElementMarshaler) +DEFINE_CLASS(BSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, BSTRArrayElementMarshaler) +DEFINE_CLASS(INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, InterfaceArrayElementMarshaler`1) +DEFINE_CLASS(TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, TypedInterfaceArrayElementMarshaler`1) +DEFINE_CLASS(VARIANT_ARRAY_ELEMENT_MARSHALER, StubHelpers, VariantArrayElementMarshaler) +#endif // FEATURE_COMINTEROP +DEFINE_CLASS(MARSHALER_OPTION_ENABLED, StubHelpers, IMarshalerOption+EnabledOption) +DEFINE_CLASS(MARSHALER_OPTION_DISABLED, StubHelpers, IMarshalerOption+DisabledOption) + DEFINE_CLASS(SZARRAYHELPER, System, SZArrayHelper) // Note: The order of methods here has to match order they are implemented on the interfaces in // IEnumerable`1 diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 6a3d0445cdcada..46662110510d88 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -3849,42 +3849,6 @@ void ILMngdMarshaler::EmitCallMngdMarshalerMethod(ILCodeStream* pslILEmit, Metho } } -void ILNativeArrayMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - m_dwMngdMarshalerLocalNum = pslILEmit->NewLocal(ELEMENT_TYPE_I); - - pslILEmit->EmitLDC(sizeof(MngdNativeArrayMarshaler)); - pslILEmit->EmitLOCALLOC(); - pslILEmit->EmitSTLOC(m_dwMngdMarshalerLocalNum); - - CREATE_MARSHALER_CARRAY_OPERANDS mops; - m_pargs->m_pMarshalInfo->GetMops(&mops); - - pslILEmit->EmitLDLOC(m_dwMngdMarshalerLocalNum); - - pslILEmit->EmitLDTOKEN(pslILEmit->GetToken(mops.methodTable)); - pslILEmit->EmitCALL(METHOD__RT_TYPE_HANDLE__TO_INTPTR, 1, 1); - - DWORD dwFlags = mops.elementType; - dwFlags |= (((DWORD)mops.bestfitmapping) << 16); - dwFlags |= (((DWORD)mops.throwonunmappablechar) << 24); - - pslILEmit->EmitLDC(dwFlags); - - if (!IsCLRToNative(m_dwMarshalFlags) && IsOut(m_dwMarshalFlags) && IsIn(m_dwMarshalFlags)) - { - pslILEmit->EmitLDC(1); // true - } - else - { - pslILEmit->EmitLDC(0); // false - } - - pslILEmit->EmitCALL(METHOD__MNGD_NATIVE_ARRAY_MARSHALER__CREATE_MARSHALER, 4, 0); -} - bool ILNativeArrayMarshaler::CanMarshalViaPinning() { // We can't pin an array if we have a marshaler for the var type @@ -4147,36 +4111,31 @@ void ILNativeArrayMarshaler::EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit { STANDARD_VM_CONTRACT; - EmitLoadMngdMarshaler(pslILEmit); - EmitLoadManagedHomeAddr(pslILEmit); - EmitLoadNativeHomeAddr(pslILEmit); - if (IsByref(m_dwMarshalFlags)) { - // - // Reset the element count just in case there is an exception thrown in the code emitted by - // EmitLoadElementCount. The best thing we can do here is to avoid a crash. - // + // Reset the element count in case EmitLoadElementCount throws. _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); pslILEmit->EmitLDC(0); pslILEmit->EmitSTLOC(m_dwSavedSizeArg); } + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_MANAGED); + + EmitLoadNativeHomeAddr(pslILEmit); + // Dynamically calculate element count using SizeParamIndex argument EmitLoadElementCount(pslILEmit); if (IsByref(m_dwMarshalFlags)) { - // - // Save the native array size before converting it to managed and load it again - // + // Save the native array size and reload it _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); pslILEmit->EmitSTLOC(m_dwSavedSizeArg); pslILEmit->EmitLDLOC(m_dwSavedSizeArg); } - // MngdNativeArrayMarshaler::ConvertSpaceToManaged - pslILEmit->EmitCALL(pslILEmit->GetToken(GetConvertSpaceToManagedMethod()), 4, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 1); + EmitStoreManagedValue(pslILEmit); } void ILNativeArrayMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) @@ -4187,31 +4146,34 @@ void ILNativeArrayMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit { _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); - // // Save the array size before converting it to native - // EmitLoadManagedValue(pslILEmit); ILCodeLabel *pManagedHomeIsNull = pslILEmit->NewCodeLabel(); pslILEmit->EmitBRFALSE(pManagedHomeIsNull); EmitLoadManagedValue(pslILEmit); pslILEmit->EmitLDLEN(); + pslILEmit->EmitCONV_OVF_I4(); pslILEmit->EmitSTLOC(m_dwSavedSizeArg); + pslILEmit->EmitLabel(pManagedHomeIsNull); } + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_NATIVE); - ILMngdMarshaler::EmitConvertSpaceCLRToNative(pslILEmit); + EmitLoadManagedHomeAddr(pslILEmit); + EmitLoadNativeHomeAddr(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); } void ILNativeArrayMarshaler::EmitClearNative(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; - EmitLoadMngdMarshaler(pslILEmit); + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CLEAR_ARRAY_NATIVE); + EmitLoadNativeHomeAddr(pslILEmit); EmitLoadNativeSize(pslILEmit); - - pslILEmit->EmitCALL(pslILEmit->GetToken(GetClearNativeMethod()), 3, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); } void ILNativeArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) @@ -4241,11 +4203,11 @@ void ILNativeArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; - EmitLoadMngdMarshaler(pslILEmit); + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__FREE_ARRAY_CONTENTS); + EmitLoadNativeHomeAddr(pslILEmit); EmitLoadNativeSize(pslILEmit); - - pslILEmit->EmitCALL(pslILEmit->GetToken(GetClearNativeContentsMethod()), 3, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); } void ILNativeArrayMarshaler::EmitSetupArgumentForMarshalling(ILCodeStream* pslILEmit) @@ -4426,6 +4388,215 @@ extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ClearNativeContents(MngdNativ END_QCALL; } +MethodTable* ILNativeArrayMarshaler::GetMarshalerMT() +{ + STANDARD_VM_CONTRACT; + + CREATE_MARSHALER_CARRAY_OPERANDS mops; + m_pargs->m_pMarshalInfo->GetMops(&mops); + VARTYPE vt = mops.elementType; + bool bestFit = mops.bestfitmapping != 0; + bool throwOnUnmappable = mops.throwonunmappablechar != 0; + + MethodTable* pEnabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_ENABLED); + MethodTable* pDisabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); + MethodTable* pBestFitMT = bestFit ? pEnabledMT : pDisabledMT; + MethodTable* pThrowOnUnmappableMT = throwOnUnmappable ? pEnabledMT : pDisabledMT; + + switch (vt) + { + case VT_I1: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__SBYTE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_UI1: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__BYTE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_I2: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__INT16); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_UI2: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__UINT16); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_I4: + case VT_INT: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__INT32); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_UI4: + case VT_UINT: + case VT_ERROR: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__UINT32); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_I8: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__INT64); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_UI8: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__UINT64); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_R4: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__SINGLE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_R8: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__DOUBLE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_DECIMAL: + { + TypeHandle thElement = CoreLibBinder::GetClass(CLASS__DECIMAL); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + case VT_BOOL: + return CoreLibBinder::GetClass(CLASS__VARIANT_BOOL_MARSHALER); + + case VTHACK_WINBOOL: + { + TypeHandle thInt32 = CoreLibBinder::GetClass(CLASS__INT32); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thInt32, 1)).AsMethodTable(); + } + + case VTHACK_CBOOL: + { + TypeHandle thByte = CoreLibBinder::GetClass(CLASS__BYTE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thByte, 1)).AsMethodTable(); + } + + case VT_DATE: + return CoreLibBinder::GetClass(CLASS__DATEMARSHALER); + + case VTHACK_ANSICHAR: + { + TypeHandle thArgs[2] = { TypeHandle(pBestFitMT), TypeHandle(pThrowOnUnmappableMT) }; + return TypeHandle(CoreLibBinder::GetClass(CLASS__ANSICHAR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + } + + case VT_LPWSTR: + return CoreLibBinder::GetClass(CLASS__LPWSTR_MARSHALER); + + case VT_LPSTR: + { + TypeHandle thArgs[2] = { TypeHandle(pBestFitMT), TypeHandle(pThrowOnUnmappableMT) }; + return TypeHandle(CoreLibBinder::GetClass(CLASS__LPSTR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + } + +#ifdef FEATURE_COMINTEROP + case VT_CY: + return CoreLibBinder::GetClass(CLASS__CURRENCY_ARRAY_ELEMENT_MARSHALER); + + case VT_BSTR: + return CoreLibBinder::GetClass(CLASS__BSTR_ARRAY_ELEMENT_MARSHALER); + + case VT_UNKNOWN: + case VT_DISPATCH: + { + TypeHandle arrayElementTypeHandle = m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle(); + if (arrayElementTypeHandle == TypeHandle(g_pObjectClass)) + { + TypeHandle thDispatch(vt == VT_DISPATCH ? pEnabledMT : pDisabledMT); + return TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); + } + else + { + return TypeHandle(CoreLibBinder::GetClass(CLASS__TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&arrayElementTypeHandle, 1)).AsMethodTable(); + } + } + + case VT_VARIANT: + return CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); +#endif // FEATURE_COMINTEROP + + case VTHACK_NONBLITTABLERECORD: + case VTHACK_BLITTABLERECORD: + case VT_RECORD: + { + TypeHandle thElement(mops.methodTable); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + default: + _ASSERTE(!"Unsupported VT for ILNativeArrayMarshaler"); + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + return NULL; + } +} + +MethodDesc* ILNativeArrayMarshaler::GetInstantiatedArrayMethod(BinderMethodID methodId) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pGenericMD = CoreLibBinder::GetMethod(methodId); + + CREATE_MARSHALER_CARRAY_OPERANDS mops; + m_pargs->m_pMarshalInfo->GetMops(&mops); + + // Determine the managed element type T from the array's element MethodTable. + TypeHandle thElementType(mops.methodTable); + + // Determine the marshaler type TMarshaler. + TypeHandle thMarshalerType(GetMarshalerMT()); + + TypeHandle thArgs[2] = { thElementType, thMarshalerType }; + + MethodDesc* pInstMD = MethodDesc::FindOrCreateAssociatedMethodDesc( + pGenericMD, + pGenericMD->GetMethodTable(), + FALSE, + Instantiation(thArgs, 2), + FALSE); + + return pInstMD; +} + +void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); + + EmitLoadManagedValue(pslILEmit); + EmitLoadNativeHomeAddr(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + +void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); + + EmitLoadManagedValue(pslILEmit); + EmitLoadNativeHomeAddr(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + void ILFixedArrayMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index e4f5d3cb32c56c..b51a8a871f2b8b 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3268,24 +3268,16 @@ class ILMngdMarshaler : public ILMarshaler const BinderMethodID m_idClearManaged; }; -class ILNativeArrayMarshaler : public ILMngdMarshaler +class ILNativeArrayMarshaler : public ILMarshaler { public: enum { c_fInOnly = FALSE, + c_nativeSize = TARGET_POINTER_SIZE, }; - ILNativeArrayMarshaler() : - ILMngdMarshaler( - METHOD__MNGD_NATIVE_ARRAY_MARSHALER__CONVERT_SPACE_TO_MANAGED, - METHOD__MNGD_NATIVE_ARRAY_MARSHALER__CONVERT_CONTENTS_TO_MANAGED, - METHOD__MNGD_NATIVE_ARRAY_MARSHALER__CONVERT_SPACE_TO_NATIVE, - METHOD__MNGD_NATIVE_ARRAY_MARSHALER__CONVERT_CONTENTS_TO_NATIVE, - METHOD__MNGD_NATIVE_ARRAY_MARSHALER__CLEAR_NATIVE, - METHOD__MNGD_NATIVE_ARRAY_MARSHALER__CLEAR_NATIVE_CONTENTS, - METHOD__NIL - ) + ILNativeArrayMarshaler() { LIMITED_METHOD_CONTRACT; m_dwSavedSizeArg = LOCAL_NUM_UNUSED; @@ -3295,30 +3287,54 @@ class ILNativeArrayMarshaler : public ILMngdMarshaler void EmitMarshalViaPinning(ILCodeStream* pslILEmit) override; void EmitSetupArgumentForMarshalling(ILCodeStream* pslILEmit) override; + void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; + void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; void EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) override; void EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) override; void EmitClearNative(ILCodeStream* pslILEmit) override; void EmitClearNativeContents(ILCodeStream* pslILEmit) override; + bool NeedsClearNative() override + { + LIMITED_METHOD_CONTRACT; + return true; + } + bool SupportsFieldMarshal(UINT* pErrorResID) override { LIMITED_METHOD_CONTRACT; return false; } + protected: + LocalDesc GetNativeType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_I); + } + + LocalDesc GetManagedType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_OBJECT); + } BOOL CheckSizeParamIndexArg(const CREATE_MARSHALER_CARRAY_OPERANDS &mops, CorElementType *pElementType); // Calculate element count and load it on evaluation stack void EmitLoadElementCount(ILCodeStream* pslILEmit); - void EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) override; - void EmitLoadNativeSize(ILCodeStream* pslILEmit); void EmitNewSavedSizeArgLocal(ILCodeStream* pslILEmit); -private : DWORD m_dwSavedSizeArg; + +private: + // Resolve the managed marshaler MethodTable for the given VT/element type. + MethodTable* GetMarshalerMT(); + + // Instantiate one of the generic StubHelpers array methods with the element type and marshaler type. + MethodDesc* GetInstantiatedArrayMethod(BinderMethodID methodId); }; struct MngdNativeArrayMarshaler From 97a98d096906d9ecfbdfb7c422b5a868e41f734c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 09:51:59 -0700 Subject: [PATCH 03/41] Migrate fixed array marshalling to use the new managed array marshalling logic --- .../src/System/StubHelpers.cs | 85 +------- src/coreclr/vm/corelib.h | 9 +- src/coreclr/vm/ilmarshalers.cpp | 195 ++++-------------- src/coreclr/vm/ilmarshalers.h | 106 +++++----- src/coreclr/vm/qcallentrypoints.cpp | 4 - 5 files changed, 91 insertions(+), 308 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 16522e252eac8c..39498a268d7311 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -592,7 +592,7 @@ internal static void ThrowCriticalHandleFieldChanged() } } - internal static class DateMarshaler : IArrayElementMarshaler + internal sealed class DateMarshaler : IArrayElementMarshaler { internal static double ConvertToNative(DateTime managedDate) { @@ -749,84 +749,6 @@ internal static unsafe void ClearNative(IntPtr pMarshalState, IntPtr pNativeHome internal static partial void ClearNativeContents(IntPtr pMarshalState, IntPtr pNativeHome, int cElements); } // class MngdNativeArrayMarshaler - internal static partial class MngdFixedArrayMarshaler - { - // Needs to match exactly with MngdFixedArrayMarshaler in ilmarshalers.h - private struct MarshalerState - { -#pragma warning disable CA1823, IDE0044 // not used by managed code - internal IntPtr m_pElementMT; - internal IntPtr m_Array; - internal int m_BestFitMap; - internal int m_ThrowOnUnmappableChar; - internal ushort m_vt; - internal uint m_cElements; -#pragma warning restore CA1823, IDE0044 - } - - internal static unsafe void CreateMarshaler(IntPtr pMarshalState, IntPtr pMT, int dwFlags, int cElements) - { - MarshalerState* pState = (MarshalerState*)pMarshalState; - pState->m_pElementMT = pMT; - pState->m_Array = default; - pState->m_BestFitMap = (byte)(dwFlags >> 16); - pState->m_ThrowOnUnmappableChar = (byte)(dwFlags >> 24); - pState->m_vt = (ushort)dwFlags; - pState->m_cElements = (uint)cElements; - } - - internal static unsafe void ConvertSpaceToNative(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) - { - // We don't actually need to allocate native space here as the space is inline in the native layout. - // However, we need to validate that we can fit the contents of the managed array in the native space. - Array arr = (Array)pManagedHome; - MarshalerState* pState = (MarshalerState*)pMarshalState; - - if (arr is not null && (uint)arr.Length < pState->m_cElements) - { - throw new ArgumentException(SR.Argument_WrongSizeArrayInNativeStruct); - } - } - - internal static void ConvertContentsToNative(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) - { - object managedHome = pManagedHome; - ConvertContentsToNative(pMarshalState, ObjectHandleOnStack.Create(ref managedHome), pNativeHome); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdFixedArrayMarshaler_ConvertContentsToNative")] - private static partial void ConvertContentsToNative(IntPtr pMarshalState, ObjectHandleOnStack pManagedHome, IntPtr pNativeHome); - - internal static void ConvertSpaceToManaged(IntPtr pMarshalState, ref object pManagedHome, IntPtr pNativeHome) - { - object managedHome = pManagedHome; - ConvertSpaceToManaged(pMarshalState, ObjectHandleOnStack.Create(ref managedHome), pNativeHome); - pManagedHome = managedHome; - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdFixedArrayMarshaler_ConvertSpaceToManaged")] - private static partial void ConvertSpaceToManaged(IntPtr pMarshalState, ObjectHandleOnStack pManagedHome, IntPtr pNativeHome); - - internal static void ConvertContentsToManaged(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) - { - object managedHome = pManagedHome; - ConvertContentsToManaged(pMarshalState, ObjectHandleOnStack.Create(ref managedHome), pNativeHome); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdFixedArrayMarshaler_ConvertContentsToManaged")] - private static partial void ConvertContentsToManaged(IntPtr pMarshalState, ObjectHandleOnStack pManagedHome, IntPtr pNativeHome); - -#pragma warning disable IDE0060 // Remove unused parameter. These APIs need to match a the shape of a "managed" marshaler. - internal static void ClearNativeContents(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) - { - ClearNativeContents(pMarshalState, pNativeHome); - } -#pragma warning restore IDE0060 - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdFixedArrayMarshaler_ClearNativeContents")] - private static partial void ClearNativeContents(IntPtr pMarshalState, IntPtr pNativeHome); - } // class MngdFixedArrayMarshaler - #if FEATURE_COMINTEROP internal static partial class MngdSafeArrayMarshaler { @@ -2486,6 +2408,11 @@ internal static unsafe void ClearArrayNative(byte* pNativeHome, i } } + internal static void ThrowWrongSizeArrayInNativeStruct() + { + throw new ArgumentException(SR.Argument_WrongSizeArrayInNativeStruct); + } + private static readonly MemberInfo StructureMarshalerConvertToUnmanaged = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.ConvertToUnmanaged))!; private static readonly MemberInfo StructureMarshalerConvertToManaged = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.ConvertToManaged))!; private static readonly MemberInfo StructureMarshalerFree = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.Free))!; diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 16cca4603c744d..ed9fcf00664d39 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1082,6 +1082,7 @@ DEFINE_METHOD(STUBHELPERS, FREE_ARRAY_CONTENTS, FreeArr DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_NATIVE, ConvertArraySpaceToNative, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_MANAGED, ConvertArraySpaceToManaged, NoSig) DEFINE_METHOD(STUBHELPERS, CLEAR_ARRAY_NATIVE, ClearArrayNative, NoSig) +DEFINE_METHOD(STUBHELPERS, THROW_WRONG_SIZE_ARRAY_IN_NSTRUCT, ThrowWrongSizeArrayInNativeStruct, SM_RetVoid) #ifdef FEATURE_COMINTEROP DEFINE_CLASS(IDISPATCHHELPERS, Interop, IDispatchHelpers) @@ -1207,14 +1208,6 @@ DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CONVERT_CONTENTS_TO_MANAGED, ConvertC DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CLEAR_NATIVE, ClearNative, SM_IntPtr_IntPtr_Int_RetVoid) DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CLEAR_NATIVE_CONTENTS, ClearNativeContents, SM_IntPtr_IntPtr_Int_RetVoid) -DEFINE_CLASS(MNGD_FIXED_ARRAY_MARSHALER, StubHelpers, MngdFixedArrayMarshaler) -DEFINE_METHOD(MNGD_FIXED_ARRAY_MARSHALER, CREATE_MARSHALER, CreateMarshaler, SM_IntPtr_IntPtr_Int_Int_RetVoid) -DEFINE_METHOD(MNGD_FIXED_ARRAY_MARSHALER, CONVERT_SPACE_TO_NATIVE, ConvertSpaceToNative, SM_IntPtr_RefObj_IntPtr_RetVoid) -DEFINE_METHOD(MNGD_FIXED_ARRAY_MARSHALER, CONVERT_CONTENTS_TO_NATIVE, ConvertContentsToNative, SM_IntPtr_RefObj_IntPtr_RetVoid) -DEFINE_METHOD(MNGD_FIXED_ARRAY_MARSHALER, CONVERT_SPACE_TO_MANAGED, ConvertSpaceToManaged, SM_IntPtr_RefObj_IntPtr_RetVoid) -DEFINE_METHOD(MNGD_FIXED_ARRAY_MARSHALER, CONVERT_CONTENTS_TO_MANAGED, ConvertContentsToManaged, SM_IntPtr_RefObj_IntPtr_RetVoid) -DEFINE_METHOD(MNGD_FIXED_ARRAY_MARSHALER, CLEAR_NATIVE_CONTENTS, ClearNativeContents, SM_IntPtr_RefObj_IntPtr_RetVoid) - DEFINE_CLASS(MNGD_REF_CUSTOM_MARSHALER, StubHelpers, MngdRefCustomMarshaler) DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, CONVERT_CONTENTS_TO_NATIVE, ConvertContentsToNative, SM_ICustomMarshaler_RefObj_PtrIntPtr_RetVoid) DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, CONVERT_CONTENTS_TO_MANAGED, ConvertContentsToManaged, SM_ICustomMarshaler_RefObj_PtrIntPtr_RetVoid) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 46662110510d88..36bed2c367af23 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4199,7 +4199,7 @@ void ILNativeArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) } } -void ILNativeArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) +void ILArrayMarshalerBase::EmitClearNativeContents(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; @@ -4388,7 +4388,7 @@ extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ClearNativeContents(MngdNativ END_QCALL; } -MethodTable* ILNativeArrayMarshaler::GetMarshalerMT() +MethodTable* ILArrayMarshalerBase::GetMarshalerMT() { STANDARD_VM_CONTRACT; @@ -4542,13 +4542,13 @@ MethodTable* ILNativeArrayMarshaler::GetMarshalerMT() } default: - _ASSERTE(!"Unsupported VT for ILNativeArrayMarshaler"); + _ASSERTE(!"Unsupported VT for ILArrayMarshalerBase"); COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); return NULL; } } -MethodDesc* ILNativeArrayMarshaler::GetInstantiatedArrayMethod(BinderMethodID methodId) +MethodDesc* ILArrayMarshalerBase::GetInstantiatedArrayMethod(BinderMethodID methodId) { STANDARD_VM_CONTRACT; @@ -4575,7 +4575,7 @@ MethodDesc* ILNativeArrayMarshaler::GetInstantiatedArrayMethod(BinderMethodID me return pInstMD; } -void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) +void ILArrayMarshalerBase::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; @@ -4586,7 +4586,7 @@ void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILE pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); } -void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) +void ILArrayMarshalerBase::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; @@ -4597,181 +4597,62 @@ void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILE pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); } -void ILFixedArrayMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) +// ==================== ILFixedArrayMarshaler ==================== + +void ILFixedArrayMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; - m_dwMngdMarshalerLocalNum = pslILEmit->NewLocal(ELEMENT_TYPE_I); - - pslILEmit->EmitLDC(sizeof(MngdFixedArrayMarshaler)); - pslILEmit->EmitLOCALLOC(); - pslILEmit->EmitSTLOC(m_dwMngdMarshalerLocalNum); - + // For fixed arrays, the native space is inline in the struct. + // Validate that the managed array (if non-null) has enough elements. CREATE_MARSHALER_CARRAY_OPERANDS mops; m_pargs->m_pMarshalInfo->GetMops(&mops); - pslILEmit->EmitLDLOC(m_dwMngdMarshalerLocalNum); - - pslILEmit->EmitLDTOKEN(pslILEmit->GetToken(mops.methodTable)); - pslILEmit->EmitCALL(METHOD__RT_TYPE_HANDLE__TO_INTPTR, 1, 1); - - DWORD dwFlags = mops.elementType; - dwFlags |= (((DWORD)mops.bestfitmapping) << 16); - dwFlags |= (((DWORD)mops.throwonunmappablechar) << 24); + ILCodeLabel* pDoneLabel = pslILEmit->NewCodeLabel(); - pslILEmit->EmitLDC(dwFlags); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitBRFALSE(pDoneLabel); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDLEN(); pslILEmit->EmitLDC(mops.additive); + pslILEmit->EmitBGE_UN(pDoneLabel); - pslILEmit->EmitCALL(METHOD__MNGD_FIXED_ARRAY_MARSHALER__CREATE_MARSHALER, 4, 0); -} - -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToNative(MngdFixedArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void* pNativeHome) -{ - QCALL_CONTRACT; - BEGIN_QCALL; - - GCX_COOP(); - - BASEARRAYREF arrayRef = NULL; - GCPROTECT_BEGIN(arrayRef); - arrayRef = (BASEARRAYREF)pManagedHome.Get(); - - if (pThis->m_vt == VTHACK_ANSICHAR) - { - SIZE_T nativeSize = sizeof(CHAR) * pThis->m_cElements; + // Array too small for the fixed-size native layout - throw + pslILEmit->EmitCALL(METHOD__STUBHELPERS__THROW_WRONG_SIZE_ARRAY_IN_NSTRUCT, 0, 0); - if (arrayRef == NULL) - { - FillMemory(pNativeHome, nativeSize, 0); - } - else - { - InternalWideToAnsi((const WCHAR*)arrayRef->GetDataPtr(), - pThis->m_cElements, - (CHAR*)pNativeHome, - (int)nativeSize, - pThis->m_BestFitMap, - pThis->m_ThrowOnUnmappableChar); - } - } - else - { - SIZE_T cbElement = OleVariant::GetElementSizeForVarType(pThis->m_vt, pThis->m_pElementMT); - SIZE_T nativeSize = cbElement * pThis->m_cElements; - - if (arrayRef == NULL) - { - FillMemory(pNativeHome, nativeSize, 0); - } - else - { - - const OleVariant::Marshaler* pMarshaler = OleVariant::GetMarshalerForVarType(pThis->m_vt, TRUE); - SIZE_T cElements = arrayRef->GetNumComponents(); - if (pMarshaler == NULL || pMarshaler->ComToOleArray == NULL) - { - _ASSERTE(!OleVariant::GetTypeHandleForVarType(pThis->m_vt).GetMethodTable()->ContainsGCPointers()); - memcpyNoGCRefs(pNativeHome, arrayRef->GetDataPtr(), nativeSize); - } - else - { - pMarshaler->ComToOleArray(&arrayRef, pNativeHome, pThis->m_pElementMT, pThis->m_BestFitMap, - pThis->m_ThrowOnUnmappableChar, FALSE, pThis->m_cElements); - } - } - } - - GCPROTECT_END(); - END_QCALL; + pslILEmit->EmitLabel(pDoneLabel); } -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertSpaceToManaged(MngdFixedArrayMarshaler* pThis, - QCall::ObjectHandleOnStack pManagedHome, void* pNativeHome) +void ILFixedArrayMarshaler::EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) { - QCALL_CONTRACT; - - BEGIN_QCALL; - - GCX_COOP(); - - // @todo: lookup this class before marshal time - if (pThis->m_Array.IsNull()) - { - // Get proper array class name & type - pThis->m_Array = OleVariant::GetArrayForVarType(pThis->m_vt, TypeHandle(pThis->m_pElementMT)); - if (pThis->m_Array.IsNull()) - COMPlusThrow(kTypeLoadException); - } - // - // Allocate array - // + STANDARD_VM_CONTRACT; - OBJECTREF arrayRef = AllocateSzArray(pThis->m_Array, pThis->m_cElements); - pManagedHome.Set(arrayRef); + // Allocate the managed array with the fixed element count. + CREATE_MARSHALER_CARRAY_OPERANDS mops; + m_pargs->m_pMarshalInfo->GetMops(&mops); - END_QCALL; + // new T[cElements] + pslILEmit->EmitLDC(mops.additive); + pslILEmit->EmitNEWARR(pslILEmit->GetToken(mops.methodTable)); + EmitStoreManagedValue(pslILEmit); } -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToManaged(MngdFixedArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void* pNativeHome) +void ILFixedArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) { - QCALL_CONTRACT; - BEGIN_QCALL; - - GCX_COOP(); - - BASEARRAYREF arrayRef = NULL; - - GCPROTECT_BEGIN(arrayRef); - arrayRef = (BASEARRAYREF)pManagedHome.Get(); - - if (pThis->m_vt == VTHACK_ANSICHAR) - { - MultiByteToWideChar(CP_ACP, - MB_PRECOMPOSED, - (const CHAR*)pNativeHome, - pThis->m_cElements * sizeof(CHAR), // size, in bytes, of in buffer - (WCHAR*)(arrayRef->GetDataPtr()), - pThis->m_cElements); // size, in WCHAR's of outbuffer - } - else - { - const OleVariant::Marshaler* pMarshaler = OleVariant::GetMarshalerForVarType(pThis->m_vt, TRUE); - - SIZE_T cbElement = OleVariant::GetElementSizeForVarType(pThis->m_vt, pThis->m_pElementMT); - SIZE_T nativeSize = cbElement * pThis->m_cElements; - - - if (pMarshaler == NULL || pMarshaler->OleToComArray == NULL) - { - // If we are copying variants, strings, etc, we need to use write barrier - _ASSERTE(!OleVariant::GetTypeHandleForVarType(pThis->m_vt).GetMethodTable()->ContainsGCPointers()); - memcpyNoGCRefs(arrayRef->GetDataPtr(), pNativeHome, nativeSize); - } - else - { - pMarshaler->OleToComArray(pNativeHome, &arrayRef, pThis->m_pElementMT); - } - } + STANDARD_VM_CONTRACT; - GCPROTECT_END(); - END_QCALL; + CREATE_MARSHALER_CARRAY_OPERANDS mops; + m_pargs->m_pMarshalInfo->GetMops(&mops); + pslILEmit->EmitLDC(mops.additive); } -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ClearNativeContents(MngdFixedArrayMarshaler* pThis, void* pNativeHome) +void ILFixedArrayMarshaler::EmitClearNative(ILCodeStream* pslILEmit) { - QCALL_CONTRACT; - BEGIN_QCALL; - GCX_COOP(); - - const OleVariant::Marshaler* pMarshaler = OleVariant::GetMarshalerForVarType(pThis->m_vt, FALSE); - - if (pMarshaler != NULL && pMarshaler->ClearOleArray != NULL) - { - pMarshaler->ClearOleArray(pNativeHome, pThis->m_cElements, pThis->m_pElementMT); - } + STANDARD_VM_CONTRACT; - END_QCALL; + // For fixed arrays, native space is inline - just clear element contents, don't free a buffer. + EmitClearNativeContents(pslILEmit); } #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index b51a8a871f2b8b..e85a4b18650df8 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3268,7 +3268,46 @@ class ILMngdMarshaler : public ILMarshaler const BinderMethodID m_idClearManaged; }; -class ILNativeArrayMarshaler : public ILMarshaler +// Base class for array marshalers that use managed IArrayElementMarshaler implementations +// for element-by-element conversion. Provides shared VT-to-marshaler-type resolution and +// generic method instantiation for ConvertArrayContents/FreeArrayContents helpers. +class ILArrayMarshalerBase : public ILMarshaler +{ +protected: + LocalDesc GetNativeType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_I); + } + + LocalDesc GetManagedType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_OBJECT); + } + + bool NeedsClearNative() override + { + LIMITED_METHOD_CONTRACT; + return true; + } + + void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; + void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; + void EmitClearNativeContents(ILCodeStream* pslILEmit) override; + + // Resolve the managed marshaler MethodTable for the given VT/element type. + MethodTable* GetMarshalerMT(); + + // Instantiate one of the generic StubHelpers array methods with the element type and marshaler type. + MethodDesc* GetInstantiatedArrayMethod(BinderMethodID methodId); + + // Load the native element count onto the evaluation stack. + // Subclasses implement this differently (dynamic size param vs. fixed count). + virtual void EmitLoadNativeSize(ILCodeStream* pslILEmit) = 0; +}; + +class ILNativeArrayMarshaler : public ILArrayMarshalerBase { public: enum @@ -3287,18 +3326,9 @@ class ILNativeArrayMarshaler : public ILMarshaler void EmitMarshalViaPinning(ILCodeStream* pslILEmit) override; void EmitSetupArgumentForMarshalling(ILCodeStream* pslILEmit) override; - void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; - void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; void EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) override; void EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) override; void EmitClearNative(ILCodeStream* pslILEmit) override; - void EmitClearNativeContents(ILCodeStream* pslILEmit) override; - - bool NeedsClearNative() override - { - LIMITED_METHOD_CONTRACT; - return true; - } bool SupportsFieldMarshal(UINT* pErrorResID) override { @@ -3307,34 +3337,16 @@ class ILNativeArrayMarshaler : public ILMarshaler } protected: - LocalDesc GetNativeType() override - { - LIMITED_METHOD_CONTRACT; - return LocalDesc(ELEMENT_TYPE_I); - } - - LocalDesc GetManagedType() override - { - LIMITED_METHOD_CONTRACT; - return LocalDesc(ELEMENT_TYPE_OBJECT); - } BOOL CheckSizeParamIndexArg(const CREATE_MARSHALER_CARRAY_OPERANDS &mops, CorElementType *pElementType); // Calculate element count and load it on evaluation stack void EmitLoadElementCount(ILCodeStream* pslILEmit); - void EmitLoadNativeSize(ILCodeStream* pslILEmit); + void EmitLoadNativeSize(ILCodeStream* pslILEmit) override; void EmitNewSavedSizeArgLocal(ILCodeStream* pslILEmit); DWORD m_dwSavedSizeArg; - -private: - // Resolve the managed marshaler MethodTable for the given VT/element type. - MethodTable* GetMarshalerMT(); - - // Instantiate one of the generic StubHelpers array methods with the element type and marshaler type. - MethodDesc* GetInstantiatedArrayMethod(BinderMethodID methodId); }; struct MngdNativeArrayMarshaler @@ -3353,7 +3365,7 @@ extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertSpaceToManaged(MngdNat extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToManaged(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome); extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ClearNativeContents(MngdNativeArrayMarshaler* pThis, void** pNativeHome, INT32 cElements); -class ILFixedArrayMarshaler : public ILMngdMarshaler +class ILFixedArrayMarshaler : public ILArrayMarshalerBase { public: enum @@ -3362,20 +3374,6 @@ class ILFixedArrayMarshaler : public ILMngdMarshaler c_fInOnly = FALSE }; - ILFixedArrayMarshaler() : - ILMngdMarshaler( - METHOD__MNGD_FIXED_ARRAY_MARSHALER__CONVERT_SPACE_TO_MANAGED, - METHOD__MNGD_FIXED_ARRAY_MARSHALER__CONVERT_CONTENTS_TO_MANAGED, - METHOD__MNGD_FIXED_ARRAY_MARSHALER__CONVERT_SPACE_TO_NATIVE, - METHOD__MNGD_FIXED_ARRAY_MARSHALER__CONVERT_CONTENTS_TO_NATIVE, - METHOD__MNGD_FIXED_ARRAY_MARSHALER__CLEAR_NATIVE_CONTENTS, - METHOD__MNGD_FIXED_ARRAY_MARSHALER__CLEAR_NATIVE_CONTENTS, - METHOD__NIL - ) - { - LIMITED_METHOD_CONTRACT; - } - bool SupportsArgumentMarshal(DWORD dwMarshalFlags, UINT* pErrorResID) override { LIMITED_METHOD_CONTRACT; @@ -3390,24 +3388,12 @@ class ILFixedArrayMarshaler : public ILMngdMarshaler protected: - void EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) override; -}; - -struct MngdFixedArrayMarshaler -{ - MethodTable* m_pElementMT; - TypeHandle m_Array; - BOOL m_BestFitMap; - BOOL m_ThrowOnUnmappableChar; - VARTYPE m_vt; - UINT32 m_cElements; + void EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) override; + void EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) override; + void EmitLoadNativeSize(ILCodeStream* pslILEmit) override; + void EmitClearNative(ILCodeStream* pslILEmit) override; }; -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToNative(MngdFixedArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void* pNativeHome); -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertSpaceToManaged(MngdFixedArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void* pNativeHome); -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ConvertContentsToManaged(MngdFixedArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void* pNativeHome); -extern "C" void QCALLTYPE MngdFixedArrayMarshaler_ClearNativeContents(MngdFixedArrayMarshaler* pThis, void* pNativeHome); - #ifdef FEATURE_COMINTEROP class ILSafeArrayMarshaler : public ILMngdMarshaler { diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index b7633063e87fab..eba0fdc56481ff 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -399,10 +399,6 @@ static const Entry s_QCall[] = DllImportEntry(MngdNativeArrayMarshaler_ConvertSpaceToManaged) DllImportEntry(MngdNativeArrayMarshaler_ConvertContentsToManaged) DllImportEntry(MngdNativeArrayMarshaler_ClearNativeContents) - DllImportEntry(MngdFixedArrayMarshaler_ConvertContentsToNative) - DllImportEntry(MngdFixedArrayMarshaler_ConvertSpaceToManaged) - DllImportEntry(MngdFixedArrayMarshaler_ConvertContentsToManaged) - DllImportEntry(MngdFixedArrayMarshaler_ClearNativeContents) #ifdef FEATURE_COMINTEROP DllImportEntry(MngdSafeArrayMarshaler_CreateMarshaler) DllImportEntry(MngdSafeArrayMarshaler_ConvertSpaceToNative) From 0e7c57af310258229b72351018b8119d9601b60b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 10:45:40 -0700 Subject: [PATCH 04/41] Convert AsAnyMarshaler to use the new array marshaling logic instead of the old one --- .../InteropServices/Marshal.CoreCLR.cs | 6 +- .../src/System/StubHelpers.cs | 148 ++++++++---------- src/coreclr/vm/corelib.h | 1 - src/coreclr/vm/ilmarshalers.cpp | 10 -- 4 files changed, 69 insertions(+), 96 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs index ffeb7092bf0fa4..bab0434a883a46 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs @@ -110,8 +110,7 @@ private static unsafe T ReadValueSlow(object ptr, int ofs, Func(object ptr, int ofs, T val, Action< (int)AsAnyMarshaler.AsAnyFlags.IsAnsi | (int)AsAnyMarshaler.AsAnyFlags.IsBestFit; - MngdNativeArrayMarshaler.MarshalerState nativeArrayMarshalerState = default; - AsAnyMarshaler marshaler = new AsAnyMarshaler(new IntPtr(&nativeArrayMarshalerState)); + AsAnyMarshaler marshaler = default; IntPtr pNativeHome = IntPtr.Zero; diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 39498a268d7311..332d26af971ecd 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -54,7 +54,7 @@ internal static char ConvertToManaged(byte nativeChar) internal static class CSTRMarshaler { - internal static unsafe IntPtr ConvertToNative(int flags, string strManaged, IntPtr pNativeBuffer) + internal static unsafe IntPtr ConvertToNative(int flags, string? strManaged, IntPtr pNativeBuffer) { if (null == strManaged) { @@ -266,7 +266,7 @@ private static void SetTrailByte(string strManaged, byte trailByte) s_trailByteTable!.Add(strManaged, new TrailByte(trailByte)); } - internal static unsafe IntPtr ConvertToNative(string strManaged, IntPtr pNativeBuffer) + internal static unsafe IntPtr ConvertToNative(string? strManaged, IntPtr pNativeBuffer) { if (null == strManaged) { @@ -960,9 +960,6 @@ internal static void GetCustomMarshalerInstance(void* pMT, byte* pCookie, int cC internal struct AsAnyMarshaler { - private const ushort VTHACK_ANSICHAR = 253; - private const ushort VTHACK_WINBOOL = 254; - private enum BackPropAction { None, @@ -972,8 +969,14 @@ private enum BackPropAction StringBuilderUnicode } - // Pointer to MngdNativeArrayMarshaler, ownership not assumed. - private readonly IntPtr pvArrayMarshaler; + private struct ArrayMarshalerMethods + { + public MethodInvoker convertContentsToNative; + public MethodInvoker convertContentsToManaged; + public unsafe delegate* convertSpaceToNative; + } + + private ArrayMarshalerMethods arrayMarshalerMethods; // Type of action to perform after the CLR-to-unmanaged call. private BackPropAction backPropAction; @@ -1000,89 +1003,75 @@ internal enum AsAnyFlags private static bool IsThrowOn(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsThrowOn) != 0; private static bool IsBestFit(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsBestFit) != 0; - internal AsAnyMarshaler(IntPtr pvArrayMarshaler) - { - // we need this in case the value being marshaled turns out to be array - Debug.Assert(pvArrayMarshaler != IntPtr.Zero, "pvArrayMarshaler must not be null"); - - this.pvArrayMarshaler = pvArrayMarshaler; - backPropAction = BackPropAction.None; - layoutType = null; - cleanupWorkList = null; - } - #region ConvertToNative helpers private unsafe IntPtr ConvertArrayToNative(object pManagedHome, int dwFlags) { Type elementType = pManagedHome.GetType().GetElementType()!; - VarEnum vt; - - switch (Type.GetTypeCode(elementType)) - { - case TypeCode.SByte: vt = VarEnum.VT_I1; break; - case TypeCode.Byte: vt = VarEnum.VT_UI1; break; - case TypeCode.Int16: vt = VarEnum.VT_I2; break; - case TypeCode.UInt16: vt = VarEnum.VT_UI2; break; - case TypeCode.Int32: vt = VarEnum.VT_I4; break; - case TypeCode.UInt32: vt = VarEnum.VT_UI4; break; - case TypeCode.Int64: vt = VarEnum.VT_I8; break; - case TypeCode.UInt64: vt = VarEnum.VT_UI8; break; - case TypeCode.Single: vt = VarEnum.VT_R4; break; - case TypeCode.Double: vt = VarEnum.VT_R8; break; - case TypeCode.Char: vt = (IsAnsi(dwFlags) ? (VarEnum)VTHACK_ANSICHAR : VarEnum.VT_UI2); break; - case TypeCode.Boolean: vt = (VarEnum)VTHACK_WINBOOL; break; - - case TypeCode.Object: - { - if (elementType == typeof(IntPtr)) - { - vt = (IntPtr.Size == 4 ? VarEnum.VT_I4 : VarEnum.VT_I8); - } - else if (elementType == typeof(UIntPtr)) - { - vt = (IntPtr.Size == 4 ? VarEnum.VT_UI4 : VarEnum.VT_UI8); - } - else goto default; - break; - } - - default: - throw new ArgumentException(SR.Arg_PInvokeBadObject); - } - // marshal the object as C-style array (UnmanagedType.LPArray) - int dwArrayMarshalerFlags = (int)vt; - if (IsBestFit(dwFlags)) dwArrayMarshalerFlags |= (1 << 16); - if (IsThrowOn(dwFlags)) dwArrayMarshalerFlags |= (1 << 24); - - MngdNativeArrayMarshaler.CreateMarshaler( - pvArrayMarshaler, - IntPtr.Zero, // not needed as we marshal primitive VTs only - dwArrayMarshalerFlags, - nativeDataValid: false); - - IntPtr pNativeHome; - IntPtr pNativeHomeAddr = new IntPtr(&pNativeHome); - - MngdNativeArrayMarshaler.ConvertSpaceToNative( - pvArrayMarshaler, - in pManagedHome, - pNativeHomeAddr); + Type[] marshalerGenericArgs = Type.GetTypeCode(elementType) switch + { + TypeCode.SByte + or TypeCode.Byte + or TypeCode.Int16 + or TypeCode.UInt16 + or TypeCode.Int32 + or TypeCode.UInt32 + or TypeCode.Int64 + or TypeCode.UInt64 + or TypeCode.Single + or TypeCode.Double => [elementType, typeof(StructureMarshaler<>).MakeGenericType(elementType)], + TypeCode.Object when elementType == typeof(nint) || elementType == typeof(nuint) => [elementType, typeof(StructureMarshaler<>).MakeGenericType(elementType)], + TypeCode.Char when !IsAnsi(dwFlags) => [typeof(char), typeof(StructureMarshaler)], + TypeCode.Char when IsAnsi(dwFlags) => [ + typeof(char), + typeof(AnsiCharArrayElementMarshaler<,>).MakeGenericType([ + IsBestFit(dwFlags) + ? typeof(IMarshalerOption.EnabledOption) + : typeof(IMarshalerOption.DisabledOption), + IsThrowOn(dwFlags) + ? typeof(IMarshalerOption.EnabledOption) + : typeof(IMarshalerOption.DisabledOption)])], + TypeCode.Boolean => [typeof(bool), typeof(BoolMarshaler)], + _ => [] + }; + + arrayMarshalerMethods = new ArrayMarshalerMethods + { + convertContentsToManaged = MethodInvoker.Create( + typeof(StubHelpers) + .GetMethod( + nameof(StubHelpers.ConvertArrayContentsToManaged), + BindingFlags.Public | BindingFlags.Static)! + .MakeGenericMethod(marshalerGenericArgs)), + convertContentsToNative = MethodInvoker.Create( + typeof(StubHelpers) + .GetMethod( + nameof(StubHelpers.ConvertArrayContentsToUnmanaged), + BindingFlags.Public | BindingFlags.Static)! + .MakeGenericMethod(marshalerGenericArgs)), + convertSpaceToNative = (delegate*) + typeof(StubHelpers) + .GetMethod( + nameof(StubHelpers.ConvertArraySpaceToNative), + BindingFlags.Public | BindingFlags.Static)! + .MakeGenericMethod(marshalerGenericArgs) + .MethodHandle.GetFunctionPointer(), + }; + + byte* pNativeHome = arrayMarshalerMethods.convertSpaceToNative((Array)pManagedHome); if (IsIn(dwFlags)) { - MngdNativeArrayMarshaler.ConvertContentsToNative( - pvArrayMarshaler, - in pManagedHome, - pNativeHomeAddr); + arrayMarshalerMethods.convertContentsToNative.Invoke(pManagedHome, Pointer.Box(pNativeHome, typeof(byte*))); } + if (IsOut(dwFlags)) { backPropAction = BackPropAction.Array; } - return pNativeHome; + return (IntPtr)pNativeHome; } private static IntPtr ConvertStringToNative(string pManagedHome, int dwFlags) @@ -1267,10 +1256,7 @@ internal unsafe void ConvertToManaged(object pManagedHome, IntPtr pNativeHome) { case BackPropAction.Array: { - MngdNativeArrayMarshaler.ConvertContentsToManaged( - pvArrayMarshaler, - in pManagedHome, - new IntPtr(&pNativeHome)); + arrayMarshalerMethods.convertContentsToManaged.Invoke(pManagedHome, pNativeHome); break; } @@ -2345,7 +2331,7 @@ internal static unsafe void LayoutTypeConvertToManaged(object* obj, byte* pNativ } } - internal static unsafe void ConvertArrayContentsToUnmanaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToUnmanaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler { for (int i = 0; i < managed.Length; i++) { @@ -2354,7 +2340,7 @@ internal static unsafe void ConvertArrayContentsToUnmanaged(T[] m } } - internal static unsafe void ConvertArrayContentsToManaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToManaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler { for (int i = 0; i < managed.Length; i++) { diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index ed9fcf00664d39..a56c24f2cb51bc 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1219,7 +1219,6 @@ DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, CLEAR_MANAGED_UCO, ClearM DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, GET_CUSTOM_MARSHALER_INSTANCE, GetCustomMarshalerInstance, SM_PtrVoid_PtrByte_Int_PtrObj_PtrException_RetVoid) DEFINE_CLASS(ASANY_MARSHALER, StubHelpers, AsAnyMarshaler) -DEFINE_METHOD(ASANY_MARSHALER, CTOR, .ctor, IM_IntPtr_RetVoid) DEFINE_METHOD(ASANY_MARSHALER, CONVERT_TO_NATIVE, ConvertToNative, IM_Obj_Int_RetIntPtr) DEFINE_METHOD(ASANY_MARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, IM_Obj_IntPtr_RetVoid) DEFINE_METHOD(ASANY_MARSHALER, CLEAR_NATIVE, ClearNative, IM_IntPtr_RetVoid) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 36bed2c367af23..05baa6a1ec254e 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -3756,19 +3756,9 @@ void ILAsAnyMarshalerBase::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) LocalDesc marshalerType(CoreLibBinder::GetClass(CLASS__ASANY_MARSHALER)); m_dwMngdMarshalerLocalNum = pslILEmit->NewLocal(marshalerType); - DWORD dwTmpLocalNum = pslILEmit->NewLocal(ELEMENT_TYPE_I); - pslILEmit->EmitLDC(sizeof(MngdNativeArrayMarshaler)); - pslILEmit->EmitLOCALLOC(); - pslILEmit->EmitSTLOC(dwTmpLocalNum); - - // marshaler = new AsAnyMarshaler(local_buffer) pslILEmit->EmitLDLOCA(m_dwMngdMarshalerLocalNum); pslILEmit->EmitINITOBJ(pslILEmit->GetToken(marshalerType.InternalToken)); - - pslILEmit->EmitLDLOCA(m_dwMngdMarshalerLocalNum); - pslILEmit->EmitLDLOC(dwTmpLocalNum); - pslILEmit->EmitCALL(METHOD__ASANY_MARSHALER__CTOR, 2, 0); } void ILAsAnyMarshalerBase::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) From f95160af02f015f039592449309f0f0f9dec970c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 10:56:15 -0700 Subject: [PATCH 05/41] Remove old native array marshaler --- .../src/System/StubHelpers.cs | 78 --------- src/coreclr/vm/corelib.h | 9 - src/coreclr/vm/ilmarshalers.cpp | 160 ------------------ src/coreclr/vm/ilmarshalers.h | 16 -- src/coreclr/vm/qcallentrypoints.cpp | 5 - 5 files changed, 268 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 332d26af971ecd..96007d54f5e36e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -671,84 +671,6 @@ internal static object GetObjectForComCallableWrapperIUnknown(IntPtr unk) } // class InterfaceMarshaler #endif // FEATURE_COMINTEROP - internal static partial class MngdNativeArrayMarshaler - { - // Needs to match exactly with MngdNativeArrayMarshaler in ilmarshalers.h - internal struct MarshalerState - { - internal IntPtr m_pElementMT; - internal TypeHandle m_Array; - internal int m_NativeDataValid; - internal int m_BestFitMap; - internal int m_ThrowOnUnmappableChar; - internal short m_vt; - } - - internal static unsafe void CreateMarshaler(IntPtr pMarshalState, IntPtr pMT, int dwFlags, bool nativeDataValid) - { - MarshalerState* pState = (MarshalerState*)pMarshalState; - pState->m_pElementMT = pMT; - pState->m_Array = default; - pState->m_NativeDataValid = nativeDataValid ? 1 : 0; - pState->m_BestFitMap = (byte)(dwFlags >> 16); - pState->m_ThrowOnUnmappableChar = (byte)(dwFlags >> 24); - pState->m_vt = (short)dwFlags; - } - - internal static void ConvertSpaceToNative(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) - { - object managedHome = pManagedHome; - ConvertSpaceToNative(pMarshalState, ObjectHandleOnStack.Create(ref managedHome), pNativeHome); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdNativeArrayMarshaler_ConvertSpaceToNative")] - private static partial void ConvertSpaceToNative(IntPtr pMarshalState, ObjectHandleOnStack pManagedHome, IntPtr pNativeHome); - - internal static void ConvertContentsToNative(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) - { - object managedHome = pManagedHome; - ConvertContentsToNative(pMarshalState, ObjectHandleOnStack.Create(ref managedHome), pNativeHome); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdNativeArrayMarshaler_ConvertContentsToNative")] - private static partial void ConvertContentsToNative(IntPtr pMarshalState, ObjectHandleOnStack pManagedHome, IntPtr pNativeHome); - - internal static void ConvertSpaceToManaged(IntPtr pMarshalState, ref object? pManagedHome, IntPtr pNativeHome, - int cElements) - { - object? managedHome = null; - ConvertSpaceToManaged(pMarshalState, ObjectHandleOnStack.Create(ref managedHome), pNativeHome, cElements); - pManagedHome = managedHome; - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdNativeArrayMarshaler_ConvertSpaceToManaged")] - private static partial void ConvertSpaceToManaged(IntPtr pMarshalState, ObjectHandleOnStack pManagedHome, IntPtr pNativeHome, - int cElements); - - internal static void ConvertContentsToManaged(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) - { - object managedHome = pManagedHome; - ConvertContentsToManaged(pMarshalState, ObjectHandleOnStack.Create(ref managedHome), pNativeHome); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdNativeArrayMarshaler_ConvertContentsToManaged")] - private static partial void ConvertContentsToManaged(IntPtr pMarshalState, ObjectHandleOnStack pManagedHome, IntPtr pNativeHome); - - internal static unsafe void ClearNative(IntPtr pMarshalState, IntPtr pNativeHome, int cElements) - { - IntPtr nativeHome = *(IntPtr*)pNativeHome; - - if (nativeHome != IntPtr.Zero) - { - ClearNativeContents(pMarshalState, pNativeHome, cElements); - Marshal.FreeCoTaskMem(nativeHome); - } - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdNativeArrayMarshaler_ClearNativeContents")] - internal static partial void ClearNativeContents(IntPtr pMarshalState, IntPtr pNativeHome, int cElements); - } // class MngdNativeArrayMarshaler - #if FEATURE_COMINTEROP internal static partial class MngdSafeArrayMarshaler { diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index a56c24f2cb51bc..7ae5316375b062 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1199,15 +1199,6 @@ DEFINE_METHOD(VBBYVALSTRMARSHALER, CONVERT_TO_NATIVE, ConvertToNative, DEFINE_METHOD(VBBYVALSTRMARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, SM_IntPtr_Int_RetStr) DEFINE_METHOD(VBBYVALSTRMARSHALER, CLEAR_NATIVE, ClearNative, SM_IntPtr_RetVoid) -DEFINE_CLASS(MNGD_NATIVE_ARRAY_MARSHALER, StubHelpers, MngdNativeArrayMarshaler) -DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CREATE_MARSHALER, CreateMarshaler, SM_IntPtr_IntPtr_Int_Bool_RetVoid) -DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CONVERT_SPACE_TO_NATIVE, ConvertSpaceToNative, SM_IntPtr_RefObj_IntPtr_RetVoid) -DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CONVERT_CONTENTS_TO_NATIVE, ConvertContentsToNative, SM_IntPtr_RefObj_IntPtr_RetVoid) -DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CONVERT_SPACE_TO_MANAGED, ConvertSpaceToManaged, SM_IntPtr_RefObj_IntPtr_Int_RetVoid) -DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CONVERT_CONTENTS_TO_MANAGED, ConvertContentsToManaged, SM_IntPtr_RefObj_IntPtr_RetVoid) -DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CLEAR_NATIVE, ClearNative, SM_IntPtr_IntPtr_Int_RetVoid) -DEFINE_METHOD(MNGD_NATIVE_ARRAY_MARSHALER, CLEAR_NATIVE_CONTENTS, ClearNativeContents, SM_IntPtr_IntPtr_Int_RetVoid) - DEFINE_CLASS(MNGD_REF_CUSTOM_MARSHALER, StubHelpers, MngdRefCustomMarshaler) DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, CONVERT_CONTENTS_TO_NATIVE, ConvertContentsToNative, SM_ICustomMarshaler_RefObj_PtrIntPtr_RetVoid) DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, CONVERT_CONTENTS_TO_MANAGED, ConvertContentsToManaged, SM_ICustomMarshaler_RefObj_PtrIntPtr_RetVoid) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 05baa6a1ec254e..1985da440d6714 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4218,166 +4218,6 @@ void ILNativeArrayMarshaler::EmitNewSavedSizeArgLocal(ILCodeStream* pslILEmit) pslILEmit->EmitSTLOC(m_dwSavedSizeArg); } -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertSpaceToNative(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - - GCX_COOP(); - - if (pManagedHome.Get() == NULL) - { - *pNativeHome = NULL; - } - else - { - SIZE_T cElements = ((BASEARRAYREF)pManagedHome.Get())->GetNumComponents(); - SIZE_T cbElement = OleVariant::GetElementSizeForVarType(pThis->m_vt, pThis->m_pElementMT); - - if (cbElement == 0) - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - - GCX_PREEMP(); - - SIZE_T cbArray; - if ( (!ClrSafeInt::multiply(cElements, cbElement, cbArray)) || cbArray > MAX_SIZE_FOR_INTEROP) - COMPlusThrow(kArgumentException, IDS_EE_STRUCTARRAYTOOLARGE); - - *pNativeHome = CoTaskMemAlloc(cbArray); - - if (*pNativeHome == NULL) - ThrowOutOfMemory(); - - // initialize the array - FillMemory(*pNativeHome, cbArray, 0); - } - - END_QCALL; -} - -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToNative(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - - GCX_COOP(); - - BASEARRAYREF arrayRef = NULL; - GCPROTECT_BEGIN(arrayRef); - arrayRef = (BASEARRAYREF)pManagedHome.Get(); - - if (arrayRef != NULL) - { - const OleVariant::Marshaler* pMarshaler = OleVariant::GetMarshalerForVarType(pThis->m_vt, TRUE); - SIZE_T cElements = arrayRef->GetNumComponents(); - if (pMarshaler == NULL || pMarshaler->ComToOleArray == NULL) - { - if ( (!ClrSafeInt::multiply(cElements, OleVariant::GetElementSizeForVarType(pThis->m_vt, pThis->m_pElementMT), cElements)) || cElements > MAX_SIZE_FOR_INTEROP) - COMPlusThrow(kArgumentException, IDS_EE_STRUCTARRAYTOOLARGE); - - _ASSERTE(!OleVariant::GetTypeHandleForVarType(pThis->m_vt).GetMethodTable()->ContainsGCPointers()); - memcpyNoGCRefs(*pNativeHome, arrayRef->GetDataPtr(), cElements); - } - else - { - pMarshaler->ComToOleArray(&arrayRef, *pNativeHome, pThis->m_pElementMT, pThis->m_BestFitMap, - pThis->m_ThrowOnUnmappableChar, pThis->m_NativeDataValid, cElements); - } - } - - GCPROTECT_END(); - - END_QCALL; -} - -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertSpaceToManaged(MngdNativeArrayMarshaler* pThis, - QCall::ObjectHandleOnStack managedHome, void** pNativeHome, INT32 cElements) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - GCX_COOP(); - - if (*pNativeHome == NULL) - { - managedHome.Set(NULL); - } - else - { - // @todo: lookup this class before marshal time - if (pThis->m_Array.IsNull()) - { - // Get proper array class name & type - pThis->m_Array = OleVariant::GetArrayForVarType(pThis->m_vt, TypeHandle(pThis->m_pElementMT)); - if (pThis->m_Array.IsNull()) - COMPlusThrow(kTypeLoadException); - } - // - // Allocate array - // - managedHome.Set(AllocateSzArray(pThis->m_Array, cElements)); - } - - END_QCALL; -} - -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToManaged(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - - GCX_COOP(); - - BASEARRAYREF arrayRef = NULL; - GCPROTECT_BEGIN(arrayRef); - arrayRef = (BASEARRAYREF)pManagedHome.Get(); - - if (*pNativeHome != NULL) - { - const OleVariant::Marshaler *pMarshaler = OleVariant::GetMarshalerForVarType(pThis->m_vt, TRUE); - - if (pMarshaler == NULL || pMarshaler->OleToComArray == NULL) - { - SIZE_T cElements; - if ( (!ClrSafeInt::multiply(arrayRef->GetNumComponents(), OleVariant::GetElementSizeForVarType(pThis->m_vt, pThis->m_pElementMT), cElements)) || cElements > MAX_SIZE_FOR_INTEROP) - COMPlusThrow(kArgumentException, IDS_EE_STRUCTARRAYTOOLARGE); - - // If we are copying variants, strings, etc, we need to use write barrier - _ASSERTE(!OleVariant::GetTypeHandleForVarType(pThis->m_vt).GetMethodTable()->ContainsGCPointers()); - memcpyNoGCRefs(arrayRef->GetDataPtr(), *pNativeHome, cElements); - } - else - { - pMarshaler->OleToComArray(*pNativeHome, &arrayRef, pThis->m_pElementMT); - } - } - - GCPROTECT_END(); - END_QCALL; -} - -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ClearNativeContents(MngdNativeArrayMarshaler* pThis, void** pNativeHome, INT32 cElements) -{ - QCALL_CONTRACT; - BEGIN_QCALL; - GCX_COOP(); - - if (*pNativeHome != NULL) - { - const OleVariant::Marshaler *pMarshaler = OleVariant::GetMarshalerForVarType(pThis->m_vt, FALSE); - - if (pMarshaler != NULL && pMarshaler->ClearOleArray != NULL) - { - pMarshaler->ClearOleArray(*pNativeHome, cElements, pThis->m_pElementMT); - } - } - - END_QCALL; -} - MethodTable* ILArrayMarshalerBase::GetMarshalerMT() { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index e85a4b18650df8..7f44934c61d22f 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3349,22 +3349,6 @@ class ILNativeArrayMarshaler : public ILArrayMarshalerBase DWORD m_dwSavedSizeArg; }; -struct MngdNativeArrayMarshaler -{ - MethodTable* m_pElementMT; - TypeHandle m_Array; - BOOL m_NativeDataValid; - BOOL m_BestFitMap; - BOOL m_ThrowOnUnmappableChar; - VARTYPE m_vt; -}; - -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertSpaceToNative(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome); -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToNative(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome); -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertSpaceToManaged(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome, INT32 cElements); -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ConvertContentsToManaged(MngdNativeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome); -extern "C" void QCALLTYPE MngdNativeArrayMarshaler_ClearNativeContents(MngdNativeArrayMarshaler* pThis, void** pNativeHome, INT32 cElements); - class ILFixedArrayMarshaler : public ILArrayMarshalerBase { public: diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index eba0fdc56481ff..1692140748aee8 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -394,11 +394,6 @@ static const Entry s_QCall[] = DllImportEntry(MarshalNative_GetEndComSlot) DllImportEntry(MarshalNative_ChangeWrapperHandleStrength) #endif - DllImportEntry(MngdNativeArrayMarshaler_ConvertSpaceToNative) - DllImportEntry(MngdNativeArrayMarshaler_ConvertContentsToNative) - DllImportEntry(MngdNativeArrayMarshaler_ConvertSpaceToManaged) - DllImportEntry(MngdNativeArrayMarshaler_ConvertContentsToManaged) - DllImportEntry(MngdNativeArrayMarshaler_ClearNativeContents) #ifdef FEATURE_COMINTEROP DllImportEntry(MngdSafeArrayMarshaler_CreateMarshaler) DllImportEntry(MngdSafeArrayMarshaler_ConvertSpaceToNative) From a62689c0e9585271a5e5c0d5386bdcdc3e58ff83 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 11:41:54 -0700 Subject: [PATCH 06/41] Remove VTHACK_ VarTypes --- src/coreclr/vm/ilmarshalers.cpp | 65 +++++++++++++++++++++------------ src/coreclr/vm/mlinfo.cpp | 14 ++++--- src/coreclr/vm/mlinfo.h | 13 ++++++- src/coreclr/vm/olevariant.cpp | 59 +----------------------------- src/coreclr/vm/olevariant.h | 17 --------- 5 files changed, 64 insertions(+), 104 deletions(-) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 1985da440d6714..e111f9a8945d48 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -3841,9 +3841,14 @@ void ILMngdMarshaler::EmitCallMngdMarshalerMethod(ILCodeStream* pslILEmit, Metho bool ILNativeArrayMarshaler::CanMarshalViaPinning() { - // We can't pin an array if we have a marshaler for the var type - // or if we can't get a method-table representing the array (how we determine the offset to pin). - return IsCLRToNative(m_dwMarshalFlags) && !IsByref(m_dwMarshalFlags) && (NULL != m_pargs->na.m_pArrayMT) && (NULL == OleVariant::GetMarshalerForVarType(m_pargs->na.m_vt, TRUE)); + // We can't pin an array if we have a non-default element native type (e.g. ANSICHAR, WINBOOL, CBOOL), + // if we have a marshaler for the var type, or if we can't get a method-table representing the array. + CREATE_MARSHALER_CARRAY_OPERANDS mops; + m_pargs->m_pMarshalInfo->GetMops(&mops); + + return IsCLRToNative(m_dwMarshalFlags) && !IsByref(m_dwMarshalFlags) && (NULL != m_pargs->na.m_pArrayMT) + && mops.elementNativeType == NATIVE_TYPE_DEFAULT + && (NULL == OleVariant::GetMarshalerForVarType(m_pargs->na.m_vt, TRUE)); } void ILNativeArrayMarshaler::EmitMarshalViaPinning(ILCodeStream* pslILEmit) @@ -4233,6 +4238,40 @@ MethodTable* ILArrayMarshalerBase::GetMarshalerMT() MethodTable* pBestFitMT = bestFit ? pEnabledMT : pDisabledMT; MethodTable* pThrowOnUnmappableMT = throwOnUnmappable ? pEnabledMT : pDisabledMT; + // Handle non-VARTYPE marshalers identified by CorNativeType. + CorNativeType nt = mops.elementNativeType; + if (nt != NATIVE_TYPE_DEFAULT) + { + switch (nt) + { + case NATIVE_TYPE_BOOLEAN: + { + _ASSERTE(m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle() == TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN))); + TypeHandle thInt32 = CoreLibBinder::GetClass(CLASS__INT32); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thInt32, 1)).AsMethodTable(); + } + + case NATIVE_TYPE_I1: + { + _ASSERTE(m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle() == TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN))); + TypeHandle thByte = CoreLibBinder::GetClass(CLASS__BYTE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thByte, 1)).AsMethodTable(); + } + + case NATIVE_TYPE_U1: + { + _ASSERTE(m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle() == TypeHandle(CoreLibBinder::GetClass(CLASS__CHAR))); + TypeHandle thArgs[2] = { TypeHandle(pBestFitMT), TypeHandle(pThrowOnUnmappableMT) }; + return TypeHandle(CoreLibBinder::GetClass(CLASS__ANSICHAR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + } + + default: + _ASSERTE(!"Unsupported CorNativeType for ILArrayMarshalerBase"); + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + return NULL; + } + } + switch (vt) { case VT_I1: @@ -4307,27 +4346,9 @@ MethodTable* ILArrayMarshalerBase::GetMarshalerMT() case VT_BOOL: return CoreLibBinder::GetClass(CLASS__VARIANT_BOOL_MARSHALER); - case VTHACK_WINBOOL: - { - TypeHandle thInt32 = CoreLibBinder::GetClass(CLASS__INT32); - return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thInt32, 1)).AsMethodTable(); - } - - case VTHACK_CBOOL: - { - TypeHandle thByte = CoreLibBinder::GetClass(CLASS__BYTE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thByte, 1)).AsMethodTable(); - } - case VT_DATE: return CoreLibBinder::GetClass(CLASS__DATEMARSHALER); - case VTHACK_ANSICHAR: - { - TypeHandle thArgs[2] = { TypeHandle(pBestFitMT), TypeHandle(pThrowOnUnmappableMT) }; - return TypeHandle(CoreLibBinder::GetClass(CLASS__ANSICHAR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); - } - case VT_LPWSTR: return CoreLibBinder::GetClass(CLASS__LPWSTR_MARSHALER); @@ -4363,8 +4384,6 @@ MethodTable* ILArrayMarshalerBase::GetMarshalerMT() return CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); #endif // FEATURE_COMINTEROP - case VTHACK_NONBLITTABLERECORD: - case VTHACK_BLITTABLERECORD: case VT_RECORD: { TypeHandle thElement(mops.methodTable); diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index db04397c72dd49..d74f843d22f725 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -2190,6 +2190,7 @@ HRESULT MarshalInfo::HandleArrayElemType(NativeTypeParamInfo *pParamInfo, TypeHa // Set the array type handle and VARTYPE to use for marshalling. m_hndArrayElemType = arrayMarshalInfo.GetElementTypeHandle(); m_arrayElementType = arrayMarshalInfo.GetElementVT(); + m_arrayElementNativeType = arrayMarshalInfo.GetElementNativeType(); if (m_type == MARSHAL_TYPE_NATIVEARRAY || m_type == MARSHAL_TYPE_FIXED_ARRAY) { @@ -3321,7 +3322,7 @@ void ArrayMarshalInfo::InitElementInfo(CorNativeType arrayNativeType, MarshalInf { case NATIVE_TYPE_I1: //fallthru case NATIVE_TYPE_U1: - m_vtElement = VTHACK_ANSICHAR; + m_ntElement = NATIVE_TYPE_U1; break; case NATIVE_TYPE_I2: //fallthru @@ -3337,7 +3338,10 @@ void ArrayMarshalInfo::InitElementInfo(CorNativeType arrayNativeType, MarshalInf m_vtElement = VT_UI2; else #endif // FEATURE_COMINTEROP - m_vtElement = isAnsi ? VTHACK_ANSICHAR : VT_UI2; + if (isAnsi) + m_ntElement = NATIVE_TYPE_U1; + else + m_vtElement = VT_UI2; } } else if (etElement == ELEMENT_TYPE_BOOLEAN) @@ -3345,7 +3349,7 @@ void ArrayMarshalInfo::InitElementInfo(CorNativeType arrayNativeType, MarshalInf switch (ntElement) { case NATIVE_TYPE_BOOLEAN: - m_vtElement = VTHACK_WINBOOL; + m_ntElement = NATIVE_TYPE_BOOLEAN; break; #ifdef FEATURE_COMINTEROP @@ -3356,7 +3360,7 @@ void ArrayMarshalInfo::InitElementInfo(CorNativeType arrayNativeType, MarshalInf case NATIVE_TYPE_I1 : case NATIVE_TYPE_U1 : - m_vtElement = VTHACK_CBOOL; + m_ntElement = NATIVE_TYPE_I1; break; // Compat: if the native type doesn't make sense, we need to ignore it and not report an error. @@ -3371,7 +3375,7 @@ void ArrayMarshalInfo::InitElementInfo(CorNativeType arrayNativeType, MarshalInf else #endif // FEATURE_COMINTEROP { - m_vtElement = VTHACK_WINBOOL; + m_ntElement = NATIVE_TYPE_BOOLEAN; } break; } diff --git a/src/coreclr/vm/mlinfo.h b/src/coreclr/vm/mlinfo.h index c56380d58cb789..496856ec49a798 100644 --- a/src/coreclr/vm/mlinfo.h +++ b/src/coreclr/vm/mlinfo.h @@ -65,6 +65,7 @@ struct CREATE_MARSHALER_CARRAY_OPERANDS UINT32 multiplier; UINT32 additive; VARTYPE elementType; + CorNativeType elementNativeType; UINT16 countParamIdx; BYTE bestfitmapping; BYTE throwonunmappablechar; @@ -398,6 +399,7 @@ class MarshalInfo WRAPPER_NO_CONTRACT; pMopsOut->methodTable = m_hndArrayElemType.AsMethodTable(); pMopsOut->elementType = m_arrayElementType; + pMopsOut->elementNativeType = m_arrayElementNativeType; pMopsOut->countParamIdx = m_countParamIdx; pMopsOut->multiplier = m_multiplier; pMopsOut->additive = m_additive; @@ -480,6 +482,7 @@ class MarshalInfo MethodDesc* m_pMD; // Save MethodDesc for later inspection so that we can pass SizeParamIndex by ref TypeHandle m_hndArrayElemType; VARTYPE m_arrayElementType; + CorNativeType m_arrayElementNativeType; int m_iArrayRank; BOOL m_nolowerbounds; // if managed type is SZARRAY, don't allow lower bounds @@ -545,6 +548,7 @@ class ArrayMarshalInfo public: ArrayMarshalInfo(ArrayMarshalInfoFlags flags) : m_vtElement(VT_EMPTY) + , m_ntElement(NATIVE_TYPE_DEFAULT) , m_errorResourceId(0) , m_flags(flags) #ifdef FEATURE_COMINTEROP @@ -592,6 +596,12 @@ class ArrayMarshalInfo } } + CorNativeType GetElementNativeType() + { + LIMITED_METHOD_CONTRACT; + return m_ntElement; + } + BOOL IsValid() { CONTRACTL @@ -602,7 +612,7 @@ class ArrayMarshalInfo } CONTRACTL_END; - return m_vtElement != VT_EMPTY; + return m_vtElement != VT_EMPTY || m_ntElement != NATIVE_TYPE_DEFAULT; } BOOL IsSafeArraySubTypeExplicitlySpecified() @@ -654,6 +664,7 @@ class ArrayMarshalInfo TypeHandle m_thElement; TypeHandle m_thInterfaceArrayElementClass; VARTYPE m_vtElement; + CorNativeType m_ntElement; DWORD m_errorResourceId; ArrayMarshalInfoFlags m_flags; diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 26fe57e76d8be7..d128d61f4c223a 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -366,15 +366,9 @@ TypeHandle OleVariant::GetArrayForVarType(VARTYPE vt, TypeHandle elemType, unsig switch (vt) { case VT_BOOL: - case VTHACK_WINBOOL: - case VTHACK_CBOOL: baseElement = ELEMENT_TYPE_BOOLEAN; break; - case VTHACK_ANSICHAR: - baseElement = ELEMENT_TYPE_CHAR; - break; - case VT_UI1: baseElement = ELEMENT_TYPE_U1; break; @@ -621,26 +615,12 @@ UINT OleVariant::GetElementSizeForVarType(VARTYPE vt, MethodTable *pInterfaceMT) sizeof(LPWSTR), // VT_LPWSTR }; - // Special cases - switch (vt) - { - case VTHACK_WINBOOL: - return sizeof(BOOL); - break; - case VTHACK_ANSICHAR: - return GetMaxDBCSCharByteSize(); // Multi byte characters. - break; - case VTHACK_CBOOL: - return sizeof(BYTE); - default: - break; - } // VT_ARRAY indicates a safe array which is always sizeof(SAFEARRAY *). if (vt & VT_ARRAY) return sizeof(SAFEARRAY*); - if (vt == VTHACK_NONBLITTABLERECORD || vt == VTHACK_BLITTABLERECORD || vt == VT_RECORD) + if (vt == VT_RECORD) { _ASSERTE(pInterfaceMT != NULL); return pInterfaceMT->GetNativeSize(); @@ -677,12 +657,8 @@ MethodTable* OleVariant::GetNativeMethodTableForVarType(VARTYPE vt, MethodTable* return CoreLibBinder::GetClass(CLASS__DOUBLE); case VT_CY: return CoreLibBinder::GetClass(CLASS__CURRENCY); - case VTHACK_WINBOOL: - return CoreLibBinder::GetClass(CLASS__INT32); case VT_BOOL: return CoreLibBinder::GetClass(CLASS__INT16); - case VTHACK_CBOOL: - return CoreLibBinder::GetClass(CLASS__BYTE); case VT_DISPATCH: case VT_UNKNOWN: case VT_LPSTR: @@ -694,8 +670,6 @@ MethodTable* OleVariant::GetNativeMethodTableForVarType(VARTYPE vt, MethodTable* return CoreLibBinder::GetClass(CLASS__INTPTR); case VT_VARIANT: return CoreLibBinder::GetClass(CLASS__COMVARIANT); - case VTHACK_ANSICHAR: - return CoreLibBinder::GetClass(CLASS__BYTE); case VT_UI2: // When CharSet = CharSet.Unicode, System.Char arrays are marshaled as VT_UI2. // However, since System.Char itself is CharSet.Ansi, the native size of @@ -798,37 +772,6 @@ const OleVariant::Marshaler *OleVariant::GetMarshalerForVarType(VARTYPE vt, BOOL #endif // FEATURE_COMINTEROP - case VTHACK_NONBLITTABLERECORD: - RETURN_MARSHALER( - MarshalNonBlittableRecordArrayOleToCom, - MarshalNonBlittableRecordArrayComToOle, - ClearNonBlittableRecordArray - ); - - case VTHACK_BLITTABLERECORD: - RETURN NULL; // Requires no marshaling - - case VTHACK_WINBOOL: - RETURN_MARSHALER( - MarshalWinBoolArrayOleToCom, - MarshalWinBoolArrayComToOle, - NULL - ); - - case VTHACK_CBOOL: - RETURN_MARSHALER( - MarshalCBoolArrayOleToCom, - MarshalCBoolArrayComToOle, - NULL - ); - - case VTHACK_ANSICHAR: - RETURN_MARSHALER( - MarshalAnsiCharArrayOleToCom, - MarshalAnsiCharArrayComToOle, - NULL - ); - case VT_LPSTR: RETURN_MARSHALER( MarshalLPSTRArrayOleToCom, diff --git a/src/coreclr/vm/olevariant.h b/src/coreclr/vm/olevariant.h index dec0cecb488400..35d6505da7ceeb 100644 --- a/src/coreclr/vm/olevariant.h +++ b/src/coreclr/vm/olevariant.h @@ -4,26 +4,9 @@ // File: OleVariant.h // -// - - #ifndef _H_OLEVARIANT_ #define _H_OLEVARIANT_ - -// The COM interop native array marshaler is built on top of VT_* types. -// The P/Invoke marshaler supports marshaling to WINBOOL's and ANSICHAR's. -// This is an annoying workaround to shoehorn these non-OleAut types into -// the COM interop marshaler. -#define VTHACK_INSPECTABLE 247 -#define VTHACK_HSTRING 248 -#define VTHACK_CBOOL 250 -#define VTHACK_NONBLITTABLERECORD 251 -#define VTHACK_BLITTABLERECORD 252 -#define VTHACK_ANSICHAR 253 -#define VTHACK_WINBOOL 254 - - class OleVariant { public: From a1d7326b1d3560c7dd2925a5f00e6b4815bb1b2a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 17:21:20 -0700 Subject: [PATCH 07/41] Convert SafeArray marshalling to use the managed array marshalling logic --- .../src/System/StubHelpers.cs | 19 +- src/coreclr/vm/corelib.h | 4 +- src/coreclr/vm/dispatchinfo.cpp | 3 +- src/coreclr/vm/dispparammarshaler.cpp | 31 +- src/coreclr/vm/dispparammarshaler.h | 9 +- src/coreclr/vm/ilmarshalers.cpp | 22 +- src/coreclr/vm/ilmarshalers.h | 4 +- src/coreclr/vm/metasig.h | 1 + src/coreclr/vm/olevariant.cpp | 312 +++++++++++++----- src/coreclr/vm/olevariant.h | 9 +- 10 files changed, 311 insertions(+), 103 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 96007d54f5e36e..568227ff2fe7d4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -677,7 +677,7 @@ internal static partial class MngdSafeArrayMarshaler [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MngdSafeArrayMarshaler_CreateMarshaler")] [SuppressGCTransition] - internal static partial void CreateMarshaler(IntPtr pMarshalState, IntPtr pMT, int iRank, int dwFlags); + internal static partial void CreateMarshaler(IntPtr pMarshalState, IntPtr pMT, int iRank, int dwFlags, IntPtr pConvertToNative, IntPtr pConvertToManaged); internal static void ConvertSpaceToNative(IntPtr pMarshalState, in object pManagedHome, IntPtr pNativeHome) { @@ -2271,6 +2271,23 @@ public static unsafe void ConvertArrayContentsToManaged(T[] manag } } + [UnmanagedCallersOnly] + internal static unsafe void InvokeArrayContentsConverter( + Array* pManagedArray, + byte* pNative, + delegate* pConvertMethod, + Exception* pException) + { + try + { + pConvertMethod(*pManagedArray, pNative); + } + catch (Exception ex) + { + *pException = ex; + } + } + internal static unsafe void FreeArrayContents(byte* pNative, int length) where TMarshaler : IArrayElementMarshaler { for (int i = 0; i < length; i++) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 7ae5316375b062..be964015bb41a0 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1082,6 +1082,7 @@ DEFINE_METHOD(STUBHELPERS, FREE_ARRAY_CONTENTS, FreeArr DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_NATIVE, ConvertArraySpaceToNative, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_MANAGED, ConvertArraySpaceToManaged, NoSig) DEFINE_METHOD(STUBHELPERS, CLEAR_ARRAY_NATIVE, ClearArrayNative, NoSig) +DEFINE_METHOD(STUBHELPERS, INVOKE_ARRAY_CONTENTS_CONVERTER, InvokeArrayContentsConverter, NoSig) DEFINE_METHOD(STUBHELPERS, THROW_WRONG_SIZE_ARRAY_IN_NSTRUCT, ThrowWrongSizeArrayInNativeStruct, SM_RetVoid) #ifdef FEATURE_COMINTEROP @@ -1169,7 +1170,7 @@ DEFINE_METHOD(INTERFACEMARSHALER, VALIDATE_COM_VISIBILITY_FOR_IUNKNOWN, Valida DEFINE_CLASS(MNGD_SAFE_ARRAY_MARSHALER, StubHelpers, MngdSafeArrayMarshaler) -DEFINE_METHOD(MNGD_SAFE_ARRAY_MARSHALER, CREATE_MARSHALER, CreateMarshaler, SM_IntPtr_IntPtr_Int_Int_RetVoid) +DEFINE_METHOD(MNGD_SAFE_ARRAY_MARSHALER, CREATE_MARSHALER, CreateMarshaler, SM_IntPtr_IntPtr_Int_Int_IntPtr_IntPtr_RetVoid) DEFINE_METHOD(MNGD_SAFE_ARRAY_MARSHALER, CONVERT_SPACE_TO_NATIVE, ConvertSpaceToNative, SM_IntPtr_RefObj_IntPtr_RetVoid) DEFINE_METHOD(MNGD_SAFE_ARRAY_MARSHALER, CONVERT_CONTENTS_TO_NATIVE, ConvertContentsToNative, SM_IntPtr_RefObj_IntPtr_Obj_RetVoid) DEFINE_METHOD(MNGD_SAFE_ARRAY_MARSHALER, CONVERT_SPACE_TO_MANAGED, ConvertSpaceToManaged, SM_IntPtr_RefObj_IntPtr_RetVoid) @@ -1250,6 +1251,7 @@ DEFINE_CLASS(CURRENCY_ARRAY_ELEMENT_MARSHALER, StubHelpers, CurrencyArrayElemen DEFINE_CLASS(BSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, BSTRArrayElementMarshaler) DEFINE_CLASS(INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, InterfaceArrayElementMarshaler`1) DEFINE_CLASS(TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, TypedInterfaceArrayElementMarshaler`1) +DEFINE_CLASS(HETEROGENEOUS_INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, HeterogeneousInterfaceArrayElementMarshaler) DEFINE_CLASS(VARIANT_ARRAY_ELEMENT_MARSHALER, StubHelpers, VariantArrayElementMarshaler) #endif // FEATURE_COMINTEROP DEFINE_CLASS(MARSHALER_OPTION_ENABLED, StubHelpers, IMarshalerOption+EnabledOption) diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index 21ecf93f664a45..9532cde13d84d7 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -2154,7 +2154,8 @@ void DispatchInfo::MarshalParamManagedToNativeRef(DispatchMemberInfo *pMemberInf MethodTable *pElementMT = (*(BASEARRAYREF *)pSrcObj)->GetArrayElementTypeHandle().GetMethodTable(); // Convert the contents of the managed array into the original SAFEARRAY. - OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF *)pSrcObj, *V_ARRAYREF(pRefVar), ElementVt, pElementMT); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ElementVt, pElementMT, FALSE); + OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF *)pSrcObj, *V_ARRAYREF(pRefVar), ElementVt, pElementMT, pConvertMD->GetMultiCallableAddrOfCode()); } else { diff --git a/src/coreclr/vm/dispparammarshaler.cpp b/src/coreclr/vm/dispparammarshaler.cpp index b7d7923d86e88a..58f3aed5b47d8e 100644 --- a/src/coreclr/vm/dispparammarshaler.cpp +++ b/src/coreclr/vm/dispparammarshaler.cpp @@ -212,6 +212,27 @@ void DispParamInterfaceMarshaler::MarshalManagedToNative(OBJECTREF *pSrcObj, VAR V_VT(pDestVar) = static_cast(m_bDispatch ? VT_DISPATCH : VT_UNKNOWN); } +DispParamArrayMarshaler::DispParamArrayMarshaler(VARTYPE ElementVT, MethodTable *pElementMT) : + m_ElementVT(ElementVT), + m_pElementMT(pElementMT), + m_pConvertContentsToManagedCode(NULL), + m_pConvertContentsToUnmanagedCode(NULL) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (ElementVT != VT_EMPTY && pElementMT != NULL) + { + m_pConvertContentsToManagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + m_pConvertContentsToUnmanagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + } +} + void DispParamArrayMarshaler::MarshalNativeToManaged(VARIANT *pSrcVar, OBJECTREF *pDestObj) { CONTRACTL @@ -251,7 +272,10 @@ void DispParamArrayMarshaler::MarshalNativeToManaged(VARIANT *pSrcVar, OBJECTREF *(BASEARRAYREF*)pDestObj = OleVariant::CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); // Convert the contents of the SAFEARRAY. - OleVariant::MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF*)pDestObj, vt, pElemMT); + PCODE pConvertCode = m_pConvertContentsToManagedCode; + if (pConvertCode == NULL) + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + OleVariant::MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF*)pDestObj, vt, pElemMT, pConvertCode); } void DispParamArrayMarshaler::MarshalManagedToNative(OBJECTREF *pSrcObj, VARIANT *pDestVar) @@ -290,7 +314,10 @@ void DispParamArrayMarshaler::MarshalManagedToNative(OBJECTREF *pSrcObj, VARIANT _ASSERTE(pSafeArray); // Marshal the contents of the SAFEARRAY. - OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF*)pSrcObj, pSafeArray, vt, pElemMT); + PCODE pConvertCode = m_pConvertContentsToUnmanagedCode; + if (pConvertCode == NULL) + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF*)pSrcObj, pSafeArray, vt, pElemMT, pConvertCode); } // Store the resulting SAFEARRAY in the destination VARIANT. diff --git a/src/coreclr/vm/dispparammarshaler.h b/src/coreclr/vm/dispparammarshaler.h index 51d4a359da420e..3249598f482622 100644 --- a/src/coreclr/vm/dispparammarshaler.h +++ b/src/coreclr/vm/dispparammarshaler.h @@ -128,12 +128,7 @@ class DispParamInterfaceMarshaler : public DispParamMarshaler class DispParamArrayMarshaler : public DispParamMarshaler { public: - DispParamArrayMarshaler(VARTYPE ElementVT, MethodTable *pElementMT) : - m_ElementVT(ElementVT), - m_pElementMT(pElementMT) - { - WRAPPER_NO_CONTRACT; - } + DispParamArrayMarshaler(VARTYPE ElementVT, MethodTable *pElementMT); virtual ~DispParamArrayMarshaler() { @@ -147,6 +142,8 @@ class DispParamArrayMarshaler : public DispParamMarshaler private: VARTYPE m_ElementVT; MethodTable* m_pElementMT; + PCODE m_pConvertContentsToManagedCode; + PCODE m_pConvertContentsToUnmanagedCode; }; diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index e111f9a8945d48..054ab68c39edc6 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4541,7 +4541,19 @@ void ILSafeArrayMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) pslILEmit->EmitLDC(m_pargs->m_pMarshalInfo->GetArrayRank()); pslILEmit->EmitLDC(dwFlags); - pslILEmit->EmitCALL(METHOD__MNGD_SAFE_ARRAY_MARSHALER__CREATE_MARSHALER, 4, 0); + // Resolve the instantiated content conversion methods at stub generation time + // and emit ldftn to pass their entry points to CreateMarshaler. + MethodDesc* pConvertToNativeMD = GetInstantiatedSafeArrayMethod( + METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, + mops.elementType, mops.methodTable, FALSE); + pslILEmit->EmitLDFTN(pslILEmit->GetToken(pConvertToNativeMD)); + + MethodDesc* pConvertToManagedMD = GetInstantiatedSafeArrayMethod( + METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, + mops.elementType, mops.methodTable, FALSE); + pslILEmit->EmitLDFTN(pslILEmit->GetToken(pConvertToManagedMD)); + + pslILEmit->EmitCALL(METHOD__MNGD_SAFE_ARRAY_MARSHALER__CREATE_MARSHALER, 6, 0); } void ILSafeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) @@ -4578,7 +4590,7 @@ void ILSafeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmi pslILEmit->EmitCALL(METHOD__MNGD_SAFE_ARRAY_MARSHALER__CONVERT_CONTENTS_TO_NATIVE, 4, 0); } -extern "C" void QCALLTYPE MngdSafeArrayMarshaler_CreateMarshaler(MngdSafeArrayMarshaler* pThis, MethodTable* pMT, UINT32 iRank, UINT32 dwFlags) +extern "C" void QCALLTYPE MngdSafeArrayMarshaler_CreateMarshaler(MngdSafeArrayMarshaler* pThis, MethodTable* pMT, UINT32 iRank, UINT32 dwFlags, PCODE pConvertToNative, PCODE pConvertToManaged) { QCALL_CONTRACT_NO_GC_TRANSITION; @@ -4587,6 +4599,8 @@ extern "C" void QCALLTYPE MngdSafeArrayMarshaler_CreateMarshaler(MngdSafeArrayMa pThis->m_vt = (VARTYPE)dwFlags; pThis->m_fStatic = (BYTE)(dwFlags >> 16); pThis->m_nolowerbounds = (BYTE)(dwFlags >> 24); + pThis->m_pConvertContentsToNativeCode = pConvertToNative; + pThis->m_pConvertContentsToManagedCode = pConvertToManaged; } extern "C" void QCALLTYPE MngdSafeArrayMarshaler_ConvertSpaceToNative(MngdSafeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome) @@ -4660,6 +4674,7 @@ extern "C" void QCALLTYPE MngdSafeArrayMarshaler_ConvertContentsToNative(MngdSaf (SAFEARRAY*)*pNativeHome, pThis->m_vt, pThis->m_pElementMT, + pThis->m_pConvertContentsToNativeCode, (pThis->m_fStatic & MngdSafeArrayMarshaler::SCSF_NativeDataValid)); } @@ -4753,7 +4768,8 @@ extern "C" void QCALLTYPE MngdSafeArrayMarshaler_ConvertContentsToManaged(MngdSa OleVariant::MarshalArrayRefForSafeArray(pNative, &arrayRef, pThis->m_vt, - pThis->m_pElementMT); + pThis->m_pElementMT, + pThis->m_pConvertContentsToManagedCode); } GCPROTECT_END(); diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index 7f44934c61d22f..77a908be34bc67 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3455,9 +3455,11 @@ class MngdSafeArrayMarshaler VARTYPE m_vt; BYTE m_fStatic; // StaticCheckStateFlags BYTE m_nolowerbounds; + PCODE m_pConvertContentsToNativeCode; + PCODE m_pConvertContentsToManagedCode; }; -extern "C" void QCALLTYPE MngdSafeArrayMarshaler_CreateMarshaler(MngdSafeArrayMarshaler* pThis, MethodTable* pMT, UINT32 iRank, UINT32 dwFlags); +extern "C" void QCALLTYPE MngdSafeArrayMarshaler_CreateMarshaler(MngdSafeArrayMarshaler* pThis, MethodTable* pMT, UINT32 iRank, UINT32 dwFlags, PCODE pConvertToNative, PCODE pConvertToManaged); extern "C" void QCALLTYPE MngdSafeArrayMarshaler_ConvertSpaceToNative(MngdSafeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome); extern "C" void QCALLTYPE MngdSafeArrayMarshaler_ConvertContentsToNative(MngdSafeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome, QCall::ObjectHandleOnStack pOriginalManaged); extern "C" void QCALLTYPE MngdSafeArrayMarshaler_ConvertSpaceToManaged(MngdSafeArrayMarshaler* pThis, QCall::ObjectHandleOnStack pManagedHome, void** pNativeHome); diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 464dd8c544180d..49136080ec8db2 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -221,6 +221,7 @@ DEFINE_METASIG_T(SM(PtrPropertyInfo_PtrException_RetInt, P(C(PROPERTY_INFO)) P(C DEFINE_METASIG(SM(IntPtr_RefObj_IntPtr_RetVoid, I r(j) I, v)) DEFINE_METASIG(SM(IntPtr_RefObj_IntPtr_Int_RetVoid, I r(j) I i,v)) DEFINE_METASIG(SM(IntPtr_IntPtr_Int_Int_RetVoid, I I i i, v)) +DEFINE_METASIG(SM(IntPtr_IntPtr_Int_Int_IntPtr_IntPtr_RetVoid, I I i i I I, v)) DEFINE_METASIG(SM(IntPtr_RefObj_IntPtr_Obj_RetVoid, I r(j) I j, v)) DEFINE_METASIG(SM(Obj_Int_RetVoid, j i,v)) DEFINE_METASIG(SM(PtrVoid_Obj_RetObj, P(v) j, j)) diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index d128d61f4c223a..a6d715b5432ac1 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -3131,7 +3131,8 @@ void OleVariant::MarshalArrayVariantOleToObject(const VARIANT* pOleVariant, BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); SetObjectReference(pObj, pArrayRef); - MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); + MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); } else { @@ -3168,7 +3169,8 @@ void OleVariant::MarshalArrayVariantObjectToOle(OBJECTREF * const & pObj, if (*pArrayRef != NULL) { pSafeArray = CreateSafeArrayForArrayRef(pArrayRef, vt, pElemMT); - MarshalSafeArrayForArrayRef(pArrayRef, pSafeArray, vt, pElemMT); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE); + MarshalSafeArrayForArrayRef(pArrayRef, pSafeArray, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); } V_ARRAY(pOleVariant) = pSafeArray; pSafeArray.SuppressRelease(); @@ -3200,7 +3202,8 @@ void OleVariant::MarshalArrayVariantOleRefToObject(const VARIANT *pOleVariant, BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); SetObjectReference(pObj, pArrayRef); - MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); + MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); } else { @@ -3467,6 +3470,201 @@ BASEARRAYREF OleVariant::CreateArrayRefForSafeArray(SAFEARRAY *pSafeArray, VARTY * Safearray marshaling * ------------------------------------------------------------------------- */ +namespace +{ + // Returns the managed IArrayElementMarshaler MethodTable for a given VARTYPE. + // This mirrors the logic in ILArrayMarshalerBase::GetMarshalerMT for SAFEARRAY-compatible types. + MethodTable* GetMarshalerMTForSafeArrayVarType(VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous) + { + STANDARD_VM_CONTRACT; + + switch (vt) + { + case VT_I1: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__SBYTE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_UI1: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__BYTE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_I2: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__INT16); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_UI2: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT16); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_I4: + case VT_INT: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__INT32); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_UI4: + case VT_UINT: + case VT_ERROR: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT32); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_I8: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__INT64); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_UI8: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT64); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_R4: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__SINGLE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_R8: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__DOUBLE); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_DECIMAL: + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__DECIMAL); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } + case VT_BOOL: + return CoreLibBinder::GetClass(CLASS__VARIANT_BOOL_MARSHALER); + + case VT_DATE: + return CoreLibBinder::GetClass(CLASS__DATEMARSHALER); + + case VT_LPWSTR: + return CoreLibBinder::GetClass(CLASS__LPWSTR_MARSHALER); + + case VT_LPSTR: + { + // SAFEARRAY LPSTR marshalling always uses default best-fit/throw-on-unmappable. + MethodTable* pDisabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); + TypeHandle thArgs[2] = { TypeHandle(pDisabledMT), TypeHandle(pDisabledMT) }; + return TypeHandle(CoreLibBinder::GetClass(CLASS__LPSTR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + } + +#ifdef FEATURE_COMINTEROP + case VT_CY: + return CoreLibBinder::GetClass(CLASS__CURRENCY_ARRAY_ELEMENT_MARSHALER); + + case VT_BSTR: + return CoreLibBinder::GetClass(CLASS__BSTR_ARRAY_ELEMENT_MARSHALER); + + case VT_UNKNOWN: + case VT_DISPATCH: + { + if (pElementMT == NULL || pElementMT == g_pObjectClass) + { + if (bHeterogeneous) + { + return CoreLibBinder::GetClass(CLASS__HETEROGENEOUS_INTERFACE_ARRAY_ELEMENT_MARSHALER); + } + MethodTable* pEnabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_ENABLED); + MethodTable* pDisabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); + TypeHandle thDispatch(vt == VT_DISPATCH ? pEnabledMT : pDisabledMT); + return TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); + } + else + { + TypeHandle thElement(pElementMT); + return TypeHandle(CoreLibBinder::GetClass(CLASS__TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + } + + case VT_VARIANT: + return CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); +#endif // FEATURE_COMINTEROP + + case VT_RECORD: + { + _ASSERTE(pElementMT != NULL); + TypeHandle thElement(pElementMT); + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + + default: + _ASSERTE(!"Unsupported VT for SafeArray marshaler"); + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + return NULL; + } + } + + // Returns the element TypeHandle for a given VARTYPE and element MethodTable. + TypeHandle GetElementTypeForSafeArrayVarType(VARTYPE vt, MethodTable* pElementMT) + { + STANDARD_VM_CONTRACT; + + switch (vt) + { + case VT_BOOL: return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN)); + case VT_I1: return TypeHandle(CoreLibBinder::GetClass(CLASS__SBYTE)); + case VT_UI1: return TypeHandle(CoreLibBinder::GetClass(CLASS__BYTE)); + case VT_I2: return TypeHandle(CoreLibBinder::GetClass(CLASS__INT16)); + case VT_UI2: return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT16)); + case VT_I4: + case VT_INT: return TypeHandle(CoreLibBinder::GetClass(CLASS__INT32)); + case VT_UI4: + case VT_UINT: + case VT_ERROR: return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT32)); + case VT_I8: return TypeHandle(CoreLibBinder::GetClass(CLASS__INT64)); + case VT_UI8: return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT64)); + case VT_R4: return TypeHandle(CoreLibBinder::GetClass(CLASS__SINGLE)); + case VT_R8: return TypeHandle(CoreLibBinder::GetClass(CLASS__DOUBLE)); + case VT_DECIMAL: return TypeHandle(CoreLibBinder::GetClass(CLASS__DECIMAL)); + case VT_DATE: return TypeHandle(CoreLibBinder::GetClass(CLASS__DATE_TIME)); + case VT_BSTR: + case VT_LPWSTR: + case VT_LPSTR: return TypeHandle(g_pStringClass); +#ifdef FEATURE_COMINTEROP + case VT_CY: return TypeHandle(CoreLibBinder::GetClass(CLASS__DECIMAL)); + case VT_VARIANT: return TypeHandle(g_pObjectClass); + case VT_UNKNOWN: + case VT_DISPATCH: + if (pElementMT == NULL || pElementMT == g_pObjectClass) + return TypeHandle(g_pObjectClass); + return TypeHandle(pElementMT); +#endif // FEATURE_COMINTEROP + case VT_RECORD: + _ASSERTE(pElementMT != NULL); + return TypeHandle(pElementMT); + default: + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + return TypeHandle(); + } + } +} + +MethodDesc* GetInstantiatedSafeArrayMethod(BinderMethodID methodId, VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pGenericMD = CoreLibBinder::GetMethod(methodId); + + TypeHandle thElementType = GetElementTypeForSafeArrayVarType(vt, pElementMT); + TypeHandle thMarshalerType(GetMarshalerMTForSafeArrayVarType(vt, pElementMT, bHeterogeneous)); + + TypeHandle thArgs[2] = { thElementType, thMarshalerType }; + + return MethodDesc::FindOrCreateAssociatedMethodDesc( + pGenericMD, + pGenericMD->GetMethodTable(), + FALSE, + Instantiation(thArgs, 2), + FALSE); +} + // // MarshalSafeArrayForArrayRef marshals the contents of the array ref into the given // safe array. It is assumed that the type & dimensions of the arrays are compatible. @@ -3475,6 +3673,7 @@ void OleVariant::MarshalSafeArrayForArrayRef(BASEARRAYREF *pArrayRef, SAFEARRAY *pSafeArray, VARTYPE vt, MethodTable *pInterfaceMT, + PCODE pConvertContentsCode, BOOL fSafeArrayIsValid /*= TRUE*/) { CONTRACTL @@ -3498,12 +3697,9 @@ void OleVariant::MarshalSafeArrayForArrayRef(BASEARRAYREF *pArrayRef, GCPROTECT_BEGIN(Array) { - // Retrieve the marshaler to use to convert the contents. - const Marshaler *marshal = GetMarshalerForVarType(vt, TRUE); - // If the array is an array of wrappers, then we need to extract the objects // being wrapped and create an array of those. - BOOL bArrayOfInterfaceWrappers; + BOOL bArrayOfInterfaceWrappers = FALSE; if (IsArrayOfWrappers(pArrayRef, &bArrayOfInterfaceWrappers)) { Array = ExtractWrappedObjectsFromArray(pArrayRef); @@ -3513,41 +3709,15 @@ void OleVariant::MarshalSafeArrayForArrayRef(BASEARRAYREF *pArrayRef, Array = *pArrayRef; } - if (marshal == NULL || marshal->ComToOleArray == NULL) + // Use managed IArrayElementMarshaler implementations for content conversion. + UnmanagedCallersOnlyCaller invoker(METHOD__STUBHELPERS__INVOKE_ARRAY_CONTENTS_CONVERTER); + invoker.InvokeThrowing(&Array, pSafeArray->pvData, (void*)pConvertContentsCode); + + if (pSafeArray->cDims != 1) { - if (pSafeArray->cDims == 1) - { - // If the array is single dimensionnal then we can simply copy it over. - memcpyNoGCRefs(pSafeArray->pvData, Array->GetDataPtr(), dwNumComponents * dwComponentSize); - } - else - { - // Copy and transpose the data. - TransposeArrayData((BYTE*)pSafeArray->pvData, Array->GetDataPtr(), dwNumComponents, dwComponentSize, pSafeArray, FALSE); - } + // The array is multidimensional - transpose the data in place. + TransposeArrayData((BYTE*)pSafeArray->pvData, (BYTE*)pSafeArray->pvData, dwNumComponents, dwComponentSize, pSafeArray, FALSE); } - else - { - { - PinningHandleHolder handle = GetAppDomain()->CreatePinningHandle((OBJECTREF)Array); - - if (bArrayOfInterfaceWrappers) - { - _ASSERTE(vt == VT_UNKNOWN || vt == VT_DISPATCH); - // Signal to code:OleVariant::MarshalInterfaceArrayComToOleHelper that this was an array - // of UnknownWrapper or DispatchWrapper. It shall use a different logic and marshal each - // element according to its specific default interface. - pInterfaceMT = NULL; - } - marshal->ComToOleArray(&Array, pSafeArray->pvData, pInterfaceMT, TRUE, FALSE, fSafeArrayIsValid, dwNumComponents); - } - - if (pSafeArray->cDims != 1) - { - // The array is multidimensionnal we need to transpose it. - TransposeArrayData((BYTE*)pSafeArray->pvData, (BYTE*)pSafeArray->pvData, dwNumComponents, dwComponentSize, pSafeArray, FALSE); - } - } } GCPROTECT_END(); } @@ -3560,7 +3730,8 @@ void OleVariant::MarshalSafeArrayForArrayRef(BASEARRAYREF *pArrayRef, void OleVariant::MarshalArrayRefForSafeArray(SAFEARRAY *pSafeArray, BASEARRAYREF *pArrayRef, VARTYPE vt, - MethodTable *pInterfaceMT) + MethodTable *pInterfaceMT, + PCODE pConvertContentsCode) { CONTRACTL { @@ -3578,59 +3749,26 @@ void OleVariant::MarshalArrayRefForSafeArray(SAFEARRAY *pSafeArray, // Retrieve the number of components. SIZE_T dwNumComponents = (*pArrayRef)->GetNumComponents(); + SIZE_T dwNativeComponentSize = GetElementSizeForVarType(vt, pInterfaceMT); - // Retrieve the marshaler to use to convert the contents. - const Marshaler *marshal = GetMarshalerForVarType(vt, TRUE); + CQuickArray TmpArray; + BYTE* pSrcData = NULL; - if (marshal == NULL || marshal->OleToComArray == NULL) + if (pSafeArray->cDims != 1) { - SIZE_T dwManagedComponentSize = (*pArrayRef)->GetComponentSize(); - -#ifdef _DEBUG - { - // If we're blasting bits, this better be a primitive type. Currency is - // an I8 on managed & unmanaged, so it's good enough. - TypeHandle th = (*pArrayRef)->GetArrayElementTypeHandle(); - - if (!CorTypeInfo::IsPrimitiveType(th.GetInternalCorElementType())) - { - _ASSERTE(!strcmp(th.AsMethodTable()->GetDebugClassName(), "System.Currency") - || !strcmp(th.AsMethodTable()->GetDebugClassName(), "System.Decimal")); - } - } -#endif - if (pSafeArray->cDims == 1) - { - // If the array is single dimensionnal then we can simply copy it over. - memcpyNoGCRefs((*pArrayRef)->GetDataPtr(), pSafeArray->pvData, dwNumComponents * dwManagedComponentSize); - } - else - { - // Copy and transpose the data. - TransposeArrayData((*pArrayRef)->GetDataPtr(), (BYTE*)pSafeArray->pvData, dwNumComponents, dwManagedComponentSize, pSafeArray, TRUE); - } + // Multi-dimensional arrays need transposition before content conversion. + TmpArray.ReSizeThrows(dwNumComponents * dwNativeComponentSize); + pSrcData = TmpArray.Ptr(); + TransposeArrayData(pSrcData, (BYTE*)pSafeArray->pvData, dwNumComponents, dwNativeComponentSize, pSafeArray, TRUE); } else { - CQuickArray TmpArray; - BYTE* pSrcData = NULL; - SIZE_T dwNativeComponentSize = GetElementSizeForVarType(vt, pInterfaceMT); - - if (pSafeArray->cDims != 1) - { - TmpArray.ReSizeThrows(dwNumComponents * dwNativeComponentSize); - pSrcData = TmpArray.Ptr(); - TransposeArrayData(pSrcData, (BYTE*)pSafeArray->pvData, dwNumComponents, dwNativeComponentSize, pSafeArray, TRUE); - } - else - { - pSrcData = (BYTE*)pSafeArray->pvData; - } - - PinningHandleHolder handle = GetAppDomain()->CreatePinningHandle((OBJECTREF)*pArrayRef); - - marshal->OleToComArray(pSrcData, pArrayRef, pInterfaceMT); + pSrcData = (BYTE*)pSafeArray->pvData; } + + // Use managed IArrayElementMarshaler implementations for content conversion. + UnmanagedCallersOnlyCaller invoker(METHOD__STUBHELPERS__INVOKE_ARRAY_CONTENTS_CONVERTER); + invoker.InvokeThrowing(pArrayRef, pSrcData, (void*)pConvertContentsCode); } void OleVariant::ConvertValueClassToVariant(OBJECTREF *pBoxedValueClass, VARIANT *pOleVariant) diff --git a/src/coreclr/vm/olevariant.h b/src/coreclr/vm/olevariant.h index 35d6505da7ceeb..a049c44201dcf9 100644 --- a/src/coreclr/vm/olevariant.h +++ b/src/coreclr/vm/olevariant.h @@ -40,12 +40,14 @@ class OleVariant SAFEARRAY* pSafeArray, VARTYPE vt, MethodTable* pInterfaceMT, + PCODE pConvertContentsCode, BOOL fSafeArrayIsValid = TRUE); static void MarshalArrayRefForSafeArray(SAFEARRAY* pSafeArray, BASEARRAYREF* pArrayRef, VARTYPE vt, - MethodTable* pInterfaceMT); + MethodTable* pInterfaceMT, + PCODE pConvertContentsCode); // Helper function to convert a boxed value class to an OLE variant. static void ConvertValueClassToVariant(OBJECTREF *pBoxedValueClass, VARIANT *pOleVariant); @@ -245,6 +247,11 @@ class OleVariant #endif // FEATURE_COMINTEROP }; +// Returns the instantiated MethodDesc for a StubHelpers array marshalling method +// (e.g. ConvertArrayContentsToUnmanaged/ConvertArrayContentsToManaged) for a given +// SAFEARRAY VARTYPE and element MethodTable. +MethodDesc* GetInstantiatedSafeArrayMethod(BinderMethodID methodId, VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous); + extern "C" void QCALLTYPE Variant_ConvertValueTypeToRecord(QCall::ObjectHandleOnStack obj, VARIANT* pOle); #endif From 70179afdddaee1408f8f1464fb9520bb3b063ba4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 17:33:22 -0700 Subject: [PATCH 08/41] The only cases where OleVariant didn't return a marshaler was blittable value types and primitives --- src/coreclr/vm/ilmarshalers.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 054ab68c39edc6..19a965b3a79ff7 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -3843,12 +3843,30 @@ bool ILNativeArrayMarshaler::CanMarshalViaPinning() { // We can't pin an array if we have a non-default element native type (e.g. ANSICHAR, WINBOOL, CBOOL), // if we have a marshaler for the var type, or if we can't get a method-table representing the array. + + if (!IsCLRToNative(m_dwMarshalFlags) || IsByref(m_dwMarshalFlags)) + { + return false; + } + CREATE_MARSHALER_CARRAY_OPERANDS mops; m_pargs->m_pMarshalInfo->GetMops(&mops); + if (mops.elementNativeType != NATIVE_TYPE_DEFAULT) + { + // This means that we have some sort of custom marshaling logic. + return false; + } + + if (NULL == m_pargs->na.m_pArrayMT) + { + return false; + } + + TypeHandle elementTypeHandle = m_pargs->na.m_pArrayMT->GetArrayElementTypeHandle(); - return IsCLRToNative(m_dwMarshalFlags) && !IsByref(m_dwMarshalFlags) && (NULL != m_pargs->na.m_pArrayMT) - && mops.elementNativeType == NATIVE_TYPE_DEFAULT - && (NULL == OleVariant::GetMarshalerForVarType(m_pargs->na.m_vt, TRUE)); + return elementTypeHandle.IsBlittable() + && (elementTypeHandle.GetMethodTable()->IsValueType() + || elementTypeHandle.GetMethodTable()->IsTruePrimitive()); } void ILNativeArrayMarshaler::EmitMarshalViaPinning(ILCodeStream* pslILEmit) From e0f1da54f1fcabd6434bc775649de9feae396686 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 18:30:56 -0700 Subject: [PATCH 09/41] Remove all now-unused OleVariant-based array marshalling --- src/coreclr/vm/dispatchinfo.cpp | 2 +- src/coreclr/vm/olevariant.cpp | 1437 +------------------------------ src/coreclr/vm/olevariant.h | 131 +-- 3 files changed, 47 insertions(+), 1523 deletions(-) diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index 9532cde13d84d7..d217a98eb3c0bf 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -1636,7 +1636,7 @@ void DispatchInfo::InvokeMemberWorker(DispatchMemberInfo* pDispMemberInfo, { // VarArg scenario // Here we only unmarshal the object whose corresponding VARIANT is VarArg - OleVariant::MarshalVariantArrayComToOle((BASEARRAYREF*)&pObjs->TmpObj, (void *)(aByrefArgOleVariant[i]), NULL, TRUE, FALSE, TRUE, TRUE, -1); + OleVariant::MarshalVarArgVariantArrayToOle((PTRARRAYREF*)&pObjs->TmpObj, (aByrefArgOleVariant[i])); } else { diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index a6d715b5432ac1..078815e7231fca 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -685,139 +685,6 @@ MethodTable* OleVariant::GetNativeMethodTableForVarType(VARTYPE vt, MethodTable* } // -// GetMarshalerForVarType returns the marshaler for the -// given VARTYPE. -// - -const OleVariant::Marshaler *OleVariant::GetMarshalerForVarType(VARTYPE vt, BOOL fThrow) -{ - CONTRACT (const OleVariant::Marshaler*) - { - if (fThrow) THROWS; else NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); - } - CONTRACT_END; - -#define RETURN_MARSHALER(ArrayOleToCom, ArrayComToOle, ClearArray) \ - { static const Marshaler marshaler = { ArrayOleToCom, ArrayComToOle, ClearArray }; RETURN &marshaler; } - -#ifdef FEATURE_COMINTEROP - if (vt & VT_ARRAY) - { -VariantArray: - RETURN_MARSHALER( - NULL, - NULL, - ClearVariantArray - ); - } -#endif // FEATURE_COMINTEROP - - switch (vt) - { - case VT_BOOL: - RETURN_MARSHALER( - MarshalBoolArrayOleToCom, - MarshalBoolArrayComToOle, - NULL - ); - - case VT_DATE: - RETURN_MARSHALER( - MarshalDateArrayOleToCom, - MarshalDateArrayComToOle, - NULL - ); - -#ifdef FEATURE_COMINTEROP - case VT_CY: - RETURN_MARSHALER( - MarshalCurrencyArrayOleToCom, - MarshalCurrencyArrayComToOle, - NULL - ); - - case VT_BSTR: - RETURN_MARSHALER( - MarshalBSTRArrayOleToCom, - MarshalBSTRArrayComToOle, - ClearBSTRArray - ); - - case VT_UNKNOWN: - RETURN_MARSHALER( - MarshalInterfaceArrayOleToCom, - MarshalIUnknownArrayComToOle, - ClearInterfaceArray - ); - - case VT_DISPATCH: - RETURN_MARSHALER( - MarshalInterfaceArrayOleToCom, - MarshalIDispatchArrayComToOle, - ClearInterfaceArray - ); - - case VT_SAFEARRAY: - goto VariantArray; - - case VT_VARIANT: - RETURN_MARSHALER( - MarshalVariantArrayOleToCom, - MarshalVariantArrayComToOle, - ClearVariantArray - ); - -#endif // FEATURE_COMINTEROP - - case VT_LPSTR: - RETURN_MARSHALER( - MarshalLPSTRArrayOleToCom, - MarshalLPSTRRArrayComToOle, - ClearLPSTRArray - ); - - case VT_LPWSTR: - RETURN_MARSHALER( - MarshalLPWSTRArrayOleToCom, - MarshalLPWSTRRArrayComToOle, - ClearLPWSTRArray - ); - - case VT_RECORD: -#ifdef FEATURE_COMINTEROP - RETURN_MARSHALER( - MarshalRecordArrayOleToCom, - MarshalRecordArrayComToOle, - ClearRecordArray - ); -#else - RETURN_MARSHALER( - MarshalRecordArrayOleToCom, - MarshalRecordArrayComToOle, - ClearRecordArray - ); -#endif // FEATURE_COMINTEROP - - case VT_CARRAY: - case VT_USERDEFINED: - if (fThrow) - { - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - } - else - { - RETURN NULL; - } - - default: - RETURN NULL; - } -} // OleVariant::Marshaler *OleVariant::GetMarshalerForVarType() - - #ifdef FEATURE_COMINTEROP void SafeVariantClear(VARIANT* pVar) @@ -850,929 +717,48 @@ class VariantEmptyHolder : public Wrapper, SafeV WRAPPER_NO_CONTRACT; } - FORCEINLINE void operator=(VARIANT* p) - { - WRAPPER_NO_CONTRACT; - - Wrapper, SafeVariantClear, 0>::operator=(p); - } -}; - -FORCEINLINE void RecordVariantRelease(VARIANT* value) -{ - if (value) - { - WRAPPER_NO_CONTRACT; - - if (V_RECORD(value)) - V_RECORDINFO(value)->RecordDestroy(V_RECORD(value)); - if (V_RECORDINFO(value)) - V_RECORDINFO(value)->Release(); - } -} - -class RecordVariantHolder : public Wrapper, RecordVariantRelease, 0> -{ -public: - RecordVariantHolder(VARIANT* p = NULL) - : Wrapper, RecordVariantRelease, 0>(p) - { - WRAPPER_NO_CONTRACT; - } - - FORCEINLINE void operator=(VARIANT* p) - { - WRAPPER_NO_CONTRACT; - Wrapper, RecordVariantRelease, 0>::operator=(p); - } -}; -#endif // FEATURE_COMINTEROP - -/* ------------------------------------------------------------------------- * - * Boolean marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalBoolArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - VARIANT_BOOL *pOle = (VARIANT_BOOL *) oleArray; - VARIANT_BOOL *pOleEnd = pOle + elementCount; - - UCHAR *pCom = (UCHAR *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - { - static_assert(sizeof(VARIANT_BOOL) == sizeof(UINT16)); - (*(pCom++)) = MAYBE_UNALIGNED_READ(pOle, 16) ? 1 : 0; - pOle++; - } -} - -void OleVariant::MarshalBoolArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - VARIANT_BOOL *pOle = (VARIANT_BOOL *) oleArray; - VARIANT_BOOL *pOleEnd = pOle + cElements; - - UCHAR *pCom = (UCHAR *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - { - static_assert(sizeof(VARIANT_BOOL) == sizeof(UINT16)); - MAYBE_UNALIGNED_WRITE(pOle, 16, *pCom ? VARIANT_TRUE : VARIANT_FALSE); - pOle++; pCom++; - } -} - -/* ------------------------------------------------------------------------- * - * WinBoolean marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalWinBoolArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - BOOL *pOle = (BOOL *) oleArray; - BOOL *pOleEnd = pOle + elementCount; - - UCHAR *pCom = (UCHAR *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - { - static_assert(sizeof(BOOL) == sizeof(UINT32)); - (*(pCom++)) = MAYBE_UNALIGNED_READ(pOle, 32) ? 1 : 0; - pOle++; - } -} - -void OleVariant::MarshalWinBoolArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - BOOL *pOle = (BOOL *) oleArray; - BOOL *pOleEnd = pOle + cElements; - - UCHAR *pCom = (UCHAR *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - { - static_assert(sizeof(BOOL) == sizeof(UINT32)); - MAYBE_UNALIGNED_WRITE(pOle, 32, *pCom ? 1 : 0); - pOle++; pCom++; - } -} - -/* ------------------------------------------------------------------------- * - * CBool marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalCBoolArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT) -{ - LIMITED_METHOD_CONTRACT; - - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - _ASSERTE((*pComArray)->GetArrayElementType() == ELEMENT_TYPE_BOOLEAN); - - SIZE_T cbArray = (*pComArray)->GetNumComponents(); - - BYTE *pOle = (BYTE *) oleArray; - BYTE *pOleEnd = pOle + cbArray; - - UCHAR *pCom = (UCHAR *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - { - (*pCom) = (*pOle ? 1 : 0); - pOle++; pCom++; - } -} - -void OleVariant::MarshalCBoolArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayIsValid, - SIZE_T cElements) -{ - LIMITED_METHOD_CONTRACT; - - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - _ASSERTE((*pComArray)->GetArrayElementType() == ELEMENT_TYPE_BOOLEAN); - - BYTE *pOle = (BYTE *) oleArray; - BYTE *pOleEnd = pOle + cElements; - - UCHAR *pCom = (UCHAR *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - { - *pOle = (*pCom ? 1 : 0); - pOle++; pCom++; - } -} - -/* ------------------------------------------------------------------------- * - * Ansi char marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalAnsiCharArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - WCHAR *pCom = (WCHAR *) (*pComArray)->GetDataPtr(); - - if (0 == elementCount) - { - *pCom = '\0'; - return; - } - - if (0 == MultiByteToWideChar(CP_ACP, - MB_PRECOMPOSED, - (const CHAR *)oleArray, - (int)elementCount, - pCom, - (int)elementCount)) - { - COMPlusThrowWin32(); - } -} - -void OleVariant::MarshalAnsiCharArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayIsValid, - SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - const WCHAR *pCom = (const WCHAR *) (*pComArray)->GetDataPtr(); - - if (!FitsIn(cElements)) - COMPlusThrowHR(COR_E_OVERFLOW); - - int cchCount = (int)cElements; - int cbBuffer; - - if (!ClrSafeInt::multiply(cchCount, GetMaxDBCSCharByteSize(), cbBuffer)) - COMPlusThrowHR(COR_E_OVERFLOW); - - InternalWideToAnsi((WCHAR*)pCom, cchCount, (CHAR*)oleArray, cbBuffer, - fBestFitMapping, fThrowOnUnmappableChar); -} - -/* ------------------------------------------------------------------------- * - * Interface marshaling routines - * ------------------------------------------------------------------------- */ - -#ifdef FEATURE_COMINTEROP -void OleVariant::MarshalInterfaceArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pElementMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - IUnknown **pOle = (IUnknown **) oleArray; - IUnknown **pOleEnd = pOle + elementCount; - - OBJECTREF *pCom = (OBJECTREF *) (*pComArray)->GetDataPtr(); - - OBJECTREF obj = NULL; - GCPROTECT_BEGIN(obj) - { - GCPROTECT_BEGININTERIOR(pCom) - { - while (pOle < pOleEnd) - { - IUnknown *unk = *pOle++; - - if (unk == NULL) - obj = NULL; - else - GetObjectRefFromComIP(&obj, unk); - - // - // Make sure the object can be cast to the destination type. - // - - if (pElementMT != NULL && !CanCastComObject(obj, pElementMT)) - { - StackSString ssObjClsName; - StackSString ssDestClsName; - obj->GetMethodTable()->_GetFullyQualifiedNameForClass(ssObjClsName); - pElementMT->_GetFullyQualifiedNameForClass(ssDestClsName); - COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCAST, - ssObjClsName.GetUnicode(), ssDestClsName.GetUnicode()); - } - - SetObjectReference(pCom++, obj); - } - } - GCPROTECT_END(); - } - GCPROTECT_END(); -} - -void OleVariant::MarshalIUnknownArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pElementMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - WRAPPER_NO_CONTRACT; - - MarshalInterfaceArrayComToOleHelper(pComArray, oleArray, pElementMT, FALSE, cElements); -} - -void OleVariant::ClearInterfaceArray(void *oleArray, SIZE_T cElements, MethodTable *pInterfaceMT) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(oleArray)); - } - CONTRACTL_END; - - IUnknown **pOle = (IUnknown **) oleArray; - IUnknown **pOleEnd = pOle + cElements; - - GCX_PREEMP(); - while (pOle < pOleEnd) - { - IUnknown *pUnk = *pOle++; - - if (pUnk != NULL) - { - ULONG cbRef = SafeReleasePreemp(pUnk); - LogInteropRelease(pUnk, cbRef, "VariantClearInterfacArray"); - } - } -} - - -/* ------------------------------------------------------------------------- * - * BSTR marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalBSTRArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - WRAPPER(THROWS); - WRAPPER(GC_TRIGGERS); - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - STRINGREF stringObj = NULL; - GCPROTECT_BEGIN(stringObj) - { - ASSERT_PROTECTED(pComArray); - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - BSTR *pOle = (BSTR *) oleArray; - BSTR *pOleEnd = pOle + elementCount; - - STRINGREF *pCom = (STRINGREF *) (*pComArray)->GetDataPtr(); - GCPROTECT_BEGININTERIOR(pCom) - { - while (pOle < pOleEnd) - { - BSTR bstr = *pOle++; - - ConvertBSTRToString(bstr, &stringObj); - - SetObjectReference((OBJECTREF*) pCom++, (OBJECTREF) stringObj); - } - } - GCPROTECT_END(); - } - GCPROTECT_END(); -} - -void OleVariant::MarshalBSTRArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - WRAPPER(GC_TRIGGERS); - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - STRINGREF stringObj = NULL; - GCPROTECT_BEGIN(stringObj) - { - ASSERT_PROTECTED(pComArray); - - BSTR *pOle = (BSTR *) oleArray; - BSTR *pOleEnd = pOle + cElements; - - STRINGREF *pCom = (STRINGREF *) (*pComArray)->GetDataPtr(); - GCPROTECT_BEGININTERIOR(pCom) - { - while (pOle < pOleEnd) - { - stringObj = *pCom++; - BSTR bstr = ConvertStringToBSTR(&stringObj); - *pOle++ = bstr; - } - } - GCPROTECT_END(); - } - GCPROTECT_END(); -} - -void OleVariant::ClearBSTRArray(void *oleArray, SIZE_T cElements, MethodTable *pInterfaceMT) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(oleArray)); - } - CONTRACTL_END; - - BSTR *pOle = (BSTR *) oleArray; - BSTR *pOleEnd = pOle + cElements; - - while (pOle < pOleEnd) - { - BSTR bstr = *pOle++; - - if (bstr != NULL) - SysFreeString(bstr); - } -} -#endif // FEATURE_COMINTEROP - - - -/* ------------------------------------------------------------------------- * - * Structure marshaling routines - * ------------------------------------------------------------------------- */ -void OleVariant::MarshalNonBlittableRecordArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - PRECONDITION(CheckPointer(pInterfaceMT)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - UnmanagedCallersOnlyCaller convertToManaged(METHOD__STUBHELPERS__NONBLITTABLE_STRUCTURE_ARRAY_CONVERT_TO_MANAGED); - convertToManaged.InvokeThrowing(pComArray, oleArray, pInterfaceMT, pInterfaceMT->GetNativeSize()); -} - -void OleVariant::MarshalNonBlittableRecordArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - PRECONDITION(CheckPointer(pInterfaceMT)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - SIZE_T elemSize = pInterfaceMT->GetNativeSize(); - - BYTE *pOle = (BYTE *) oleArray; - BYTE *pOleEnd = pOle + elemSize * cElements; - - if (!fOleArrayIsValid) - { - // field marshalers assume that the native structure is valid - FillMemory(pOle, pOleEnd - pOle, 0); - } - - UnmanagedCallersOnlyCaller convertToUnmanaged(METHOD__STUBHELPERS__NONBLITTABLE_STRUCTURE_ARRAY_CONVERT_TO_UNMANAGED); - convertToUnmanaged.InvokeThrowing(pComArray, oleArray, pInterfaceMT, pInterfaceMT->GetNativeSize()); -} - -void OleVariant::ClearNonBlittableRecordArray(void *oleArray, SIZE_T cElements, MethodTable *pInterfaceMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pInterfaceMT)); - } - CONTRACTL_END; - - UnmanagedCallersOnlyCaller free(METHOD__STUBHELPERS__NONBLITTABLE_STRUCTURE_ARRAY_FREE); - free.InvokeThrowing(oleArray, cElements, pInterfaceMT, pInterfaceMT->GetNativeSize()); -} - - -/* ------------------------------------------------------------------------- * - * LPWSTR marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalLPWSTRArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - LPWSTR *pOle = (LPWSTR *) oleArray; - LPWSTR *pOleEnd = pOle + elementCount; - - STRINGREF *pCom = (STRINGREF *) (*pComArray)->GetDataPtr(); - GCPROTECT_BEGININTERIOR(pCom) - { - while (pOle < pOleEnd) - { - LPWSTR lpwstr = *pOle++; - - STRINGREF string; - if (lpwstr == NULL) - string = NULL; - else - string = StringObject::NewString(lpwstr); - - SetObjectReference((OBJECTREF*) pCom++, (OBJECTREF) string); - } - } - GCPROTECT_END(); -} - -void OleVariant::MarshalLPWSTRRArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - LPWSTR *pOle = (LPWSTR *) oleArray; - LPWSTR *pOleEnd = pOle + cElements; - - struct - { - BASEARRAYREF pCom; - STRINGREF stringRef; - } gc; - gc.pCom = *pComArray; - gc.stringRef = NULL; - GCPROTECT_BEGIN(gc) - { - - int i = 0; - while (pOle < pOleEnd) - { - gc.stringRef = *((STRINGREF*)gc.pCom->GetDataPtr() + i); - - LPWSTR lpwstr; - if (gc.stringRef == NULL) - { - lpwstr = NULL; - } - else - { - // Retrieve the length of the string. - int Length = gc.stringRef->GetStringLength(); - int allocLength = (Length + 1) * sizeof(WCHAR); - if (allocLength < Length) - ThrowOutOfMemory(); - - // Allocate the string using CoTaskMemAlloc. - { - GCX_PREEMP(); - lpwstr = (LPWSTR)CoTaskMemAlloc(allocLength); - } - if (lpwstr == NULL) - ThrowOutOfMemory(); - - // Copy the COM+ string into the newly allocated LPWSTR. - memcpyNoGCRefs(lpwstr, gc.stringRef->GetBuffer(), allocLength); - lpwstr[Length] = W('\0'); - } - - *pOle++ = lpwstr; - i++; - } - } - GCPROTECT_END(); -} - -void OleVariant::ClearLPWSTRArray(void *oleArray, SIZE_T cElements, MethodTable *pInterfaceMT) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(oleArray)); - } - CONTRACTL_END; - - GCX_PREEMP(); - LPWSTR *pOle = (LPWSTR *) oleArray; - LPWSTR *pOleEnd = pOle + cElements; - - while (pOle < pOleEnd) - { - LPWSTR lpwstr = *pOle++; - - if (lpwstr != NULL) - CoTaskMemFree(lpwstr); - } -} - -/* ------------------------------------------------------------------------- * - * LPWSTR marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalLPSTRArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - LPSTR *pOle = (LPSTR *) oleArray; - LPSTR *pOleEnd = pOle + elementCount; - - STRINGREF *pCom = (STRINGREF *) (*pComArray)->GetDataPtr(); - GCPROTECT_BEGININTERIOR(pCom) - { - while (pOle < pOleEnd) - { - LPSTR lpstr = *pOle++; - - STRINGREF string; - if (lpstr == NULL) - string = NULL; - else - string = StringObject::NewString(lpstr); - - SetObjectReference((OBJECTREF*) pCom++, (OBJECTREF) string); - } - } - GCPROTECT_END(); -} - -void OleVariant::MarshalLPSTRRArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - INJECT_FAULT(COMPlusThrowOM()); - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - LPSTR *pOle = (LPSTR *) oleArray; - LPSTR *pOleEnd = pOle + cElements; - - struct - { - BASEARRAYREF pCom; - STRINGREF stringRef; - } gc; - gc.pCom = *pComArray; - gc.stringRef = NULL; - GCPROTECT_BEGIN(gc) - { - int i = 0; - while (pOle < pOleEnd) - { - gc.stringRef = *((STRINGREF*)gc.pCom->GetDataPtr() + i); - - CoTaskMemHolder lpstr(NULL); - if (gc.stringRef == NULL) - { - lpstr = NULL; - } - else - { - // Retrieve the length of the string. - int Length = gc.stringRef->GetStringLength(); - int allocLength = Length * GetMaxDBCSCharByteSize() + 1; - if (allocLength < Length) - ThrowOutOfMemory(); - - // Allocate the string using CoTaskMemAlloc. - { - GCX_PREEMP(); - lpstr = (LPSTR)CoTaskMemAlloc(allocLength); - } - if (lpstr == NULL) - ThrowOutOfMemory(); - - // Convert the unicode string to an ansi string. - int bytesWritten = InternalWideToAnsi(gc.stringRef->GetBuffer(), Length, lpstr, allocLength, fBestFitMapping, fThrowOnUnmappableChar); - _ASSERTE(bytesWritten >= 0 && bytesWritten < allocLength); - lpstr[bytesWritten] = '\0'; - } - - *pOle++ = lpstr; - i++; - lpstr.SuppressRelease(); - } - } - GCPROTECT_END(); -} - -void OleVariant::ClearLPSTRArray(void *oleArray, SIZE_T cElements, MethodTable *pInterfaceMT) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(oleArray)); - } - CONTRACTL_END; - - GCX_PREEMP(); - LPSTR *pOle = (LPSTR *) oleArray; - LPSTR *pOleEnd = pOle + cElements; - - while (pOle < pOleEnd) + FORCEINLINE void operator=(VARIANT* p) { - LPSTR lpstr = *pOle++; + WRAPPER_NO_CONTRACT; - if (lpstr != NULL) - CoTaskMemFree(lpstr); + Wrapper, SafeVariantClear, 0>::operator=(p); } -} - -/* ------------------------------------------------------------------------- * - * Date marshaling routines - * ------------------------------------------------------------------------- */ +}; -void OleVariant::MarshalDateArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) +FORCEINLINE void RecordVariantRelease(VARIANT* value) { - CONTRACTL + if (value) { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - DATE *pOle = (DATE *) oleArray; - DATE *pOleEnd = pOle + elementCount; - - INT64 *pCom = (INT64 *) (*pComArray)->GetDataPtr(); - - // - // We aren't calling anything which might cause a GC, so don't worry about - // the array moving here. - // + WRAPPER_NO_CONTRACT; - while (pOle < pOleEnd) - *pCom++ = COMDateTime::DoubleDateToTicks(*pOle++); + if (V_RECORD(value)) + V_RECORDINFO(value)->RecordDestroy(V_RECORD(value)); + if (V_RECORDINFO(value)) + V_RECORDINFO(value)->Release(); + } } -void OleVariant::MarshalDateArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) +class RecordVariantHolder : public Wrapper, RecordVariantRelease, 0> { - CONTRACTL +public: + RecordVariantHolder(VARIANT* p = NULL) + : Wrapper, RecordVariantRelease, 0>(p) { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); + WRAPPER_NO_CONTRACT; } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - DATE *pOle = (DATE *) oleArray; - DATE *pOleEnd = pOle + cElements; + FORCEINLINE void operator=(VARIANT* p) + { + WRAPPER_NO_CONTRACT; + Wrapper, RecordVariantRelease, 0>::operator=(p); + } +}; +#endif // FEATURE_COMINTEROP - INT64 *pCom = (INT64 *) (*pComArray)->GetDataPtr(); +/* ------------------------------------------------------------------------- * +/* ------------------------------------------------------------------------- * - // - // We aren't calling anything which might cause a GC, so don't worry about - // the array moving here. - // - while (pOle < pOleEnd) - *pOle++ = COMDateTime::TicksToDoubleDate(*pCom++); -} /* ------------------------------------------------------------------------- * * Record marshaling routines @@ -1833,87 +819,6 @@ void OleVariant::MarshalRecordVariantOleToObject(const VARIANT *pOleVariant, } #endif // FEATURE_COMINTEROP -void OleVariant::MarshalRecordArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pElementMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - PRECONDITION(CheckPointer(pElementMT)); - } - CONTRACTL_END; - - if (pElementMT->IsBlittable()) - { - // The array is blittable so we can simply copy it. - _ASSERTE(pComArray); - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - SIZE_T elemSize = pElementMT->GetNativeSize(); - memcpyNoGCRefs((*pComArray)->GetDataPtr(), oleArray, elementCount * elemSize); - } - else - { - // The array is non blittable so we need to marshal the elements. - _ASSERTE(pElementMT->HasLayout()); - MarshalNonBlittableRecordArrayOleToCom(oleArray, pComArray, pElementMT); - } -} - -void OleVariant::MarshalRecordArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pElementMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - PRECONDITION(CheckPointer(pElementMT)); - } - CONTRACTL_END; - - if (pElementMT->IsBlittable()) - { - // The array is blittable so we can simply copy it. - _ASSERTE(pComArray); - SIZE_T elemSize = pElementMT->GetNativeSize(); - memcpyNoGCRefs(oleArray, (*pComArray)->GetDataPtr(), cElements * elemSize); - } - else - { - // The array is non blittable so we need to marshal the elements. - _ASSERTE(pElementMT->HasLayout()); - MarshalNonBlittableRecordArrayComToOle(pComArray, oleArray, pElementMT, fBestFitMapping, fThrowOnUnmappableChar, fOleArrayIsValid, cElements); - } -} - - -void OleVariant::ClearRecordArray(void *oleArray, SIZE_T cElements, MethodTable *pElementMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pElementMT)); - } - CONTRACTL_END; - - if (!pElementMT->IsBlittable()) - { - _ASSERTE(pElementMT->HasLayout()); - ClearNonBlittableRecordArray(oleArray, cElements, pElementMT); - } -} - #ifdef FEATURE_COMINTEROP // Warning! VariantClear's previous contents of pVarOut. @@ -2708,90 +1613,6 @@ void OleVariant::MarshalOleVariantForObjectUncommon(OBJECTREF * const & pObj, VA veh.SuppressRelease(); } -void OleVariant::MarshalInterfaceArrayComToOleHelper(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pElementMT, BOOL bDefaultIsDispatch, - SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(pComArray)); - PRECONDITION(CheckPointer(oleArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - - BOOL bDispatch = bDefaultIsDispatch; - BOOL bHeterogenous = (pElementMT == NULL); - - // If the method table is for Object then don't consider it. - if (pElementMT == g_pObjectClass) - pElementMT = NULL; - - // If the element MT represents a class, then we need to determine the default - // interface to use to expose the object out to COM. - if (pElementMT && !pElementMT->IsInterface()) - { - pElementMT = GetDefaultInterfaceMTForClass(pElementMT, &bDispatch); - } - - // Determine the start and the end of the data in the OLE array. - IUnknown **pOle = (IUnknown **) oleArray; - IUnknown **pOleEnd = pOle + cElements; - - // Retrieve the start of the data in the managed array. - OBJECTREF *pCom = (OBJECTREF *) (*pComArray)->GetDataPtr(); - - OBJECTREF TmpObj = NULL; - GCPROTECT_BEGIN(TmpObj) - { - GCPROTECT_BEGININTERIOR(pCom) - { - MethodTable *pLastElementMT = NULL; - - while (pOle < pOleEnd) - { - TmpObj = *pCom++; - - IUnknown *unk; - if (TmpObj == NULL) - unk = NULL; - else - { - if (bHeterogenous) - { - // Inspect the type of each element separately (cache the last type for perf). - if (TmpObj->GetMethodTable() != pLastElementMT) - { - pLastElementMT = TmpObj->GetMethodTable(); - pElementMT = GetDefaultInterfaceMTForClass(pLastElementMT, &bDispatch); - } - } - - if (pElementMT) - { - // Convert to COM IP based on an interface MT (a specific interface will be exposed). - unk = GetComIPFromObjectRef(&TmpObj, pElementMT); - } - else - { - // Convert to COM IP exposing either IDispatch or IUnknown. - unk = GetComIPFromObjectRef(&TmpObj, (bDispatch ? ComIpType_Dispatch : ComIpType_Unknown), NULL); - } - } - - *pOle++ = unk; - } - } - GCPROTECT_END(); - } - GCPROTECT_END(); -} - // Used by customer checked build to test validity of VARIANT BOOL OleVariant::CheckVariant(VARIANT* pOle) @@ -2884,81 +1705,7 @@ HRESULT OleVariant::ClearAndInsertContentsIntoByrefRecordVariant(VARIANT* pOle, return S_OK; } -void OleVariant::MarshalIDispatchArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pElementMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayIsValid, - SIZE_T cElements) -{ - WRAPPER_NO_CONTRACT; - - MarshalInterfaceArrayComToOleHelper(pComArray, oleArray, pElementMT, TRUE, cElements); -} - - -/* ------------------------------------------------------------------------- * - * Currency marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalCurrencyArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - CURRENCY *pOle = (CURRENCY *) oleArray; - CURRENCY *pOleEnd = pOle + elementCount; - - DECIMAL *pCom = (DECIMAL *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - { - VarDecFromCyCanonicalize(*pOle++, pCom++); - } -} - -void OleVariant::MarshalCurrencyArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - ASSERT_PROTECTED(pComArray); - - CURRENCY *pOle = (CURRENCY *) oleArray; - CURRENCY *pOleEnd = pOle + cElements; - - DECIMAL *pCom = (DECIMAL *) (*pComArray)->GetDataPtr(); - - while (pOle < pOleEnd) - IfFailThrow(VarCyFromDec(pCom++, pOle++)); -} - - -/* ------------------------------------------------------------------------- * - * Variant marshaling routines - * ------------------------------------------------------------------------- */ - -void OleVariant::MarshalVariantArrayOleToCom(void *oleArray, BASEARRAYREF *pComArray, - MethodTable *pInterfaceMT) +void OleVariant::MarshalVarArgVariantArrayToOle(PTRARRAYREF *pClrArray, VARIANT *oleArray) { CONTRACTL { @@ -2966,134 +1713,40 @@ void OleVariant::MarshalVariantArrayOleToCom(void *oleArray, BASEARRAYREF *pComA GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); + PRECONDITION(CheckPointer(pClrArray)); } CONTRACTL_END; - ASSERT_PROTECTED(pComArray); + ASSERT_PROTECTED(pClrArray); - SIZE_T elementCount = (*pComArray)->GetNumComponents(); + SIZE_T elementCount = (*pClrArray)->GetNumComponents(); VARIANT *pOle = (VARIANT *) oleArray; - VARIANT *pOleEnd = pOle + elementCount; - OBJECTREF *pCom = (OBJECTREF *) (*pComArray)->GetDataPtr(); + OBJECTREF *pClr = (*pClrArray)->GetDataPtr(); OBJECTREF TmpObj = NULL; GCPROTECT_BEGIN(TmpObj) + GCPROTECT_BEGININTERIOR(pClr) + for (SIZE_T i = 0; i < elementCount; i++) { - GCPROTECT_BEGININTERIOR(pCom) - { - while (pOle < pOleEnd) - { - // Marshal the OLE variant into a temp managed variant. - MarshalObjectForOleVariant(pOle++, &TmpObj); - - SetObjectReference(pCom++, TmpObj); - } - } - GCPROTECT_END(); - } - GCPROTECT_END(); -} - -void OleVariant::MarshalVariantArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid, SIZE_T cElements) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; - - - MarshalVariantArrayComToOle(pComArray, oleArray, pInterfaceMT, fBestFitMapping, fThrowOnUnmappableChar, FALSE, fOleArrayIsValid); -} - - -void OleVariant::MarshalVariantArrayComToOle(BASEARRAYREF *pComArray, void *oleArray, - MethodTable *pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fMarshalByrefArgOnly, - BOOL fOleArrayIsValid, int nOleArrayStepLength) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(oleArray)); - PRECONDITION(CheckPointer(pComArray)); - } - CONTRACTL_END; + TmpObj = pClr[i]; + VARIANT *pCurrent = pOle - ((SSIZE_T)i); - ASSERT_PROTECTED(pComArray); - - SIZE_T elementCount = (*pComArray)->GetNumComponents(); - - VARIANT *pOle = (VARIANT *) oleArray; - VARIANT *pOleEnd = pOle + elementCount * nOleArrayStepLength; - - OBJECTREF *pCom = (OBJECTREF *) (*pComArray)->GetDataPtr(); - - OBJECTREF TmpObj = NULL; - GCPROTECT_BEGIN(TmpObj) - { - GCPROTECT_BEGININTERIOR(pCom) + // Marshal the temp managed variant into the OLE variant. + // We firstly try MarshalCommonOleRefVariantForObject for VT_BYREF variant because + // MarshalOleVariantForObject() VariantClear the variant and does not keep the VT_BYREF. + // MarshalCommonOleRefVariantForObject is used instead of MarshalOleRefVariantForObject so + // that cast will not be done based on the VT of the variant. + if (!((pCurrent->vt & VT_BYREF) && + SUCCEEDED(MarshalCommonOleRefVariantForObject(&TmpObj, pCurrent)))) { - while (pOle != pOleEnd) - { - TmpObj = *pCom++; - - // Marshal the temp managed variant into the OLE variant. - if (fOleArrayIsValid) - { - // We firstly try MarshalCommonOleRefVariantForObject for VT_BYREF variant because - // MarshalOleVariantForObject() VariantClear the variant and does not keep the VT_BYREF. - // For back compating the old behavior(we used MarshalOleVariantForObject in the previous - // version) that casts the managed object to Variant based on the object's MethodTable, - // MarshalCommonOleRefVariantForObject is used instead of MarshalOleRefVariantForObject so - // that cast will not be done based on the VT of the variant. - if (!((pOle->vt & VT_BYREF) && - SUCCEEDED(MarshalCommonOleRefVariantForObject(&TmpObj, pOle)))) - if (pOle->vt & VT_BYREF || !fMarshalByrefArgOnly) - MarshalOleVariantForObject(&TmpObj, pOle); - } - else - { - // The contents of pOle is undefined, don't try to handle byrefs. - MarshalOleVariantForObject(&TmpObj, pOle); - } - - pOle += nOleArrayStepLength; - } + if (pCurrent->vt & VT_BYREF) + MarshalOleVariantForObject(&TmpObj, pCurrent); } - GCPROTECT_END(); } GCPROTECT_END(); -} - -void OleVariant::ClearVariantArray(void *oleArray, SIZE_T cElements, MethodTable *pInterfaceMT) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(oleArray)); - } - CONTRACTL_END; - - VARIANT *pOle = (VARIANT *) oleArray; - VARIANT *pOleEnd = pOle + cElements; - - while (pOle < pOleEnd) - SafeVariantClear(pOle++); + GCPROTECT_END(); } diff --git a/src/coreclr/vm/olevariant.h b/src/coreclr/vm/olevariant.h index a049c44201dcf9..022e145ba10153 100644 --- a/src/coreclr/vm/olevariant.h +++ b/src/coreclr/vm/olevariant.h @@ -98,147 +98,18 @@ class OleVariant // Determine the type of the elements for a safe array of records. static TypeHandle GetElementTypeForRecordSafeArray(SAFEARRAY* pSafeArray); - // Helper called from MarshalIUnknownArrayComToOle and MarshalIDispatchArrayComToOle. - static void MarshalInterfaceArrayComToOleHelper(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pElementMT, BOOL bDefaultIsDispatch, - SIZE_T cElements); + static void MarshalVarArgVariantArrayToOle(PTRARRAYREF* pComArray, VARIANT* oleArray); #endif // FEATURE_COMINTEROP - struct Marshaler - { - void (*OleToComArray)(void* oleArray, BASEARRAYREF* pComArray, MethodTable* pInterfaceMT); - void (*ComToOleArray)(BASEARRAYREF* pComArray, void* oleArray, MethodTable* pInterfaceMT, - BOOL fBestFitMapping, BOOL fThrowOnUnmappableChar, - BOOL fOleArrayIsValid,SIZE_T cElements); - void (*ClearOleArray)(void* oleArray, SIZE_T cElements, MethodTable* pInterfaceMT); - }; - - static const Marshaler* GetMarshalerForVarType(VARTYPE vt, BOOL fThrow); - - static void MarshalVariantArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fMarshalByrefArgOnly, - BOOL fOleArrayIsValid, int nOleArrayStepLength = 1); - private: - - // Specific marshaler functions - - static void MarshalBoolArrayOleToCom(void *oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalBoolArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayIsValid, - SIZE_T cElements); - - static void MarshalWinBoolArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalWinBoolArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - static void MarshalCBoolArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalCBoolArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - - static void MarshalAnsiCharArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalAnsiCharArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - -#ifdef FEATURE_COMINTEROP - static void MarshalIDispatchArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); -#endif // FEATURE_COMINTEROP - -#ifdef FEATURE_COMINTEROP - static void MarshalBSTRArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalBSTRArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - static void ClearBSTRArray(void* oleArray, SIZE_T cElements, MethodTable* pInterfaceMT); -#endif // FEATURE_COMINTEROP - - static void MarshalNonBlittableRecordArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalNonBlittableRecordArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - static void ClearNonBlittableRecordArray(void* oleArray, - SIZE_T cElements, MethodTable* pInterfaceMT); - - static void MarshalLPWSTRArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalLPWSTRRArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - static void ClearLPWSTRArray(void* oleArray, - SIZE_T cElements, MethodTable* pInterfaceMT); - - static void MarshalLPSTRArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalLPSTRRArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - static void ClearLPSTRArray(void* oleArray, - SIZE_T cElements, MethodTable* pInterfaceMT); - - static void MarshalDateArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalDateArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - - static void MarshalRecordArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, MethodTable* pElementMT); - static void MarshalRecordArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, MethodTable* pElementMT, - BOOL fBestFitMapping, BOOL fThrowOnUnmappableChar, - BOOL fOleArrayValid, - SIZE_T cElements); - static void ClearRecordArray(void* oleArray, SIZE_T cElements, MethodTable* pElementMT); - #ifdef FEATURE_COMINTEROP static HRESULT MarshalCommonOleRefVariantForObject(OBJECTREF *pObj, VARIANT *pOle); - static void MarshalInterfaceArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalIUnknownArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - static void ClearInterfaceArray(void* oleArray, SIZE_T cElements, MethodTable* pInterfaceMT); #ifdef FEATURE_COMINTEROP static void MarshalRecordVariantOleToObject(const VARIANT* pOleVariant, OBJECTREF * const & pComVariant); #endif - static void MarshalCurrencyArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalCurrencyArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - - static void MarshalVariantArrayOleToCom(void* oleArray, BASEARRAYREF* pComArray, - MethodTable* pInterfaceMT); - static void MarshalVariantArrayComToOle(BASEARRAYREF* pComArray, void* oleArray, - MethodTable* pInterfaceMT, BOOL fBestFitMapping, - BOOL fThrowOnUnmappableChar, BOOL fOleArrayValid, - SIZE_T cElements); - static void ClearVariantArray(void* oleArray, SIZE_T cElements, MethodTable* pInterfaceMT); - #ifdef FEATURE_COMINTEROP static void MarshalArrayVariantOleToObject(const VARIANT* pOleVariant, OBJECTREF * const & pObj); static void MarshalArrayVariantOleRefToObject(const VARIANT* pOleVariant, OBJECTREF * const & pObj); From 51e9004d7e4db0eec423606ab236e3872adae23d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 9 Apr 2026 20:35:28 -0700 Subject: [PATCH 10/41] Make olevariant windows-only --- src/coreclr/vm/CMakeLists.txt | 4 +- src/coreclr/vm/binder.cpp | 1 - src/coreclr/vm/ceemain.cpp | 1 - src/coreclr/vm/comcallablewrapper.cpp | 1 - src/coreclr/vm/fieldmarshaler.cpp | 214 ++++++++++++++++- src/coreclr/vm/fieldmarshaler.h | 4 +- src/coreclr/vm/ilmarshalers.cpp | 2 + src/coreclr/vm/interopconverter.cpp | 1 - src/coreclr/vm/mlinfo.cpp | 4 +- src/coreclr/vm/olevariant.cpp | 318 +------------------------- src/coreclr/vm/olevariant.h | 23 +- src/coreclr/vm/stubhelpers.cpp | 1 + 12 files changed, 227 insertions(+), 347 deletions(-) diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 7376b252c41c2e..2f9aedafd76f29 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -357,7 +357,6 @@ set(VM_SOURCES_WKS nativeeventsource.cpp nativelibrary.cpp nativelibrarynative.cpp - olevariant.cpp pendingload.cpp pinvokeoverride.cpp profdetach.cpp @@ -466,7 +465,6 @@ set(VM_HEADERS_WKS multicorejit.h multicorejitimpl.h nativeeventsource.h - olevariant.h pendingload.h profdetach.h profilingenumerators.h @@ -606,6 +604,7 @@ if(CLR_CMAKE_TARGET_WIN32) dispatchinfo.cpp dispparammarshaler.cpp olecontexthelpers.cpp + olevariant.cpp runtimecallablewrapper.cpp stdinterfaces.cpp stdinterfaces_wrapper.cpp @@ -622,6 +621,7 @@ if(CLR_CMAKE_TARGET_WIN32) dispatchinfo.h dispparammarshaler.h olecontexthelpers.h + olevariant.h runtimecallablewrapper.h stdinterfaces.h stdinterfaces_internal.h diff --git a/src/coreclr/vm/binder.cpp b/src/coreclr/vm/binder.cpp index 0a2326d1143def..877d0b1ec39f1b 100644 --- a/src/coreclr/vm/binder.cpp +++ b/src/coreclr/vm/binder.cpp @@ -19,7 +19,6 @@ #include "dllimport.h" #include "clrvarargs.h" #include "sigbuilder.h" -#include "olevariant.h" #include "configuration.h" #include "conditionalweaktable.h" #include "interoplibinterface_comwrappers.h" diff --git a/src/coreclr/vm/ceemain.cpp b/src/coreclr/vm/ceemain.cpp index 289f91e1828504..4f715ddee4e475 100644 --- a/src/coreclr/vm/ceemain.cpp +++ b/src/coreclr/vm/ceemain.cpp @@ -147,7 +147,6 @@ #include "eventtrace.h" #include "corhost.h" #include "binder.h" -#include "olevariant.h" #include "comcallablewrapper.h" #include "../dlls/mscorrc/resource.h" #include "util.hpp" diff --git a/src/coreclr/vm/comcallablewrapper.cpp b/src/coreclr/vm/comcallablewrapper.cpp index b0f1341a4632eb..9bb65ef2d95021 100644 --- a/src/coreclr/vm/comcallablewrapper.cpp +++ b/src/coreclr/vm/comcallablewrapper.cpp @@ -21,7 +21,6 @@ #include "method.hpp" #include "class.h" #include "runtimecallablewrapper.h" -#include "olevariant.h" #include "cachelinealloc.h" #include "threads.h" #include "ceemain.h" diff --git a/src/coreclr/vm/fieldmarshaler.cpp b/src/coreclr/vm/fieldmarshaler.cpp index 81b52188444982..f35a270971781d 100644 --- a/src/coreclr/vm/fieldmarshaler.cpp +++ b/src/coreclr/vm/fieldmarshaler.cpp @@ -19,12 +19,222 @@ #include "comdelegate.h" #include "eeconfig.h" #include "comdatetime.h" -#include "olevariant.h" #include #include #include #include "sigformat.h" #include "marshalnative.h" +#ifdef FEATURE_COMINTEROP +#include "interoputil.h" +#endif // FEATURE_COMINTEROP + +static VARTYPE GetVarTypeForCorElementType(CorElementType type) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + static const BYTE map[] = + { + VT_EMPTY, // ELEMENT_TYPE_END + VT_VOID, // ELEMENT_TYPE_VOID + VT_BOOL, // ELEMENT_TYPE_BOOLEAN + VT_UI2, // ELEMENT_TYPE_CHAR + VT_I1, // ELEMENT_TYPE_I1 + VT_UI1, // ELEMENT_TYPE_U1 + VT_I2, // ELEMENT_TYPE_I2 + VT_UI2, // ELEMENT_TYPE_U2 + VT_I4, // ELEMENT_TYPE_I4 + VT_UI4, // ELEMENT_TYPE_U4 + VT_I8, // ELEMENT_TYPE_I8 + VT_UI8, // ELEMENT_TYPE_U8 + VT_R4, // ELEMENT_TYPE_R4 + VT_R8, // ELEMENT_TYPE_R8 + }; + + _ASSERTE(type < (CorElementType) (sizeof(map) / sizeof(map[0]))); + + VARTYPE vt = VARTYPE(map[type]); + + return vt; +} + +VARTYPE GetVarTypeForTypeHandle(TypeHandle type) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // Handle primitive types. + CorElementType elemType = type.GetSignatureCorElementType(); + if (elemType <= ELEMENT_TYPE_R8) + return GetVarTypeForCorElementType(elemType); + + // Types incompatible with interop. + if (type.IsTypeDesc()) + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + + // Handle objects. + MethodTable * pMT = type.AsMethodTable(); + + if (pMT == g_pStringClass) + return VT_BSTR; + if (pMT == g_pObjectClass) + return VT_VARIANT; + + // We need to make sure the CVClasses table is populated. + if(CoreLibBinder::IsClass(pMT, CLASS__DATE_TIME)) + return VT_DATE; + if(CoreLibBinder::IsClass(pMT, CLASS__DECIMAL)) + return VT_DECIMAL; + +#ifdef HOST_64BIT + if (CoreLibBinder::IsClass(pMT, CLASS__INTPTR)) + return VT_I8; + if (CoreLibBinder::IsClass(pMT, CLASS__UINTPTR)) + return VT_UI8; +#else + if (CoreLibBinder::IsClass(pMT, CLASS__INTPTR)) + return VT_INT; + if (CoreLibBinder::IsClass(pMT, CLASS__UINTPTR)) + return VT_UINT; +#endif + +#ifdef FEATURE_COMINTEROP + // The wrapper types are only available when built-in COM is supported. + if (g_pConfig->IsBuiltInCOMSupported()) + { + if (CoreLibBinder::IsClass(pMT, CLASS__DISPATCH_WRAPPER)) + return VT_DISPATCH; + if (CoreLibBinder::IsClass(pMT, CLASS__UNKNOWN_WRAPPER)) + return VT_UNKNOWN; + if (CoreLibBinder::IsClass(pMT, CLASS__ERROR_WRAPPER)) + return VT_ERROR; + if (CoreLibBinder::IsClass(pMT, CLASS__CURRENCY_WRAPPER)) + return VT_CY; + if (CoreLibBinder::IsClass(pMT, CLASS__BSTR_WRAPPER)) + return VT_BSTR; + + // VariantWrappers cannot be stored in VARIANT's. + if (CoreLibBinder::IsClass(pMT, CLASS__VARIANT_WRAPPER)) + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + } +#endif // FEATURE_COMINTEROP + + if (pMT->IsEnum()) + return GetVarTypeForCorElementType(type.GetInternalCorElementType()); + + if (pMT->IsValueType()) + return VT_RECORD; + + if (pMT->IsArray()) + return VT_ARRAY; + +#ifdef FEATURE_COMINTEROP + // There is no VT corresponding to SafeHandles as they cannot be stored in + // VARIANTs or Arrays. The same applies to CriticalHandle. + if (type.CanCastTo(TypeHandle(CoreLibBinder::GetClass(CLASS__SAFE_HANDLE)))) + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + if (type.CanCastTo(TypeHandle(CoreLibBinder::GetClass(CLASS__CRITICAL_HANDLE)))) + COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); + + if (pMT->IsInterface()) + { + CorIfaceAttr ifaceType = pMT->GetComInterfaceType(); + return static_cast(IsDispatchBasedItf(ifaceType) ? VT_DISPATCH : VT_UNKNOWN); + } + + TypeHandle hndDefItfClass; + DefaultInterfaceType DefItfType = GetDefaultInterfaceForClassWrapper(type, &hndDefItfClass); + switch (DefItfType) + { + case DefaultInterfaceType_Explicit: + { + CorIfaceAttr ifaceType = hndDefItfClass.GetMethodTable()->GetComInterfaceType(); + return static_cast(IsDispatchBasedItf(ifaceType) ? VT_DISPATCH : VT_UNKNOWN); + } + + case DefaultInterfaceType_AutoDual: + { + return VT_DISPATCH; + } + + case DefaultInterfaceType_IUnknown: + case DefaultInterfaceType_BaseComClass: + { + return VT_UNKNOWN; + } + + case DefaultInterfaceType_AutoDispatch: + { + return VT_DISPATCH; + } + + default: + { + _ASSERTE(!"Invalid default interface type!"); + } + } +#endif // FEATURE_COMINTEROP + + return VT_UNKNOWN; +} + +MethodTable* GetNativeMethodTableForVarType(VARTYPE vt, MethodTable* pManagedMT) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (vt & VT_ARRAY) + { + return CoreLibBinder::GetClass(CLASS__INTPTR); + } + + switch (vt) + { + case VT_DATE: + return CoreLibBinder::GetClass(CLASS__DOUBLE); + case VT_CY: + return CoreLibBinder::GetClass(CLASS__CURRENCY); + case VT_BOOL: + return CoreLibBinder::GetClass(CLASS__INT16); + case VT_DISPATCH: + case VT_UNKNOWN: + case VT_LPSTR: + case VT_LPWSTR: + case VT_BSTR: + case VT_USERDEFINED: + case VT_SAFEARRAY: + case VT_CARRAY: + return CoreLibBinder::GetClass(CLASS__INTPTR); + case VT_VARIANT: + return CoreLibBinder::GetClass(CLASS__COMVARIANT); + case VT_UI2: + // When CharSet = CharSet.Unicode, System.Char arrays are marshaled as VT_UI2. + // However, since System.Char itself is CharSet.Ansi, the native size of + // System.Char is 1 byte instead of 2. So here we explicitly return System.UInt16's + // MethodTable to ensure the correct size. + return CoreLibBinder::GetClass(CLASS__UINT16); + case VT_DECIMAL: + return CoreLibBinder::GetClass(CLASS__DECIMAL); + default: + _ASSERTE(pManagedMT != NULL); + return pManagedMT; + } +} VOID ParseNativeType(Module* pModule, SigPointer sig, @@ -180,7 +390,7 @@ VOID ParseNativeType(Module* pModule, pMT = CoreLibBinder::GetElementType(pMT->GetInternalCorElementType()); } - *pNFD = NativeFieldDescriptor(pFD, OleVariant::GetNativeMethodTableForVarType(mops.elementType, pMT), mops.additive); + *pNFD = NativeFieldDescriptor(pFD, GetNativeMethodTableForVarType(mops.elementType, pMT), mops.additive); break; } case MarshalInfo::MARSHAL_TYPE_FIXED_CSTR: diff --git a/src/coreclr/vm/fieldmarshaler.h b/src/coreclr/vm/fieldmarshaler.h index 568322cab21106..93d548c61e13e9 100644 --- a/src/coreclr/vm/fieldmarshaler.h +++ b/src/coreclr/vm/fieldmarshaler.h @@ -11,7 +11,9 @@ #include "util.hpp" #include "mlinfo.h" #include "eeconfig.h" -#include "olevariant.h" + +VARTYPE GetVarTypeForTypeHandle(TypeHandle typeHnd); +MethodTable* GetNativeMethodTableForVarType(VARTYPE vt, MethodTable* pManagedMT); // Forward references class EEClassLayoutInfo; diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 19a965b3a79ff7..ed419c90467623 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -11,7 +11,9 @@ #include "dllimport.h" #include "mlinfo.h" #include "ilmarshalers.h" +#ifdef FEATURE_COMINTEROP #include "olevariant.h" +#endif // FEATURE_COMINTEROP #include "comdatetime.h" #include "fieldmarshaler.h" diff --git a/src/coreclr/vm/interopconverter.cpp b/src/coreclr/vm/interopconverter.cpp index 4f951c97634037..f9bc94daf257a1 100644 --- a/src/coreclr/vm/interopconverter.cpp +++ b/src/coreclr/vm/interopconverter.cpp @@ -7,7 +7,6 @@ #include "excep.h" #include "interoputil.h" #include "interopconverter.h" -#include "olevariant.h" #include "comcallablewrapper.h" #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index d74f843d22f725..e1d3fa2ec2ec59 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -15,7 +15,7 @@ #include "../dlls/mscorrc/resource.h" #include "typeparse.h" #include "comdelegate.h" -#include "olevariant.h" +#include "fieldmarshaler.h" #include "ilmarshalers.h" #include "interoputil.h" #include "mdfileformat.h" // For CPackedLen @@ -3533,7 +3533,7 @@ void ArrayMarshalInfo::InitElementInfo(CorNativeType arrayNativeType, MarshalInf } else { - m_vtElement = OleVariant::GetVarTypeForTypeHandle(m_thElement); + m_vtElement = GetVarTypeForTypeHandle(m_thElement); } } #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 078815e7231fca..0caf7f1e05e73c 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -22,249 +22,6 @@ * Local constants * ------------------------------------------------------------------------- */ -#define NO_MAPPING ((BYTE) -1) - - -/* ------------------------------------------------------------------------- * - * Mapping routines - * ------------------------------------------------------------------------- */ - -VARTYPE GetVarTypeForCorElementType(CorElementType type) -{ - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - static const BYTE map[] = - { - VT_EMPTY, // ELEMENT_TYPE_END - VT_VOID, // ELEMENT_TYPE_VOID - VT_BOOL, // ELEMENT_TYPE_BOOLEAN - VT_UI2, // ELEMENT_TYPE_CHAR - VT_I1, // ELEMENT_TYPE_I1 - VT_UI1, // ELEMENT_TYPE_U1 - VT_I2, // ELEMENT_TYPE_I2 - VT_UI2, // ELEMENT_TYPE_U2 - VT_I4, // ELEMENT_TYPE_I4 - VT_UI4, // ELEMENT_TYPE_U4 - VT_I8, // ELEMENT_TYPE_I8 - VT_UI8, // ELEMENT_TYPE_U8 - VT_R4, // ELEMENT_TYPE_R4 - VT_R8, // ELEMENT_TYPE_R8 - VT_BSTR, // ELEMENT_TYPE_STRING - }; - - _ASSERTE(type < (CorElementType) (sizeof(map) / sizeof(map[0]))); - - VARTYPE vt = VARTYPE(map[type]); - - if (vt == NO_MAPPING) - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - - return vt; -} - -// -// GetTypeHandleForVarType returns the TypeHandle for a given -// VARTYPE. This is called by the marshaller in the context of -// a function call. -// - -TypeHandle OleVariant::GetTypeHandleForVarType(VARTYPE vt) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - static const BYTE map[] = - { - CLASS__EMPTY, // VT_EMPTY - CLASS__NULL, // VT_NULL - CLASS__INT16, // VT_I2 - CLASS__INT32, // VT_I4 - CLASS__SINGLE, // VT_R4 - CLASS__DOUBLE, // VT_R8 - CLASS__DECIMAL, // VT_CY - CLASS__DATE_TIME, // VT_DATE - CLASS__STRING, // VT_BSTR - CLASS__OBJECT, // VT_DISPATCH - CLASS__INT32, // VT_ERROR - CLASS__BOOLEAN, // VT_BOOL - NO_MAPPING, // VT_VARIANT - CLASS__OBJECT, // VT_UNKNOWN - CLASS__DECIMAL, // VT_DECIMAL - NO_MAPPING, // unused - CLASS__SBYTE, // VT_I1 - CLASS__BYTE, // VT_UI1 - CLASS__UINT16, // VT_UI2 - CLASS__UINT32, // VT_UI4 - CLASS__INT64, // VT_I8 - CLASS__UINT64, // VT_UI8 - CLASS__INT32, // VT_INT - CLASS__UINT32, // VT_UINT - CLASS__VOID, // VT_VOID - NO_MAPPING, // VT_HRESULT - NO_MAPPING, // VT_PTR - NO_MAPPING, // VT_SAFEARRAY - NO_MAPPING, // VT_CARRAY - NO_MAPPING, // VT_USERDEFINED - NO_MAPPING, // VT_LPSTR - NO_MAPPING, // VT_LPWSTR - NO_MAPPING, // unused - NO_MAPPING, // unused - NO_MAPPING, // unused - NO_MAPPING, // unused - CLASS__OBJECT, // VT_RECORD - }; - - BinderClassID type = CLASS__NIL; - - // Validate the arguments. - _ASSERTE((vt & VT_BYREF) == 0); - - // Array's map to object. - if (vt & VT_ARRAY) - return TypeHandle(CoreLibBinder::GetClass(CLASS__OBJECT)); - - // This is prety much a workaround because you cannot cast a CorElementType into a CVTYPE - if (vt > VT_RECORD || (type = (BinderClassID) map[vt]) == NO_MAPPING) - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_TYPE); - - return TypeHandle(CoreLibBinder::GetClass(type)); -} // CVTypes OleVariant::GetCVTypeForVarType() - -VARTYPE OleVariant::GetVarTypeForTypeHandle(TypeHandle type) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - // Handle primitive types. - CorElementType elemType = type.GetSignatureCorElementType(); - if (elemType <= ELEMENT_TYPE_R8) - return GetVarTypeForCorElementType(elemType); - - // Types incompatible with interop. - if (type.IsTypeDesc()) - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - - // Handle objects. - MethodTable * pMT = type.AsMethodTable(); - - if (pMT == g_pStringClass) - return VT_BSTR; - if (pMT == g_pObjectClass) - return VT_VARIANT; - - // We need to make sure the CVClasses table is populated. - if(CoreLibBinder::IsClass(pMT, CLASS__DATE_TIME)) - return VT_DATE; - if(CoreLibBinder::IsClass(pMT, CLASS__DECIMAL)) - return VT_DECIMAL; - -#ifdef HOST_64BIT - if (CoreLibBinder::IsClass(pMT, CLASS__INTPTR)) - return VT_I8; - if (CoreLibBinder::IsClass(pMT, CLASS__UINTPTR)) - return VT_UI8; -#else - if (CoreLibBinder::IsClass(pMT, CLASS__INTPTR)) - return VT_INT; - if (CoreLibBinder::IsClass(pMT, CLASS__UINTPTR)) - return VT_UINT; -#endif - -#ifdef FEATURE_COMINTEROP - // The wrapper types are only available when built-in COM is supported. - if (g_pConfig->IsBuiltInCOMSupported()) - { - if (CoreLibBinder::IsClass(pMT, CLASS__DISPATCH_WRAPPER)) - return VT_DISPATCH; - if (CoreLibBinder::IsClass(pMT, CLASS__UNKNOWN_WRAPPER)) - return VT_UNKNOWN; - if (CoreLibBinder::IsClass(pMT, CLASS__ERROR_WRAPPER)) - return VT_ERROR; - if (CoreLibBinder::IsClass(pMT, CLASS__CURRENCY_WRAPPER)) - return VT_CY; - if (CoreLibBinder::IsClass(pMT, CLASS__BSTR_WRAPPER)) - return VT_BSTR; - - // VariantWrappers cannot be stored in VARIANT's. - if (CoreLibBinder::IsClass(pMT, CLASS__VARIANT_WRAPPER)) - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - } -#endif // FEATURE_COMINTEROP - - if (pMT->IsEnum()) - return GetVarTypeForCorElementType(type.GetInternalCorElementType()); - - if (pMT->IsValueType()) - return VT_RECORD; - - if (pMT->IsArray()) - return VT_ARRAY; - -#ifdef FEATURE_COMINTEROP - // There is no VT corresponding to SafeHandles as they cannot be stored in - // VARIANTs or Arrays. The same applies to CriticalHandle. - if (type.CanCastTo(TypeHandle(CoreLibBinder::GetClass(CLASS__SAFE_HANDLE)))) - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - if (type.CanCastTo(TypeHandle(CoreLibBinder::GetClass(CLASS__CRITICAL_HANDLE)))) - COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - - if (pMT->IsInterface()) - { - CorIfaceAttr ifaceType = pMT->GetComInterfaceType(); - return static_cast(IsDispatchBasedItf(ifaceType) ? VT_DISPATCH : VT_UNKNOWN); - } - - TypeHandle hndDefItfClass; - DefaultInterfaceType DefItfType = GetDefaultInterfaceForClassWrapper(type, &hndDefItfClass); - switch (DefItfType) - { - case DefaultInterfaceType_Explicit: - { - CorIfaceAttr ifaceType = hndDefItfClass.GetMethodTable()->GetComInterfaceType(); - return static_cast(IsDispatchBasedItf(ifaceType) ? VT_DISPATCH : VT_UNKNOWN); - } - - case DefaultInterfaceType_AutoDual: - { - return VT_DISPATCH; - } - - case DefaultInterfaceType_IUnknown: - case DefaultInterfaceType_BaseComClass: - { - return VT_UNKNOWN; - } - - case DefaultInterfaceType_AutoDispatch: - { - return VT_DISPATCH; - } - - default: - { - _ASSERTE(!"Invalid default interface type!"); - } - } -#endif // FEATURE_COMINTEROP - - return VT_UNKNOWN; -} // // GetElementVarTypeForArrayRef returns the safearray variant type for the @@ -282,11 +39,9 @@ VARTYPE OleVariant::GetElementVarTypeForArrayRef(BASEARRAYREF pArrayRef) CONTRACTL_END; TypeHandle elemTypeHnd = pArrayRef->GetArrayElementTypeHandle(); - return(GetVarTypeForTypeHandle(elemTypeHnd)); + return(::GetVarTypeForTypeHandle(elemTypeHnd)); } -#ifdef FEATURE_COMINTEROP - BOOL OleVariant::IsValidArrayForSafeArrayElementType(BASEARRAYREF *pArrayRef, VARTYPE vtExpected) { CONTRACTL @@ -337,8 +92,6 @@ BOOL OleVariant::IsValidArrayForSafeArrayElementType(BASEARRAYREF *pArrayRef, VA } } -#endif // FEATURE_COMINTEROP - // // GetArrayClassForVarType returns the element class name and underlying method table // to use to represent an array with the given variant type. @@ -632,61 +385,6 @@ UINT OleVariant::GetElementSizeForVarType(VARTYPE vt, MethodTable *pInterfaceMT) } // -// GetElementSizeForVarType returns the a MethodTable* to a type that it blittable to the native -// element representation, or pManagedMT if vt represents a record (user-defined type). -// - -MethodTable* OleVariant::GetNativeMethodTableForVarType(VARTYPE vt, MethodTable* pManagedMT) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } - CONTRACTL_END; - - if (vt & VT_ARRAY) - { - return CoreLibBinder::GetClass(CLASS__INTPTR); - } - - switch (vt) - { - case VT_DATE: - return CoreLibBinder::GetClass(CLASS__DOUBLE); - case VT_CY: - return CoreLibBinder::GetClass(CLASS__CURRENCY); - case VT_BOOL: - return CoreLibBinder::GetClass(CLASS__INT16); - case VT_DISPATCH: - case VT_UNKNOWN: - case VT_LPSTR: - case VT_LPWSTR: - case VT_BSTR: - case VT_USERDEFINED: - case VT_SAFEARRAY: - case VT_CARRAY: - return CoreLibBinder::GetClass(CLASS__INTPTR); - case VT_VARIANT: - return CoreLibBinder::GetClass(CLASS__COMVARIANT); - case VT_UI2: - // When CharSet = CharSet.Unicode, System.Char arrays are marshaled as VT_UI2. - // However, since System.Char itself is CharSet.Ansi, the native size of - // System.Char is 1 byte instead of 2. So here we explicitly return System.UInt16's - // MethodTable to ensure the correct size. - return CoreLibBinder::GetClass(CLASS__UINT16); - case VT_DECIMAL: - return CoreLibBinder::GetClass(CLASS__DECIMAL); - default: - _ASSERTE(pManagedMT != NULL); - return pManagedMT; - } -} - -// -#ifdef FEATURE_COMINTEROP - void SafeVariantClear(VARIANT* pVar) { CONTRACTL @@ -753,7 +451,6 @@ class RecordVariantHolder : public Wrapper, Reco Wrapper, RecordVariantRelease, 0>::operator=(p); } }; -#endif // FEATURE_COMINTEROP /* ------------------------------------------------------------------------- * /* ------------------------------------------------------------------------- * @@ -764,7 +461,6 @@ class RecordVariantHolder : public Wrapper, Reco * Record marshaling routines * ------------------------------------------------------------------------- */ -#ifdef FEATURE_COMINTEROP void OleVariant::MarshalRecordVariantOleToObject(const VARIANT *pOleVariant, OBJECTREF * const & pObj) { @@ -817,9 +513,6 @@ void OleVariant::MarshalRecordVariantOleToObject(const VARIANT *pOleVariant, } GCPROTECT_END(); } -#endif // FEATURE_COMINTEROP - -#ifdef FEATURE_COMINTEROP // Warning! VariantClear's previous contents of pVarOut. void OleVariant::MarshalOleVariantForObject(OBJECTREF * const & pObj, VARIANT *pOle) @@ -1753,7 +1446,6 @@ void OleVariant::MarshalVarArgVariantArrayToOle(PTRARRAYREF *pClrArray, VARIANT /* ------------------------------------------------------------------------- * * Array marshaling routines * ------------------------------------------------------------------------- */ -#ifdef FEATURE_COMINTEROP void OleVariant::MarshalArrayVariantOleToObject(const VARIANT* pOleVariant, OBJECTREF * const & pObj) @@ -1863,7 +1555,6 @@ void OleVariant::MarshalArrayVariantOleRefToObject(const VARIANT *pOleVariant, SetObjectReference(pObj, NULL); } } -#endif //FEATURE_COMINTEROP /* ------------------------------------------------------------------------- * @@ -2208,7 +1899,6 @@ namespace return TypeHandle(CoreLibBinder::GetClass(CLASS__LPSTR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); } -#ifdef FEATURE_COMINTEROP case VT_CY: return CoreLibBinder::GetClass(CLASS__CURRENCY_ARRAY_ELEMENT_MARSHALER); @@ -2238,7 +1928,6 @@ namespace case VT_VARIANT: return CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); -#endif // FEATURE_COMINTEROP case VT_RECORD: { @@ -2280,7 +1969,6 @@ namespace case VT_BSTR: case VT_LPWSTR: case VT_LPSTR: return TypeHandle(g_pStringClass); -#ifdef FEATURE_COMINTEROP case VT_CY: return TypeHandle(CoreLibBinder::GetClass(CLASS__DECIMAL)); case VT_VARIANT: return TypeHandle(g_pObjectClass); case VT_UNKNOWN: @@ -2288,7 +1976,6 @@ namespace if (pElementMT == NULL || pElementMT == g_pObjectClass) return TypeHandle(g_pObjectClass); return TypeHandle(pElementMT); -#endif // FEATURE_COMINTEROP case VT_RECORD: _ASSERTE(pElementMT != NULL); return TypeHandle(pElementMT); @@ -2830,7 +2517,6 @@ TypeHandle OleVariant::GetArrayElementTypeWrapperAware(BASEARRAYREF *pArray) } } -#ifdef FEATURE_COMINTEROP TypeHandle OleVariant::GetElementTypeForRecordSafeArray(SAFEARRAY* pSafeArray) { CONTRACTL @@ -2846,7 +2532,6 @@ TypeHandle OleVariant::GetElementTypeForRecordSafeArray(SAFEARRAY* pSafeArray) COMPlusThrow(kArgumentException, IDS_EE_CANNOT_MAP_TO_MANAGED_VC); return TypeHandle(); // Unreachable } -#endif //FEATURE_COMINTEROP void OleVariant::ConvertBSTRToString(BSTR bstr, STRINGREF *pStringObj) { @@ -2909,5 +2594,4 @@ extern "C" void QCALLTYPE Variant_ConvertValueTypeToRecord(QCall::ObjectHandleOn END_QCALL; } -#endif // FEATURE_COMINTEROP diff --git a/src/coreclr/vm/olevariant.h b/src/coreclr/vm/olevariant.h index 022e145ba10153..dfc9bd536a9f6e 100644 --- a/src/coreclr/vm/olevariant.h +++ b/src/coreclr/vm/olevariant.h @@ -7,11 +7,14 @@ #ifndef _H_OLEVARIANT_ #define _H_OLEVARIANT_ +#ifndef FEATURE_COMINTEROP +#error FEATURE_COMINTEROP is required for this file +#endif // FEATURE_COMINTEROP + class OleVariant { public: -#ifdef FEATURE_COMINTEROP // New variant conversion static void MarshalOleVariantForObject(OBJECTREF * const & pObj, VARIANT *pOle); static void MarshalObjectForOleVariant(const VARIANT *pOle, OBJECTREF * const & pObj); @@ -22,9 +25,7 @@ class OleVariant static void MarshalObjectForOleVariantUncommon(const VARIANT *pOle, OBJECTREF * const & pObj); static void MarshalOleVariantForObjectUncommon(OBJECTREF * const & pObj, VARIANT *pOle); -#endif // FEATURE_COMINTEROP -#ifdef FEATURE_COMINTEROP // Safearray conversion static SAFEARRAY* CreateSafeArrayDescriptorForArrayRef(BASEARRAYREF* pArrayRef, VARTYPE vt, @@ -64,29 +65,20 @@ class OleVariant static HRESULT ClearAndInsertContentsIntoByrefRecordVariant(VARIANT* pOle, OBJECTREF* pObj); static BOOL IsValidArrayForSafeArrayElementType(BASEARRAYREF* pArrayRef, VARTYPE vtExpected); -#endif // FEATURE_COMINTEROP -#ifdef FEATURE_COMINTEROP static BOOL CheckVariant(VARIANT *pOle); // Type conversion utilities static void ExtractContentsFromByrefVariant(VARIANT* pByrefVar, VARIANT* pDestVar); static void InsertContentsIntoByRefVariant(VARIANT* pSrcVar, VARIANT* pByrefVar); static void CreateByrefVariantForVariant(VARIANT* pSrcVar, VARIANT* pByrefVar); -#endif // FEATURE_COMINTEROP - static TypeHandle GetTypeHandleForVarType(VARTYPE vt); - static VARTYPE GetVarTypeForTypeHandle(TypeHandle typeHnd); - - static VARTYPE GetVarTypeForValueClassArrayName(LPCUTF8 pArrayClassName); static VARTYPE GetElementVarTypeForArrayRef(BASEARRAYREF pArrayRef); // Note that Rank == 0 means SZARRAY (that is rank 1, no lower bounds) static TypeHandle GetArrayForVarType(VARTYPE vt, TypeHandle elemType, unsigned rank=0); static UINT GetElementSizeForVarType(VARTYPE vt, MethodTable* pInterfaceMT); - static MethodTable* GetNativeMethodTableForVarType(VARTYPE vt, MethodTable* pManagedMT); -#ifdef FEATURE_COMINTEROP // Determine the element type of the objects being wrapped by an array of wrappers. static TypeHandle GetWrappedArrayElementType(BASEARRAYREF* pArray); @@ -99,23 +91,16 @@ class OleVariant static TypeHandle GetElementTypeForRecordSafeArray(SAFEARRAY* pSafeArray); static void MarshalVarArgVariantArrayToOle(PTRARRAYREF* pComArray, VARIANT* oleArray); -#endif // FEATURE_COMINTEROP private: -#ifdef FEATURE_COMINTEROP static HRESULT MarshalCommonOleRefVariantForObject(OBJECTREF *pObj, VARIANT *pOle); -#ifdef FEATURE_COMINTEROP static void MarshalRecordVariantOleToObject(const VARIANT* pOleVariant, OBJECTREF * const & pComVariant); -#endif -#ifdef FEATURE_COMINTEROP static void MarshalArrayVariantOleToObject(const VARIANT* pOleVariant, OBJECTREF * const & pObj); static void MarshalArrayVariantOleRefToObject(const VARIANT* pOleVariant, OBJECTREF * const & pObj); static void MarshalArrayVariantObjectToOle(OBJECTREF * const & pObj, VARIANT* pOleVariant); -#endif -#endif // FEATURE_COMINTEROP }; // Returns the instantiated MethodDesc for a StubHelpers array marshalling method diff --git a/src/coreclr/vm/stubhelpers.cpp b/src/coreclr/vm/stubhelpers.cpp index 36dd859b5c7a84..2088fa3e06ad92 100644 --- a/src/coreclr/vm/stubhelpers.cpp +++ b/src/coreclr/vm/stubhelpers.cpp @@ -21,6 +21,7 @@ #ifdef FEATURE_COMINTEROP #include #include "olecontexthelpers.h" +#include "olevariant.h" #include "runtimecallablewrapper.h" #include "comcallablewrapper.h" #include "clrtocomcall.h" From 7175f678ad4cc4ce5506645ca0ee99aa6f94c48c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 10 Apr 2026 18:28:04 +0000 Subject: [PATCH 11/41] Remove legacy non-blittable structure array marshalling support in CoreLib. Adjust ifdefs for windows vs non-windows --- .../src/System/StubHelpers.cs | 135 +++--------------- src/coreclr/vm/corelib.h | 6 +- src/coreclr/vm/ilmarshalers.cpp | 6 +- 3 files changed, 23 insertions(+), 124 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 568227ff2fe7d4..292a4645a0e766 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1690,47 +1690,48 @@ public static unsafe void Free(byte* unmanaged) static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); } - internal sealed class CurrencyArrayElementMarshaler : IArrayElementMarshaler + internal sealed class BSTRArrayElementMarshaler : IArrayElementMarshaler { - public static unsafe void ConvertToUnmanaged(ref decimal managed, byte* unmanaged) + public static unsafe void ConvertToUnmanaged(ref string? managed, byte* unmanaged) { - *(Currency*)unmanaged = new Currency(managed); + *(IntPtr*)unmanaged = BSTRMarshaler.ConvertToNative(managed, IntPtr.Zero); } - public static unsafe void ConvertToManaged(ref decimal managed, byte* unmanaged) + public static unsafe void ConvertToManaged(ref string? managed, byte* unmanaged) { - managed = new decimal(*(Currency*)unmanaged); + managed = BSTRMarshaler.ConvertToManaged(*(IntPtr*)unmanaged); } public static unsafe void Free(byte* unmanaged) { + IntPtr bstr = *(IntPtr*)unmanaged; + if (bstr != IntPtr.Zero) + { + BSTRMarshaler.ClearNative(bstr); + } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(Currency); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); } - internal sealed class BSTRArrayElementMarshaler : IArrayElementMarshaler +#if FEATURE_COMINTEROP + internal sealed class CurrencyArrayElementMarshaler : IArrayElementMarshaler { - public static unsafe void ConvertToUnmanaged(ref string? managed, byte* unmanaged) + public static unsafe void ConvertToUnmanaged(ref decimal managed, byte* unmanaged) { - *(IntPtr*)unmanaged = BSTRMarshaler.ConvertToNative(managed, IntPtr.Zero); + *(Currency*)unmanaged = new Currency(managed); } - public static unsafe void ConvertToManaged(ref string? managed, byte* unmanaged) + public static unsafe void ConvertToManaged(ref decimal managed, byte* unmanaged) { - managed = BSTRMarshaler.ConvertToManaged(*(IntPtr*)unmanaged); + managed = new decimal(*(Currency*)unmanaged); } public static unsafe void Free(byte* unmanaged) { - IntPtr bstr = *(IntPtr*)unmanaged; - if (bstr != IntPtr.Zero) - { - BSTRMarshaler.ClearNative(bstr); - } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(Currency); } [SupportedOSPlatform("windows")] @@ -1881,6 +1882,7 @@ public static unsafe void Free(byte* unmanaged) static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(ComVariant); } +#endif // FEATURE_COMINTEROP internal interface IMarshalerOption { @@ -2338,105 +2340,6 @@ internal static void ThrowWrongSizeArrayInNativeStruct() throw new ArgumentException(SR.Argument_WrongSizeArrayInNativeStruct); } - private static readonly MemberInfo StructureMarshalerConvertToUnmanaged = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.ConvertToUnmanaged))!; - private static readonly MemberInfo StructureMarshalerConvertToManaged = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.ConvertToManaged))!; - private static readonly MemberInfo StructureMarshalerFree = typeof(StructureMarshaler<>).GetMethod(nameof(StructureMarshaler<>.Free))!; - - private sealed unsafe class StructureMarshalInfo - { - public delegate* ConvertToUnmanaged; - public delegate* ConvertToManaged; - public delegate* Free; - - public int ManagedSize; - } - - private static readonly ConditionalWeakTable s_structureMarshalInfoCache = []; - - private static unsafe StructureMarshalInfo GetStructureMarshalMethods(Type structureType) - { - return s_structureMarshalInfoCache.GetOrAdd(structureType, static structureType => - { - Type structureMarshalerType = typeof(StructureMarshaler<>).MakeGenericType(structureType); - var convertToUnmanagedMethodInfo = (MethodInfo)structureMarshalerType.GetMemberWithSameMetadataDefinitionAs(StructureMarshalerConvertToUnmanaged)!; - var convertToManagedMethodInfo = (MethodInfo)structureMarshalerType.GetMemberWithSameMetadataDefinitionAs(StructureMarshalerConvertToManaged)!; - var freeMethodInfo = (MethodInfo)structureMarshalerType.GetMemberWithSameMetadataDefinitionAs(StructureMarshalerFree)!; - - return new StructureMarshalInfo - { - ConvertToUnmanaged = (delegate*)convertToUnmanagedMethodInfo.MethodHandle.GetFunctionPointer(), - ConvertToManaged = (delegate*)convertToManagedMethodInfo.MethodHandle.GetFunctionPointer(), - Free = (delegate*)freeMethodInfo.MethodHandle.GetFunctionPointer(), - ManagedSize = RuntimeHelpers.SizeOf(structureType.TypeHandle) - }; - }); - } - - [UnmanagedCallersOnly] - internal static unsafe void NonBlittableStructureArrayConvertToUnmanaged(Array* managedArray, byte* pNative, MethodTable* pInterfaceMT, int nativeSize, Exception* pException) - { - try - { - StructureMarshalInfo marshalInfo = GetStructureMarshalMethods(RuntimeTypeHandle.GetRuntimeTypeFromHandle((IntPtr)pInterfaceMT)); - - nint length = (nint)managedArray->Length * (nint)marshalInfo.ManagedSize; - - for (ref byte managedElement = ref MemoryMarshal.GetArrayDataReference(*managedArray), end = ref Unsafe.AddByteOffset(ref managedElement, length); - Unsafe.IsAddressLessThan(ref managedElement, ref end); - managedElement = ref Unsafe.AddByteOffset(ref managedElement, marshalInfo.ManagedSize)) - { - marshalInfo.ConvertToUnmanaged(ref managedElement, pNative, nativeSize, ref Unsafe.NullRef()); - pNative += nativeSize; - } - } - catch (Exception ex) - { - *pException = ex; - } - } - - [UnmanagedCallersOnly] - internal static unsafe void NonBlittableStructureArrayConvertToManaged(Array* managedArray, byte* pNative, MethodTable* pInterfaceMT, int nativeSize, Exception* pException) - { - try - { - StructureMarshalInfo marshalInfo = GetStructureMarshalMethods(RuntimeTypeHandle.GetRuntimeTypeFromHandle((IntPtr)pInterfaceMT)); - - nint length = (nint)managedArray->Length * (nint)marshalInfo.ManagedSize; - - for (ref byte managedElement = ref MemoryMarshal.GetArrayDataReference(*managedArray), end = ref Unsafe.AddByteOffset(ref managedElement, length); - Unsafe.IsAddressLessThan(ref managedElement, ref end); - managedElement = ref Unsafe.AddByteOffset(ref managedElement, marshalInfo.ManagedSize)) - { - marshalInfo.ConvertToManaged(ref managedElement, pNative, ref Unsafe.NullRef()); - pNative += nativeSize; - } - } - catch (Exception ex) - { - *pException = ex; - } - } - - [UnmanagedCallersOnly] - internal static unsafe void NonBlittableStructureArrayFree(byte* pArray, nuint numElements, MethodTable* pInterfaceMT, int nativeSize, Exception* pException) - { - try - { - StructureMarshalInfo marshalInfo = GetStructureMarshalMethods(RuntimeTypeHandle.GetRuntimeTypeFromHandle((IntPtr)pInterfaceMT)); - - for (nuint i = 0; i < numElements; i++) - { - marshalInfo.Free(ref Unsafe.NullRef(), pArray, nativeSize, ref Unsafe.NullRef()); - pArray += nativeSize; - } - } - catch (Exception ex) - { - *pException = ex; - } - } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint="StubHelpers_MarshalToManagedVaList")] internal static partial void MarshalToManagedVaList(IntPtr va_list, IntPtr pArgIterator); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index be964015bb41a0..8701b3d8e3afa6 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1072,10 +1072,6 @@ DEFINE_METHOD(STUBHELPERS, CHECK_STRING_LENGTH, CheckStringLength, DEFINE_METHOD(STUBHELPERS, LAYOUT_TYPE_CONVERT_TO_UNMANAGED, LayoutTypeConvertToUnmanaged, SM_PtrObj_PtrByte_PtrException_RetVoid) DEFINE_METHOD(STUBHELPERS, LAYOUT_TYPE_CONVERT_TO_MANAGED, LayoutTypeConvertToManaged, SM_PtrObj_PtrByte_PtrException_RetVoid) -DEFINE_METHOD(STUBHELPERS, NONBLITTABLE_STRUCTURE_ARRAY_CONVERT_TO_UNMANAGED, NonBlittableStructureArrayConvertToUnmanaged, NoSig) -DEFINE_METHOD(STUBHELPERS, NONBLITTABLE_STRUCTURE_ARRAY_CONVERT_TO_MANAGED, NonBlittableStructureArrayConvertToManaged, NoSig) -DEFINE_METHOD(STUBHELPERS, NONBLITTABLE_STRUCTURE_ARRAY_FREE, NonBlittableStructureArrayFree, NoSig) - DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ConvertArrayContentsToUnmanaged, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_MANAGED, ConvertArrayContentsToManaged, NoSig) DEFINE_METHOD(STUBHELPERS, FREE_ARRAY_CONTENTS, FreeArrayContents, NoSig) @@ -1246,9 +1242,9 @@ DEFINE_CLASS(BOOL_MARSHALER, StubHelpers, BoolMarshaler`1) DEFINE_CLASS(LPWSTR_MARSHALER, StubHelpers, LPWSTRMarshaler) DEFINE_CLASS(ANSICHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, AnsiCharArrayElementMarshaler`2) DEFINE_CLASS(LPSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, LPSTRArrayElementMarshaler`2) +DEFINE_CLASS(BSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, BSTRArrayElementMarshaler) #ifdef FEATURE_COMINTEROP DEFINE_CLASS(CURRENCY_ARRAY_ELEMENT_MARSHALER, StubHelpers, CurrencyArrayElementMarshaler) -DEFINE_CLASS(BSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, BSTRArrayElementMarshaler) DEFINE_CLASS(INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, InterfaceArrayElementMarshaler`1) DEFINE_CLASS(TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, TypedInterfaceArrayElementMarshaler`1) DEFINE_CLASS(HETEROGENEOUS_INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, HeterogeneousInterfaceArrayElementMarshaler) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index ed419c90467623..d68be8aa582573 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4378,13 +4378,13 @@ MethodTable* ILArrayMarshalerBase::GetMarshalerMT() return TypeHandle(CoreLibBinder::GetClass(CLASS__LPSTR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); } + case VT_BSTR: + return CoreLibBinder::GetClass(CLASS__BSTR_ARRAY_ELEMENT_MARSHALER); + #ifdef FEATURE_COMINTEROP case VT_CY: return CoreLibBinder::GetClass(CLASS__CURRENCY_ARRAY_ELEMENT_MARSHALER); - case VT_BSTR: - return CoreLibBinder::GetClass(CLASS__BSTR_ARRAY_ELEMENT_MARSHALER); - case VT_UNKNOWN: case VT_DISPATCH: { From da721039a672df1dee728257b0e9d7615108884d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 10 Apr 2026 21:15:13 +0000 Subject: [PATCH 12/41] Various fixes to get tests passing in WSL --- .../src/System/StubHelpers.cs | 142 +++++++---- src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/ilmarshalers.cpp | 236 +++++++++++------- src/coreclr/vm/ilmarshalers.h | 9 +- 4 files changed, 241 insertions(+), 147 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 292a4645a0e766..a98e33ca4c9545 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -592,7 +592,7 @@ internal static void ThrowCriticalHandleFieldChanged() } } - internal sealed class DateMarshaler : IArrayElementMarshaler + internal sealed class DateMarshaler : IArrayElementMarshaler { internal static double ConvertToNative(DateTime managedDate) { @@ -604,21 +604,21 @@ internal static long ConvertToManaged(double nativeDate) return DateTime.DoubleDateToTicks(nativeDate); } - static unsafe void IArrayElementMarshaler.ConvertToUnmanaged(ref DateTime managed, byte* unmanaged) + static unsafe void IArrayElementMarshaler.ConvertToUnmanaged(ref DateTime managed, byte* unmanaged) { Unsafe.WriteUnaligned(unmanaged, ConvertToNative(managed)); } - static unsafe void IArrayElementMarshaler.ConvertToManaged(ref DateTime managed, byte* unmanaged) + static unsafe void IArrayElementMarshaler.ConvertToManaged(ref DateTime managed, byte* unmanaged) { managed = new DateTime(ConvertToManaged(Unsafe.ReadUnaligned(unmanaged))); } - static unsafe void IArrayElementMarshaler.Free(byte* unmanaged) + static unsafe void IArrayElementMarshaler.Free(byte* unmanaged) { } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(double); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(double); } // class DateMarshaler #if FEATURE_COMINTEROP @@ -944,7 +944,7 @@ or TypeCode.UInt64 or TypeCode.Single or TypeCode.Double => [elementType, typeof(StructureMarshaler<>).MakeGenericType(elementType)], TypeCode.Object when elementType == typeof(nint) || elementType == typeof(nuint) => [elementType, typeof(StructureMarshaler<>).MakeGenericType(elementType)], - TypeCode.Char when !IsAnsi(dwFlags) => [typeof(char), typeof(StructureMarshaler)], + TypeCode.Char when !IsAnsi(dwFlags) => [typeof(char), typeof(UnicodeCharArrayElementMarshaler)], TypeCode.Char when IsAnsi(dwFlags) => [ typeof(char), typeof(AnsiCharArrayElementMarshaler<,>).MakeGenericType([ @@ -985,7 +985,7 @@ TypeCode.Char when IsAnsi(dwFlags) => [ if (IsIn(dwFlags)) { - arrayMarshalerMethods.convertContentsToNative.Invoke(pManagedHome, Pointer.Box(pNativeHome, typeof(byte*))); + arrayMarshalerMethods.convertContentsToNative.Invoke(null, pManagedHome, Pointer.Box(pNativeHome, typeof(byte*))); } if (IsOut(dwFlags)) @@ -1178,7 +1178,7 @@ internal unsafe void ConvertToManaged(object pManagedHome, IntPtr pNativeHome) { case BackPropAction.Array: { - arrayMarshalerMethods.convertContentsToManaged.Invoke(pManagedHome, pNativeHome); + arrayMarshalerMethods.convertContentsToManaged.Invoke(null, pManagedHome, pNativeHome); break; } @@ -1239,13 +1239,19 @@ internal void ClearNative(IntPtr pNativeHome) } } // struct AsAnyMarshaler - internal interface IArrayElementMarshaler + internal interface IArrayElementMarshaler where TSelf : IArrayElementMarshaler { static abstract unsafe void ConvertToUnmanaged(ref T managed, byte* unmanaged); static abstract unsafe void ConvertToManaged(ref T managed, byte* unmanaged); static abstract unsafe void Free(byte* unmanaged); static abstract nuint UnmanagedSize { get; } + + // Space to allocate per element. Defaults to UnmanagedSize but may be larger + // when conversion can produce more bytes than the final native layout (e.g. + // ANSI char needs SystemMaxDBCSCharSize bytes during conversion but only + // occupies 1 byte in the native struct). + static virtual nuint AllocationSize => TSelf.UnmanagedSize; } // Constants for direction argument of struct marshalling stub. @@ -1256,24 +1262,24 @@ internal static class MarshalOperation internal const int Free = 2; } - internal sealed unsafe class StructureMarshaler : IArrayElementMarshaler where T : notnull + internal sealed unsafe class StructureMarshaler : IArrayElementMarshaler> where T : notnull { - static unsafe void IArrayElementMarshaler.ConvertToManaged(ref T managed, byte* unmanaged) + static unsafe void IArrayElementMarshaler>.ConvertToManaged(ref T managed, byte* unmanaged) { ConvertToManaged(ref managed, unmanaged, ref Unsafe.NullRef()); } - static unsafe void IArrayElementMarshaler.ConvertToUnmanaged(ref T managed, byte* unmanaged) + static unsafe void IArrayElementMarshaler>.ConvertToUnmanaged(ref T managed, byte* unmanaged) { ConvertToUnmanaged(ref managed, unmanaged, ref Unsafe.NullRef()); } - static unsafe void IArrayElementMarshaler.Free(byte* unmanaged) + static unsafe void IArrayElementMarshaler>.Free(byte* unmanaged) { Free(ref Unsafe.NullRef(), unmanaged, ref Unsafe.NullRef()); } - static nuint IArrayElementMarshaler.UnmanagedSize => (nuint)s_nativeSize; + static nuint IArrayElementMarshaler>.UnmanagedSize => (nuint)s_nativeSize; private static readonly int s_nativeSize; @@ -1343,7 +1349,7 @@ public static void Free(ref T managed, byte* unmanaged, ref CleanupWorkListEleme } } - internal sealed unsafe class LayoutClassMarshaler : IArrayElementMarshaler where T : notnull + internal sealed unsafe class LayoutClassMarshaler : IArrayElementMarshaler> where T : notnull { // We use a nested Methods class with properties that unwrap the TypeInitializationException // to ensure that users see a TypeLoadException if the type has a recursive native layout. @@ -1486,25 +1492,37 @@ public static void Free(T? managed, byte* unmanaged, ref CleanupWorkListElement? private static nuint NativeSize { [MethodImpl(MethodImplOptions.NoInlining)] - get => Methods.NativeSize; + get + { + try + { + return Methods.NativeSize; + } + catch (TypeInitializationException ex) + { + ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw(); + // Unreachable + return 0; + } + } } - static unsafe void IArrayElementMarshaler.ConvertToManaged(ref T managed, byte* unmanaged) + static unsafe void IArrayElementMarshaler>.ConvertToManaged(ref T managed, byte* unmanaged) { ConvertToManaged(managed, unmanaged, ref Unsafe.NullRef()); } - static unsafe void IArrayElementMarshaler.ConvertToUnmanaged(ref T managed, byte* unmanaged) + static unsafe void IArrayElementMarshaler>.ConvertToUnmanaged(ref T managed, byte* unmanaged) { ConvertToUnmanaged(managed, unmanaged, ref Unsafe.NullRef()); } - static unsafe void IArrayElementMarshaler.Free(byte* unmanaged) + static unsafe void IArrayElementMarshaler>.Free(byte* unmanaged) { Free(default, unmanaged, ref Unsafe.NullRef()); } - static nuint IArrayElementMarshaler.UnmanagedSize => NativeSize; + static nuint IArrayElementMarshaler>.UnmanagedSize => NativeSize; } // Marshaller for layout classes and boxed structs. @@ -1554,7 +1572,7 @@ public static void Free(object? managed, byte* unmanaged, ref CleanupWorkListEle } } - internal sealed class VariantBoolMarshaler : IArrayElementMarshaler + internal sealed class VariantBoolMarshaler : IArrayElementMarshaler { private const ushort VARIANT_TRUE = unchecked((ushort)-1); private const ushort VARIANT_FALSE = 0; @@ -1574,10 +1592,10 @@ public static unsafe void Free(byte* unmanaged) // Nothing to free for VARIANT_BOOL. } - static nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(short); + static nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(short); } - internal sealed class BoolMarshaler : IArrayElementMarshaler where TUnmanaged : unmanaged, INumberBase + internal sealed class BoolMarshaler : IArrayElementMarshaler> where TUnmanaged : unmanaged, INumberBase { public static unsafe void ConvertToUnmanaged(ref bool managed, byte* unmanaged) { @@ -1597,10 +1615,10 @@ public static unsafe void Free(byte* unmanaged) // Nothing to free for boolean values. } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(TUnmanaged); + static unsafe nuint IArrayElementMarshaler>.UnmanagedSize => (nuint)sizeof(TUnmanaged); } - internal sealed class LPWSTRMarshaler : IArrayElementMarshaler + internal sealed class LPWSTRMarshaler : IArrayElementMarshaler { public static unsafe void ConvertToUnmanaged(ref string? managed, byte* unmanaged) { @@ -1639,10 +1657,10 @@ public static unsafe void Free(byte* unmanaged) } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); } - internal sealed class AnsiCharArrayElementMarshaler : IArrayElementMarshaler + internal sealed class AnsiCharArrayElementMarshaler : IArrayElementMarshaler> where TBestFit : IMarshalerOption where TThrowOnUnmappable : IMarshalerOption { @@ -1660,10 +1678,31 @@ public static unsafe void Free(byte* unmanaged) { } - static nuint IArrayElementMarshaler.UnmanagedSize => (nuint)Marshal.SystemMaxDBCSCharSize; + static nuint IArrayElementMarshaler>.UnmanagedSize => sizeof(byte); + + static nuint IArrayElementMarshaler>.AllocationSize => (nuint)Marshal.SystemMaxDBCSCharSize; + } + + internal sealed class UnicodeCharArrayElementMarshaler : IArrayElementMarshaler + { + public static unsafe void ConvertToUnmanaged(ref char managed, byte* unmanaged) + { + *(char*)unmanaged = managed; + } + + public static unsafe void ConvertToManaged(ref char managed, byte* unmanaged) + { + managed = *(char*)unmanaged; + } + + public static unsafe void Free(byte* unmanaged) + { + } + + static nuint IArrayElementMarshaler.UnmanagedSize => sizeof(char); } - internal sealed class LPSTRArrayElementMarshaler : IArrayElementMarshaler + internal sealed class LPSTRArrayElementMarshaler : IArrayElementMarshaler> where TBestFit : IMarshalerOption where TThrowOnUnmappable : IMarshalerOption { @@ -1687,10 +1726,10 @@ public static unsafe void Free(byte* unmanaged) } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + static unsafe nuint IArrayElementMarshaler>.UnmanagedSize => (nuint)sizeof(IntPtr); } - internal sealed class BSTRArrayElementMarshaler : IArrayElementMarshaler + internal sealed class BSTRArrayElementMarshaler : IArrayElementMarshaler { public static unsafe void ConvertToUnmanaged(ref string? managed, byte* unmanaged) { @@ -1711,11 +1750,11 @@ public static unsafe void Free(byte* unmanaged) } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); } #if FEATURE_COMINTEROP - internal sealed class CurrencyArrayElementMarshaler : IArrayElementMarshaler + internal sealed class CurrencyArrayElementMarshaler : IArrayElementMarshaler { public static unsafe void ConvertToUnmanaged(ref decimal managed, byte* unmanaged) { @@ -1731,11 +1770,11 @@ public static unsafe void Free(byte* unmanaged) { } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(Currency); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(Currency); } [SupportedOSPlatform("windows")] - internal sealed class InterfaceArrayElementMarshaler : IArrayElementMarshaler + internal sealed class InterfaceArrayElementMarshaler : IArrayElementMarshaler> where TIsDispatch : IMarshalerOption { public static unsafe void ConvertToUnmanaged(ref object? managed, byte* unmanaged) @@ -1776,11 +1815,11 @@ public static unsafe void Free(byte* unmanaged) } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + static unsafe nuint IArrayElementMarshaler>.UnmanagedSize => (nuint)sizeof(IntPtr); } [SupportedOSPlatform("windows")] - internal sealed class TypedInterfaceArrayElementMarshaler : IArrayElementMarshaler + internal sealed class TypedInterfaceArrayElementMarshaler : IArrayElementMarshaler> where T : class { public static unsafe void ConvertToUnmanaged(ref T? managed, byte* unmanaged) @@ -1817,11 +1856,11 @@ public static unsafe void Free(byte* unmanaged) } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + static unsafe nuint IArrayElementMarshaler>.UnmanagedSize => (nuint)sizeof(IntPtr); } [SupportedOSPlatform("windows")] - internal sealed class HeterogeneousInterfaceArrayElementMarshaler : IArrayElementMarshaler + internal sealed class HeterogeneousInterfaceArrayElementMarshaler : IArrayElementMarshaler { public static unsafe void ConvertToUnmanaged(ref object? managed, byte* unmanaged) { @@ -1860,10 +1899,10 @@ public static unsafe void Free(byte* unmanaged) } } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); } - internal sealed class VariantArrayElementMarshaler : IArrayElementMarshaler + internal sealed class VariantArrayElementMarshaler : IArrayElementMarshaler { public static unsafe void ConvertToUnmanaged(ref object? managed, byte* unmanaged) { @@ -1880,7 +1919,7 @@ public static unsafe void Free(byte* unmanaged) ObjectMarshaler.ClearNative((IntPtr)unmanaged); } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(ComVariant); + static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(ComVariant); } #endif // FEATURE_COMINTEROP @@ -2255,7 +2294,7 @@ internal static unsafe void LayoutTypeConvertToManaged(object* obj, byte* pNativ } } - public static unsafe void ConvertArrayContentsToUnmanaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToUnmanaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler { for (int i = 0; i < managed.Length; i++) { @@ -2264,7 +2303,7 @@ public static unsafe void ConvertArrayContentsToUnmanaged(T[] man } } - public static unsafe void ConvertArrayContentsToManaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToManaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler { for (int i = 0; i < managed.Length; i++) { @@ -2290,7 +2329,7 @@ internal static unsafe void InvokeArrayContentsConverter( } } - internal static unsafe void FreeArrayContents(byte* pNative, int length) where TMarshaler : IArrayElementMarshaler + internal static unsafe void FreeArrayContents(byte* pNative, int length) where TMarshaler : IArrayElementMarshaler { for (int i = 0; i < length; i++) { @@ -2299,8 +2338,8 @@ internal static unsafe void FreeArrayContents(byte* pNative, int } } - internal static unsafe byte* ConvertArraySpaceToNative(T[]? managed) - where TMarshaler : IArrayElementMarshaler + public static unsafe byte* ConvertArraySpaceToNative(T[]? managed) + where TMarshaler : IArrayElementMarshaler { if (managed is null) { @@ -2308,12 +2347,15 @@ internal static unsafe void FreeArrayContents(byte* pNative, int } else { - return (byte*)Marshal.AllocCoTaskMem(checked(managed.Length * (int)TMarshaler.UnmanagedSize)); + int nativeBytes = checked(managed.Length * (int)TMarshaler.AllocationSize); + byte* pNative = (byte*)Marshal.AllocCoTaskMem(nativeBytes); + NativeMemory.Clear(pNative, (nuint)nativeBytes); + return pNative; } } internal static unsafe T[]? ConvertArraySpaceToManaged(byte* pNativeHome, int cElements) - where TMarshaler : IArrayElementMarshaler + where TMarshaler : IArrayElementMarshaler { if (pNativeHome == null) { @@ -2326,7 +2368,7 @@ internal static unsafe void FreeArrayContents(byte* pNative, int } internal static unsafe void ClearArrayNative(byte* pNativeHome, int cElements) - where TMarshaler : IArrayElementMarshaler + where TMarshaler : IArrayElementMarshaler { if (pNativeHome != null) { diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 8701b3d8e3afa6..bc295c7797e857 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1241,6 +1241,7 @@ DEFINE_CLASS(VARIANT_BOOL_MARSHALER, StubHelpers, VariantBoolMarshale DEFINE_CLASS(BOOL_MARSHALER, StubHelpers, BoolMarshaler`1) DEFINE_CLASS(LPWSTR_MARSHALER, StubHelpers, LPWSTRMarshaler) DEFINE_CLASS(ANSICHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, AnsiCharArrayElementMarshaler`2) +DEFINE_CLASS(UNICODECHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, UnicodeCharArrayElementMarshaler) DEFINE_CLASS(LPSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, LPSTRArrayElementMarshaler`2) DEFINE_CLASS(BSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, BSTRArrayElementMarshaler) #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index d68be8aa582573..f32e6205b693fb 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -1115,11 +1115,9 @@ void ILValueClassMarshaler::EmitClearNative(ILCodeStream * pslILEmit) EmitLoadManagedHomeAddr(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); - pslILEmit->EmitLDC(m_pargs->m_pMT->GetNativeLayoutInfo()->GetSize()); EmitLoadCleanupWorkList(pslILEmit); - - pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__STRUCTURE_MARSHALER__FREE, m_pargs->m_pMT)), 4, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__STRUCTURE_MARSHALER__FREE, m_pargs->m_pMT)), 3, 0); } @@ -1131,7 +1129,7 @@ void ILValueClassMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEm EmitLoadNativeHomeAddr(pslILEmit); EmitLoadCleanupWorkList(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__STRUCTURE_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 4, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__STRUCTURE_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 3, 0); } void ILValueClassMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) @@ -2328,10 +2326,9 @@ void ILLayoutClassPtrMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* psl EmitLoadManagedValue(pslILEmit); EmitLoadNativeValue(pslILEmit); - pslILEmit->EmitLDC(m_pargs->m_pMT->GetNativeLayoutInfo()->GetSize()); EmitLoadCleanupWorkList(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 4, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 3, 0); if (emittedTypeCheck) { @@ -2387,10 +2384,9 @@ void ILLayoutClassPtrMarshaler::EmitClearNativeContents(ILCodeStream * pslILEmit EmitLoadManagedValue(pslILEmit); EmitLoadNativeValue(pslILEmit); - pslILEmit->EmitLDC(m_pargs->m_pMT->GetNativeLayoutInfo()->GetSize()); EmitLoadCleanupWorkList(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__FREE, m_pargs->m_pMT)), 4, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__FREE, m_pargs->m_pMT)), 3, 0); if (emittedTypeCheck) { @@ -2536,7 +2532,7 @@ void ILLayoutClassMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILE EmitLoadNativeHomeAddr(pslILEmit); EmitLoadCleanupWorkList(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 4, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__CONVERT_TO_UNMANAGED, m_pargs->m_pMT)), 3, 0); pslILEmit->EmitLabel(pNullRefLabel); } @@ -2568,7 +2564,7 @@ void ILLayoutClassMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) EmitLoadNativeHomeAddr(pslILEmit); EmitLoadCleanupWorkList(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__FREE, m_pargs->m_pMT)), 4, 0); + pslILEmit->EmitCALL(pslILEmit->GetToken(GetStructMarshalingMethod(METHOD__LAYOUTCLASS_MARSHALER__FREE, m_pargs->m_pMT)), 3, 0); } @@ -4136,7 +4132,7 @@ void ILNativeArrayMarshaler::EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_MANAGED); - EmitLoadNativeHomeAddr(pslILEmit); + EmitLoadNativeValue(pslILEmit); // Dynamically calculate element count using SizeParamIndex argument EmitLoadElementCount(pslILEmit); @@ -4175,9 +4171,9 @@ void ILNativeArrayMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_NATIVE); - EmitLoadManagedHomeAddr(pslILEmit); - EmitLoadNativeHomeAddr(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 1, 1); + EmitStoreNativeValue(pslILEmit); } void ILNativeArrayMarshaler::EmitClearNative(ILCodeStream* pslILEmit) @@ -4186,7 +4182,7 @@ void ILNativeArrayMarshaler::EmitClearNative(ILCodeStream* pslILEmit) MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CLEAR_ARRAY_NATIVE); - EmitLoadNativeHomeAddr(pslILEmit); + EmitLoadNativeValue(pslILEmit); EmitLoadNativeSize(pslILEmit); pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); } @@ -4214,6 +4210,39 @@ void ILNativeArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) } } +void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); + + EmitLoadManagedValue(pslILEmit); + EmitLoadNativeValue(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + +void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); + + EmitLoadManagedValue(pslILEmit); + EmitLoadNativeValue(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + +void ILNativeArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__FREE_ARRAY_CONTENTS); + + EmitLoadNativeValue(pslILEmit); + EmitLoadNativeSize(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + void ILArrayMarshalerBase::EmitClearNativeContents(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; @@ -4243,7 +4272,7 @@ void ILNativeArrayMarshaler::EmitNewSavedSizeArgLocal(ILCodeStream* pslILEmit) pslILEmit->EmitSTLOC(m_dwSavedSizeArg); } -MethodTable* ILArrayMarshalerBase::GetMarshalerMT() +void ILArrayMarshalerBase::GetMarshalerAndElementTypes(MethodTable** ppMarshalerMT, TypeHandle* pElementType) { STANDARD_VM_CONTRACT; @@ -4253,12 +4282,24 @@ MethodTable* ILArrayMarshalerBase::GetMarshalerMT() bool bestFit = mops.bestfitmapping != 0; bool throwOnUnmappable = mops.throwonunmappablechar != 0; + // Start from the managed element type - this is the authoritative source. + MethodTable* pElementMT = mops.methodTable; + + // Enums marshal as their underlying primitive type. + if (pElementMT->IsEnum()) + { + pElementMT = CoreLibBinder::GetElementType(pElementMT->GetInternalCorElementType()); + } + + TypeHandle thElement(pElementMT); + MethodTable* pEnabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_ENABLED); MethodTable* pDisabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); MethodTable* pBestFitMT = bestFit ? pEnabledMT : pDisabledMT; MethodTable* pThrowOnUnmappableMT = throwOnUnmappable ? pEnabledMT : pDisabledMT; - // Handle non-VARTYPE marshalers identified by CorNativeType. + // Handle explicit CorNativeType overrides first. These override the default + // marshaling for the element type (e.g. bool as NATIVE_TYPE_BOOLEAN vs VT_BOOL). CorNativeType nt = mops.elementNativeType; if (nt != NATIVE_TYPE_DEFAULT) { @@ -4266,124 +4307,122 @@ MethodTable* ILArrayMarshalerBase::GetMarshalerMT() { case NATIVE_TYPE_BOOLEAN: { - _ASSERTE(m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle() == TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN))); + _ASSERTE(thElement == TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN))); TypeHandle thInt32 = CoreLibBinder::GetClass(CLASS__INT32); - return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thInt32, 1)).AsMethodTable(); + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thInt32, 1)).AsMethodTable(); + return; } case NATIVE_TYPE_I1: { - _ASSERTE(m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle() == TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN))); + _ASSERTE(thElement == TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN))); TypeHandle thByte = CoreLibBinder::GetClass(CLASS__BYTE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thByte, 1)).AsMethodTable(); + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__BOOL_MARSHALER)).Instantiate(Instantiation(&thByte, 1)).AsMethodTable(); + return; } case NATIVE_TYPE_U1: { - _ASSERTE(m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle() == TypeHandle(CoreLibBinder::GetClass(CLASS__CHAR))); + _ASSERTE(thElement == TypeHandle(CoreLibBinder::GetClass(CLASS__CHAR))); TypeHandle thArgs[2] = { TypeHandle(pBestFitMT), TypeHandle(pThrowOnUnmappableMT) }; - return TypeHandle(CoreLibBinder::GetClass(CLASS__ANSICHAR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__ANSICHAR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + return; } default: _ASSERTE(!"Unsupported CorNativeType for ILArrayMarshalerBase"); COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - return NULL; + return; } } + // Use the VT only to select the marshaler pattern; element type comes from the + // managed side so enums, char, etc. are handled naturally. switch (vt) { case VT_I1: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__SBYTE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_UI1: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__BYTE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_I2: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__INT16); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - - case VT_UI2: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__UINT16); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_I4: case VT_INT: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__INT32); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_UI4: case VT_UINT: case VT_ERROR: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__UINT32); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_I8: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__INT64); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_UI8: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__UINT64); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_R4: - { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__SINGLE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } - case VT_R8: + case VT_DECIMAL: { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__DOUBLE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + return; } - case VT_DECIMAL: + case VT_UI2: { - TypeHandle thElement = CoreLibBinder::GetClass(CLASS__DECIMAL); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + // System.Char arrays use a dedicated marshaler; other VT_UI2 types (UInt16) + // use the standard StructureMarshaler. + if (thElement == TypeHandle(CoreLibBinder::GetClass(CLASS__CHAR))) + { + *pElementType = thElement; + *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__UNICODECHAR_ARRAY_ELEMENT_MARSHALER); + } + else + { + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + return; } case VT_BOOL: - return CoreLibBinder::GetClass(CLASS__VARIANT_BOOL_MARSHALER); + { + _ASSERTE(thElement == TypeHandle(CoreLibBinder::GetClass(CLASS__BOOLEAN))); + *pElementType = thElement; + *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__VARIANT_BOOL_MARSHALER); + return; + } case VT_DATE: - return CoreLibBinder::GetClass(CLASS__DATEMARSHALER); + { + *pElementType = thElement; + *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__DATEMARSHALER); + return; + } case VT_LPWSTR: - return CoreLibBinder::GetClass(CLASS__LPWSTR_MARSHALER); + { + *pElementType = thElement; + *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__LPWSTR_MARSHALER); + return; + } case VT_LPSTR: { TypeHandle thArgs[2] = { TypeHandle(pBestFitMT), TypeHandle(pThrowOnUnmappableMT) }; - return TypeHandle(CoreLibBinder::GetClass(CLASS__LPSTR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__LPSTR_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(thArgs, 2)).AsMethodTable(); + return; } case VT_BSTR: - return CoreLibBinder::GetClass(CLASS__BSTR_ARRAY_ELEMENT_MARSHALER); + { + *pElementType = thElement; + *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__BSTR_ARRAY_ELEMENT_MARSHALER); + return; + } #ifdef FEATURE_COMINTEROP case VT_CY: - return CoreLibBinder::GetClass(CLASS__CURRENCY_ARRAY_ELEMENT_MARSHALER); + { + *pElementType = thElement; + *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__CURRENCY_ARRAY_ELEMENT_MARSHALER); + return; + } case VT_UNKNOWN: case VT_DISPATCH: @@ -4392,46 +4431,53 @@ MethodTable* ILArrayMarshalerBase::GetMarshalerMT() if (arrayElementTypeHandle == TypeHandle(g_pObjectClass)) { TypeHandle thDispatch(vt == VT_DISPATCH ? pEnabledMT : pDisabledMT); - return TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); + *pElementType = TypeHandle(g_pObjectClass); + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); } else { - return TypeHandle(CoreLibBinder::GetClass(CLASS__TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&arrayElementTypeHandle, 1)).AsMethodTable(); + *pElementType = arrayElementTypeHandle; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&arrayElementTypeHandle, 1)).AsMethodTable(); } + return; } case VT_VARIANT: - return CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); + { + *pElementType = TypeHandle(g_pObjectClass); + *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); + return; + } #endif // FEATURE_COMINTEROP case VT_RECORD: { - TypeHandle thElement(mops.methodTable); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + return; } default: _ASSERTE(!"Unsupported VT for ILArrayMarshalerBase"); COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); - return NULL; + return; } } + MethodDesc* ILArrayMarshalerBase::GetInstantiatedArrayMethod(BinderMethodID methodId) { STANDARD_VM_CONTRACT; MethodDesc* pGenericMD = CoreLibBinder::GetMethod(methodId); - CREATE_MARSHALER_CARRAY_OPERANDS mops; - m_pargs->m_pMarshalInfo->GetMops(&mops); - - // Determine the managed element type T from the array's element MethodTable. - TypeHandle thElementType(mops.methodTable); - - // Determine the marshaler type TMarshaler. - TypeHandle thMarshalerType(GetMarshalerMT()); + // Get both the element type and marshaler type from a single source + // to guarantee they are consistent (e.g. enums map to their underlying type). + TypeHandle thElementType; + MethodTable* pMarshalerMT; + GetMarshalerAndElementTypes(&pMarshalerMT, &thElementType); + TypeHandle thMarshalerType(pMarshalerMT); TypeHandle thArgs[2] = { thElementType, thMarshalerType }; MethodDesc* pInstMD = MethodDesc::FindOrCreateAssociatedMethodDesc( diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index 77a908be34bc67..c85662e5270e5d 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3296,8 +3296,10 @@ class ILArrayMarshalerBase : public ILMarshaler void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; void EmitClearNativeContents(ILCodeStream* pslILEmit) override; - // Resolve the managed marshaler MethodTable for the given VT/element type. - MethodTable* GetMarshalerMT(); + // Resolve the managed marshaler MethodTable and the element type it marshals. + // Both are returned together to guarantee they are consistent (e.g. for enums, + // the element type is the underlying primitive, matching the marshaler's T). + void GetMarshalerAndElementTypes(MethodTable** ppMarshalerMT, TypeHandle* pElementType); // Instantiate one of the generic StubHelpers array methods with the element type and marshaler type. MethodDesc* GetInstantiatedArrayMethod(BinderMethodID methodId); @@ -3328,7 +3330,10 @@ class ILNativeArrayMarshaler : public ILArrayMarshalerBase void EmitSetupArgumentForMarshalling(ILCodeStream* pslILEmit) override; void EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) override; void EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) override; + void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; + void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; void EmitClearNative(ILCodeStream* pslILEmit) override; + void EmitClearNativeContents(ILCodeStream* pslILEmit) override; bool SupportsFieldMarshal(UINT* pErrorResID) override { From 01fcc73374a8f754e465417df8c3483e29f79d8d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 10 Apr 2026 16:12:19 -0700 Subject: [PATCH 13/41] Support multidimensional arrays in managed SafeArray marshalling The new managed array marshalling helpers (ConvertArrayContentsToUnmanaged/ ConvertArrayContentsToManaged) accept T[] parameters, which only work for single-dimensional zero-lower-bound arrays. SafeArrays can be multidimensional or have non-zero lower bounds, producing general Array objects that cannot be cast to T[]. Add SafeArray-specific overloads (ConvertSafeArrayContentsToUnmanaged/ ConvertSafeArrayContentsToManaged) that accept Array and use MemoryMarshal.GetArrayDataReference to access elements contiguously regardless of array rank or lower bounds. Update all SafeArray marshalling callsites (ilmarshalers.cpp, olevariant.cpp, dispparammarshaler.cpp, dispatchinfo.cpp) to use the new methods. Add tests for 2D SafeArray marshalling with int, bool, and string types, including round-trip verification. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 27 +++ src/coreclr/vm/corelib.h | 2 + src/coreclr/vm/dispatchinfo.cpp | 2 +- src/coreclr/vm/dispparammarshaler.cpp | 8 +- src/coreclr/vm/ilmarshalers.cpp | 4 +- src/coreclr/vm/olevariant.cpp | 6 +- .../SafeArray/SafeArrayNative.cpp | 199 ++++++++++++++++++ .../SafeArray/SafeArrayTest.cs | 141 +++++++++++++ 8 files changed, 379 insertions(+), 10 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index a98e33ca4c9545..145e8e0e007125 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -2312,6 +2312,33 @@ public static unsafe void ConvertArrayContentsToManaged(T[] manag } } + // SafeArray-specific overloads that accept Array instead of T[]. + // SafeArrays can be multidimensional or have non-zero lower bounds, + // producing general Array objects that are not T[]. + // CLR arrays store elements contiguously regardless of rank, + // so we use MemoryMarshal.GetArrayDataReference to access the data directly. + public static unsafe void ConvertSafeArrayContentsToUnmanaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + { + ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); + int length = managed.Length; + for (int i = 0; i < length; i++) + { + TMarshaler.ConvertToUnmanaged(ref Unsafe.Add(ref firstElement, i), pNative); + pNative += TMarshaler.UnmanagedSize; + } + } + + public static unsafe void ConvertSafeArrayContentsToManaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + { + ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); + int length = managed.Length; + for (int i = 0; i < length; i++) + { + TMarshaler.ConvertToManaged(ref Unsafe.Add(ref firstElement, i), pNative); + pNative += TMarshaler.UnmanagedSize; + } + } + [UnmanagedCallersOnly] internal static unsafe void InvokeArrayContentsConverter( Array* pManagedArray, diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index bc295c7797e857..96bcb9e7b6eacd 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1074,6 +1074,8 @@ DEFINE_METHOD(STUBHELPERS, LAYOUT_TYPE_CONVERT_TO_MANAGED, LayoutTypeCo DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ConvertArrayContentsToUnmanaged, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_MANAGED, ConvertArrayContentsToManaged, NoSig) +DEFINE_METHOD(STUBHELPERS, CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ConvertSafeArrayContentsToUnmanaged, NoSig) +DEFINE_METHOD(STUBHELPERS, CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, ConvertSafeArrayContentsToManaged, NoSig) DEFINE_METHOD(STUBHELPERS, FREE_ARRAY_CONTENTS, FreeArrayContents, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_NATIVE, ConvertArraySpaceToNative, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_MANAGED, ConvertArraySpaceToManaged, NoSig) diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index d217a98eb3c0bf..d2d9e02d3d5634 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -2154,7 +2154,7 @@ void DispatchInfo::MarshalParamManagedToNativeRef(DispatchMemberInfo *pMemberInf MethodTable *pElementMT = (*(BASEARRAYREF *)pSrcObj)->GetArrayElementTypeHandle().GetMethodTable(); // Convert the contents of the managed array into the original SAFEARRAY. - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ElementVt, pElementMT, FALSE); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ElementVt, pElementMT, FALSE); OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF *)pSrcObj, *V_ARRAYREF(pRefVar), ElementVt, pElementMT, pConvertMD->GetMultiCallableAddrOfCode()); } else diff --git a/src/coreclr/vm/dispparammarshaler.cpp b/src/coreclr/vm/dispparammarshaler.cpp index 58f3aed5b47d8e..bc0ddb36d1cd33 100644 --- a/src/coreclr/vm/dispparammarshaler.cpp +++ b/src/coreclr/vm/dispparammarshaler.cpp @@ -228,8 +228,8 @@ DispParamArrayMarshaler::DispParamArrayMarshaler(VARTYPE ElementVT, MethodTable if (ElementVT != VT_EMPTY && pElementMT != NULL) { - m_pConvertContentsToManagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); - m_pConvertContentsToUnmanagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + m_pConvertContentsToManagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + m_pConvertContentsToUnmanagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); } } @@ -274,7 +274,7 @@ void DispParamArrayMarshaler::MarshalNativeToManaged(VARIANT *pSrcVar, OBJECTREF // Convert the contents of the SAFEARRAY. PCODE pConvertCode = m_pConvertContentsToManagedCode; if (pConvertCode == NULL) - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); OleVariant::MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF*)pDestObj, vt, pElemMT, pConvertCode); } @@ -316,7 +316,7 @@ void DispParamArrayMarshaler::MarshalManagedToNative(OBJECTREF *pSrcObj, VARIANT // Marshal the contents of the SAFEARRAY. PCODE pConvertCode = m_pConvertContentsToUnmanagedCode; if (pConvertCode == NULL) - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF*)pSrcObj, pSafeArray, vt, pElemMT, pConvertCode); } diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index f32e6205b693fb..b197aea55ed978 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4610,12 +4610,12 @@ void ILSafeArrayMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) // Resolve the instantiated content conversion methods at stub generation time // and emit ldftn to pass their entry points to CreateMarshaler. MethodDesc* pConvertToNativeMD = GetInstantiatedSafeArrayMethod( - METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, + METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, mops.elementType, mops.methodTable, FALSE); pslILEmit->EmitLDFTN(pslILEmit->GetToken(pConvertToNativeMD)); MethodDesc* pConvertToManagedMD = GetInstantiatedSafeArrayMethod( - METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, + METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, mops.elementType, mops.methodTable, FALSE); pslILEmit->EmitLDFTN(pslILEmit->GetToken(pConvertToManagedMD)); diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 0caf7f1e05e73c..5de83282c311e6 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1476,7 +1476,7 @@ void OleVariant::MarshalArrayVariantOleToObject(const VARIANT* pOleVariant, BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); SetObjectReference(pObj, pArrayRef); - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); } else @@ -1514,7 +1514,7 @@ void OleVariant::MarshalArrayVariantObjectToOle(OBJECTREF * const & pObj, if (*pArrayRef != NULL) { pSafeArray = CreateSafeArrayForArrayRef(pArrayRef, vt, pElemMT); - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE); MarshalSafeArrayForArrayRef(pArrayRef, pSafeArray, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); } V_ARRAY(pOleVariant) = pSafeArray; @@ -1547,7 +1547,7 @@ void OleVariant::MarshalArrayVariantOleRefToObject(const VARIANT *pOleVariant, BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); SetObjectReference(pObj, pArrayRef); - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); + MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); } else diff --git a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp index cf3571f4ed9bf8..15cc679287deb5 100644 --- a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp +++ b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp @@ -309,3 +309,202 @@ extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE XorBoolArrayInStruct(StructWithS { return XorBoolArray(str.array, result); } + +// Creates a 2D SAFEARRAY of VT_I4 with dimensions [rows x cols]. +// Data is filled with value = row * cols + col (column-major storage in SAFEARRAY). +extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Create2DIntSafeArray(int rows, int cols, SAFEARRAY** ppResult) +{ + SAFEARRAYBOUND bounds[2]; + // SAFEARRAY dimension order: first bound is leftmost dimension. + // In column-major (Fortran) layout, the first dimension varies fastest. + bounds[0].lLbound = 0; + bounds[0].cElements = (ULONG)rows; + bounds[1].lLbound = 0; + bounds[1].cElements = (ULONG)cols; + + SAFEARRAY* psa = ::SafeArrayCreate(VT_I4, 2, bounds); + if (!psa) + return E_OUTOFMEMORY; + + for (LONG r = 0; r < rows; r++) + { + for (LONG c = 0; c < cols; c++) + { + LONG indices[2] = { r, c }; + int value = r * cols + c; + HRESULT hr = ::SafeArrayPutElement(psa, indices, &value); + if (FAILED(hr)) + { + ::SafeArrayDestroy(psa); + return hr; + } + } + } + + *ppResult = psa; + return S_OK; +} + +// Verifies a 2D SAFEARRAY of VT_I4 with dimensions [rows x cols]. +// Expected value at [r,c] = r * cols + c. +extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Verify2DIntSafeArray(SAFEARRAY* psa, int rows, int cols) +{ + HRESULT hr; + VARTYPE vt; + RETURN_IF_FAILED(::SafeArrayGetVartype(psa, &vt)); + if (vt != VT_I4) + return E_INVALIDARG; + + if (psa->cDims != 2) + return E_INVALIDARG; + + for (LONG r = 0; r < rows; r++) + { + for (LONG c = 0; c < cols; c++) + { + LONG indices[2] = { r, c }; + int value = 0; + RETURN_IF_FAILED(::SafeArrayGetElement(psa, indices, &value)); + int expected = r * cols + c; + if (value != expected) + return E_FAIL; + } + } + + return S_OK; +} + +// Creates a 2D SAFEARRAY of VT_BOOL with dimensions [rows x cols]. +// Value at [r,c] = ((r + c) % 2 == 0) ? VARIANT_TRUE : VARIANT_FALSE. +extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Create2DBoolSafeArray(int rows, int cols, SAFEARRAY** ppResult) +{ + SAFEARRAYBOUND bounds[2]; + bounds[0].lLbound = 0; + bounds[0].cElements = (ULONG)rows; + bounds[1].lLbound = 0; + bounds[1].cElements = (ULONG)cols; + + SAFEARRAY* psa = ::SafeArrayCreate(VT_BOOL, 2, bounds); + if (!psa) + return E_OUTOFMEMORY; + + for (LONG r = 0; r < rows; r++) + { + for (LONG c = 0; c < cols; c++) + { + LONG indices[2] = { r, c }; + VARIANT_BOOL value = ((r + c) % 2 == 0) ? VARIANT_TRUE : VARIANT_FALSE; + HRESULT hr = ::SafeArrayPutElement(psa, indices, &value); + if (FAILED(hr)) + { + ::SafeArrayDestroy(psa); + return hr; + } + } + } + + *ppResult = psa; + return S_OK; +} + +// Verifies a 2D SAFEARRAY of VT_BOOL with dimensions [rows x cols]. +extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Verify2DBoolSafeArray(SAFEARRAY* psa, int rows, int cols) +{ + HRESULT hr; + VARTYPE vt; + RETURN_IF_FAILED(::SafeArrayGetVartype(psa, &vt)); + if (vt != VT_BOOL) + return E_INVALIDARG; + + if (psa->cDims != 2) + return E_INVALIDARG; + + for (LONG r = 0; r < rows; r++) + { + for (LONG c = 0; c < cols; c++) + { + LONG indices[2] = { r, c }; + VARIANT_BOOL value = VARIANT_FALSE; + RETURN_IF_FAILED(::SafeArrayGetElement(psa, indices, &value)); + VARIANT_BOOL expected = ((r + c) % 2 == 0) ? VARIANT_TRUE : VARIANT_FALSE; + if (value != expected) + return E_FAIL; + } + } + + return S_OK; +} + +// Creates a 2D SAFEARRAY of VT_BSTR with dimensions [rows x cols]. +// Value at [r,c] = "r,c". +extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Create2DStringSafeArray(int rows, int cols, SAFEARRAY** ppResult) +{ + SAFEARRAYBOUND bounds[2]; + bounds[0].lLbound = 0; + bounds[0].cElements = (ULONG)rows; + bounds[1].lLbound = 0; + bounds[1].cElements = (ULONG)cols; + + SAFEARRAY* psa = ::SafeArrayCreate(VT_BSTR, 2, bounds); + if (!psa) + return E_OUTOFMEMORY; + + for (LONG r = 0; r < rows; r++) + { + for (LONG c = 0; c < cols; c++) + { + LONG indices[2] = { r, c }; + WCHAR buf[32]; + swprintf_s(buf, 32, L"%d,%d", (int)r, (int)c); + BSTR bstr = TP_SysAllocString(buf); + if (!bstr) + { + ::SafeArrayDestroy(psa); + return E_OUTOFMEMORY; + } + HRESULT hr = ::SafeArrayPutElement(psa, indices, bstr); + ::SysFreeString(bstr); + if (FAILED(hr)) + { + ::SafeArrayDestroy(psa); + return hr; + } + } + } + + *ppResult = psa; + return S_OK; +} + +// Verifies a 2D SAFEARRAY of VT_BSTR with dimensions [rows x cols]. +extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Verify2DStringSafeArray(SAFEARRAY* psa, int rows, int cols) +{ + HRESULT hr; + VARTYPE vt; + RETURN_IF_FAILED(::SafeArrayGetVartype(psa, &vt)); + if (vt != VT_BSTR) + return E_INVALIDARG; + + if (psa->cDims != 2) + return E_INVALIDARG; + + for (LONG r = 0; r < rows; r++) + { + for (LONG c = 0; c < cols; c++) + { + LONG indices[2] = { r, c }; + BSTR value = nullptr; + RETURN_IF_FAILED(::SafeArrayGetElement(psa, indices, &value)); + + WCHAR expected[32]; + swprintf_s(expected, 32, L"%d,%d", (int)r, (int)c); + + bool match = (value != nullptr && wcscmp(value, expected) == 0); + ::SysFreeString(value); + if (!match) + return E_FAIL; + } + } + + return S_OK; +} diff --git a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs index 860dfefd2d58d1..5f1a8a3513d6e8 100644 --- a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs +++ b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs @@ -88,6 +88,105 @@ public static int TestEntryPoint() return 100; } + [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] + [SkipOnMono("Requires COM support")] + public static void MultidimensionalIntArray() + { + const int rows = 3; + const int cols = 4; + + SafeArrayNative.Create2DIntSafeArray(rows, cols, out int[,] result); + + Assert.Equal(rows, result.GetLength(0)); + Assert.Equal(cols, result.GetLength(1)); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + Assert.Equal(r * cols + c, result[r, c]); + } + } + } + + [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] + [SkipOnMono("Requires COM support")] + public static void MultidimensionalIntArrayRoundTrip() + { + const int rows = 3; + const int cols = 4; + + SafeArrayNative.Create2DIntSafeArray(rows, cols, out int[,] result); + + SafeArrayNative.Verify2DIntSafeArray(result, rows, cols); + } + + [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] + [SkipOnMono("Requires COM support")] + public static void MultidimensionalBoolArray() + { + const int rows = 2; + const int cols = 3; + + SafeArrayNative.Create2DBoolSafeArray(rows, cols, out bool[,] result); + + Assert.Equal(rows, result.GetLength(0)); + Assert.Equal(cols, result.GetLength(1)); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + Assert.Equal((r + c) % 2 == 0, result[r, c]); + } + } + } + + [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] + [SkipOnMono("Requires COM support")] + public static void MultidimensionalBoolArrayRoundTrip() + { + const int rows = 2; + const int cols = 3; + + SafeArrayNative.Create2DBoolSafeArray(rows, cols, out bool[,] result); + + SafeArrayNative.Verify2DBoolSafeArray(result, rows, cols); + } + + [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] + [SkipOnMono("Requires COM support")] + public static void MultidimensionalStringArray() + { + const int rows = 2; + const int cols = 3; + + SafeArrayNative.Create2DStringSafeArray(rows, cols, out string[,] result); + + Assert.Equal(rows, result.GetLength(0)); + Assert.Equal(cols, result.GetLength(1)); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + Assert.Equal($"{r},{c}", result[r, c]); + } + } + } + + [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] + [SkipOnMono("Requires COM support")] + public static void MultidimensionalStringArrayRoundTrip() + { + const int rows = 2; + const int cols = 3; + + SafeArrayNative.Create2DStringSafeArray(rows, cols, out string[,] result); + + SafeArrayNative.Verify2DStringSafeArray(result, rows, cols); + } + private static bool XorArray(bool[] values) { bool retVal = false; @@ -221,4 +320,46 @@ out double result [DllImport(nameof(SafeArrayNative), PreserveSig = false)] public static extern void XorBoolArrayInStruct(StructWithSafeArray str, out bool result); + + [DllImport(nameof(SafeArrayNative), PreserveSig = false)] + public static extern void Create2DIntSafeArray( + int rows, + int cols, + [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)] out int[,] result + ); + + [DllImport(nameof(SafeArrayNative), PreserveSig = false)] + public static extern void Verify2DIntSafeArray( + [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)] int[,] array, + int rows, + int cols + ); + + [DllImport(nameof(SafeArrayNative), PreserveSig = false)] + public static extern void Create2DBoolSafeArray( + int rows, + int cols, + [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BOOL)] out bool[,] result + ); + + [DllImport(nameof(SafeArrayNative), PreserveSig = false)] + public static extern void Verify2DBoolSafeArray( + [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BOOL)] bool[,] array, + int rows, + int cols + ); + + [DllImport(nameof(SafeArrayNative), PreserveSig = false)] + public static extern void Create2DStringSafeArray( + int rows, + int cols, + [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] out string[,] result + ); + + [DllImport(nameof(SafeArrayNative), PreserveSig = false)] + public static extern void Verify2DStringSafeArray( + [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] string[,] array, + int rows, + int cols + ); } From 5ae2e55696a7a4e8d56fbc3673d9bcaa25bfb37b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 10 Apr 2026 17:20:20 -0700 Subject: [PATCH 14/41] Fix GC mode contract violations in SafeArray variant marshalling GetInstantiatedSafeArrayMethod has STANDARD_VM_CONTRACT (MODE_PREEMPTIVE) but was called from variant marshalling paths that run in MODE_COOPERATIVE. Fix by wrapping calls with GCX_PREEMP() to temporarily switch to preemptive mode for the method resolution, then return to cooperative mode for managed object manipulation. The method resolution only needs native pointers (vt, pElemMT), so no managed references are at risk during the mode switch. Affected callsites: - olevariant.cpp: MarshalArrayVariantOleToObject, MarshalArrayVariantObjectToOle, MarshalArrayVariantOleRefToObject - dispparammarshaler.cpp: MarshalNativeToManaged, MarshalManagedToNative fallbacks - dispatchinfo.cpp: MarshalParamManagedToNativeRef Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/dispatchinfo.cpp | 9 +++++++-- src/coreclr/vm/dispparammarshaler.cpp | 6 ++++++ src/coreclr/vm/olevariant.cpp | 28 +++++++++++++++++++++------ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index d2d9e02d3d5634..06297121a2b7d7 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -2154,8 +2154,13 @@ void DispatchInfo::MarshalParamManagedToNativeRef(DispatchMemberInfo *pMemberInf MethodTable *pElementMT = (*(BASEARRAYREF *)pSrcObj)->GetArrayElementTypeHandle().GetMethodTable(); // Convert the contents of the managed array into the original SAFEARRAY. - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ElementVt, pElementMT, FALSE); - OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF *)pSrcObj, *V_ARRAYREF(pRefVar), ElementVt, pElementMT, pConvertMD->GetMultiCallableAddrOfCode()); + PCODE pConvertCode; + { + GCX_PREEMP(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ElementVt, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + } + + OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF *)pSrcObj, *V_ARRAYREF(pRefVar), ElementVt, pElementMT, pConvertCode); } else { diff --git a/src/coreclr/vm/dispparammarshaler.cpp b/src/coreclr/vm/dispparammarshaler.cpp index bc0ddb36d1cd33..39057c73f3bb92 100644 --- a/src/coreclr/vm/dispparammarshaler.cpp +++ b/src/coreclr/vm/dispparammarshaler.cpp @@ -274,7 +274,10 @@ void DispParamArrayMarshaler::MarshalNativeToManaged(VARIANT *pSrcVar, OBJECTREF // Convert the contents of the SAFEARRAY. PCODE pConvertCode = m_pConvertContentsToManagedCode; if (pConvertCode == NULL) + { + GCX_PREEMP(); pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + } OleVariant::MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF*)pDestObj, vt, pElemMT, pConvertCode); } @@ -316,7 +319,10 @@ void DispParamArrayMarshaler::MarshalManagedToNative(OBJECTREF *pSrcObj, VARIANT // Marshal the contents of the SAFEARRAY. PCODE pConvertCode = m_pConvertContentsToUnmanagedCode; if (pConvertCode == NULL) + { + GCX_PREEMP(); pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + } OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF*)pSrcObj, pSafeArray, vt, pElemMT, pConvertCode); } diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 5de83282c311e6..35107ebad9c9fa 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1474,10 +1474,15 @@ void OleVariant::MarshalArrayVariantOleToObject(const VARIANT* pOleVariant, if (vt == VT_RECORD) pElemMT = GetElementTypeForRecordSafeArray(pSafeArray).GetMethodTable(); + PCODE pConvertCode; + { + GCX_PREEMP(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + } + BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); SetObjectReference(pObj, pArrayRef); - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); - MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); + MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertCode); } else { @@ -1514,8 +1519,14 @@ void OleVariant::MarshalArrayVariantObjectToOle(OBJECTREF * const & pObj, if (*pArrayRef != NULL) { pSafeArray = CreateSafeArrayForArrayRef(pArrayRef, vt, pElemMT); - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE); - MarshalSafeArrayForArrayRef(pArrayRef, pSafeArray, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); + + PCODE pConvertCode; + { + GCX_PREEMP(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + } + + MarshalSafeArrayForArrayRef(pArrayRef, pSafeArray, vt, pElemMT, pConvertCode); } V_ARRAY(pOleVariant) = pSafeArray; pSafeArray.SuppressRelease(); @@ -1545,10 +1556,15 @@ void OleVariant::MarshalArrayVariantOleRefToObject(const VARIANT *pOleVariant, if (vt == VT_RECORD) pElemMT = GetElementTypeForRecordSafeArray(pSafeArray).GetMethodTable(); + PCODE pConvertCode; + { + GCX_PREEMP(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + } + BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); SetObjectReference(pObj, pArrayRef); - MethodDesc* pConvertMD = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE); - MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertMD->GetMultiCallableAddrOfCode()); + MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF *) pObj, vt, pElemMT, pConvertCode); } else { From 36ae765f13b2e91c1030095076725f8ca927e830 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 10 Apr 2026 19:26:30 -0700 Subject: [PATCH 15/41] Preserve initial variant clear in array marshalling --- src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 145e8e0e007125..8ceb1a2b40903d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1906,6 +1906,7 @@ internal sealed class VariantArrayElementMarshaler : IArrayElementMarshaler Date: Sat, 11 Apr 2026 12:06:19 -0700 Subject: [PATCH 16/41] Consolidate array marshalling to use Array instead of T[] Replace the separate T[]-based (P/Invoke) and Array-based (SafeArray) content conversion methods with a single set that accepts System.Array. This correctly supports multidimensional arrays in both P/Invoke and SafeArray paths using MemoryMarshal.GetArrayDataReference to access elements contiguously regardless of rank. Also update ConvertArraySpaceToNative to accept Array instead of T[]. Update AsAnyMarshaler to use delegate* function pointers instead of MethodInvoker for all array marshalling methods, since the signatures are now uniform (Array, byte*) -> void. Remove the now-unnecessary CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED and CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED method IDs from corelib.h. All SafeArray callsites now use the unified CONVERT_ARRAY_CONTENTS_TO_* IDs. Also fix VariantArrayElementMarshaler.ConvertToUnmanaged to zero the destination ComVariant before calling ObjectMarshaler.ConvertToNative, preventing garbage VT_BYREF bits in uninitialized caller buffers from selecting the wrong marshalling path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 47 +++++-------------- src/coreclr/vm/corelib.h | 2 - src/coreclr/vm/dispatchinfo.cpp | 2 +- src/coreclr/vm/dispparammarshaler.cpp | 8 ++-- src/coreclr/vm/ilmarshalers.cpp | 4 +- src/coreclr/vm/olevariant.cpp | 6 +-- 6 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 8ceb1a2b40903d..95555d168c85ae 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -893,8 +893,8 @@ private enum BackPropAction private struct ArrayMarshalerMethods { - public MethodInvoker convertContentsToNative; - public MethodInvoker convertContentsToManaged; + public unsafe delegate* convertContentsToNative; + public unsafe delegate* convertContentsToManaged; public unsafe delegate* convertSpaceToNative; } @@ -960,18 +960,20 @@ TypeCode.Char when IsAnsi(dwFlags) => [ arrayMarshalerMethods = new ArrayMarshalerMethods { - convertContentsToManaged = MethodInvoker.Create( + convertContentsToManaged = (delegate*) typeof(StubHelpers) .GetMethod( nameof(StubHelpers.ConvertArrayContentsToManaged), BindingFlags.Public | BindingFlags.Static)! - .MakeGenericMethod(marshalerGenericArgs)), - convertContentsToNative = MethodInvoker.Create( + .MakeGenericMethod(marshalerGenericArgs) + .MethodHandle.GetFunctionPointer(), + convertContentsToNative = (delegate*) typeof(StubHelpers) .GetMethod( nameof(StubHelpers.ConvertArrayContentsToUnmanaged), BindingFlags.Public | BindingFlags.Static)! - .MakeGenericMethod(marshalerGenericArgs)), + .MakeGenericMethod(marshalerGenericArgs) + .MethodHandle.GetFunctionPointer(), convertSpaceToNative = (delegate*) typeof(StubHelpers) .GetMethod( @@ -985,7 +987,7 @@ TypeCode.Char when IsAnsi(dwFlags) => [ if (IsIn(dwFlags)) { - arrayMarshalerMethods.convertContentsToNative.Invoke(null, pManagedHome, Pointer.Box(pNativeHome, typeof(byte*))); + arrayMarshalerMethods.convertContentsToNative((Array)pManagedHome, pNativeHome); } if (IsOut(dwFlags)) @@ -1178,7 +1180,7 @@ internal unsafe void ConvertToManaged(object pManagedHome, IntPtr pNativeHome) { case BackPropAction.Array: { - arrayMarshalerMethods.convertContentsToManaged.Invoke(null, pManagedHome, pNativeHome); + arrayMarshalerMethods.convertContentsToManaged((Array)pManagedHome, (byte*)pNativeHome); break; } @@ -2295,30 +2297,7 @@ internal static unsafe void LayoutTypeConvertToManaged(object* obj, byte* pNativ } } - public static unsafe void ConvertArrayContentsToUnmanaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler - { - for (int i = 0; i < managed.Length; i++) - { - TMarshaler.ConvertToUnmanaged(ref managed[i], pNative); - pNative += TMarshaler.UnmanagedSize; - } - } - - public static unsafe void ConvertArrayContentsToManaged(T[] managed, byte* pNative) where TMarshaler : IArrayElementMarshaler - { - for (int i = 0; i < managed.Length; i++) - { - TMarshaler.ConvertToManaged(ref managed[i], pNative); - pNative += TMarshaler.UnmanagedSize; - } - } - - // SafeArray-specific overloads that accept Array instead of T[]. - // SafeArrays can be multidimensional or have non-zero lower bounds, - // producing general Array objects that are not T[]. - // CLR arrays store elements contiguously regardless of rank, - // so we use MemoryMarshal.GetArrayDataReference to access the data directly. - public static unsafe void ConvertSafeArrayContentsToUnmanaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToUnmanaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler { ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); int length = managed.Length; @@ -2329,7 +2308,7 @@ public static unsafe void ConvertSafeArrayContentsToUnmanaged(Arr } } - public static unsafe void ConvertSafeArrayContentsToManaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToManaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler { ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); int length = managed.Length; @@ -2366,7 +2345,7 @@ internal static unsafe void FreeArrayContents(byte* pNative, int } } - public static unsafe byte* ConvertArraySpaceToNative(T[]? managed) + public static unsafe byte* ConvertArraySpaceToNative(Array? managed) where TMarshaler : IArrayElementMarshaler { if (managed is null) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 96bcb9e7b6eacd..bc295c7797e857 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1074,8 +1074,6 @@ DEFINE_METHOD(STUBHELPERS, LAYOUT_TYPE_CONVERT_TO_MANAGED, LayoutTypeCo DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ConvertArrayContentsToUnmanaged, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_CONTENTS_TO_MANAGED, ConvertArrayContentsToManaged, NoSig) -DEFINE_METHOD(STUBHELPERS, CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ConvertSafeArrayContentsToUnmanaged, NoSig) -DEFINE_METHOD(STUBHELPERS, CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, ConvertSafeArrayContentsToManaged, NoSig) DEFINE_METHOD(STUBHELPERS, FREE_ARRAY_CONTENTS, FreeArrayContents, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_NATIVE, ConvertArraySpaceToNative, NoSig) DEFINE_METHOD(STUBHELPERS, CONVERT_ARRAY_SPACE_TO_MANAGED, ConvertArraySpaceToManaged, NoSig) diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index 06297121a2b7d7..7625edbf50f1c9 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -2157,7 +2157,7 @@ void DispatchInfo::MarshalParamManagedToNativeRef(DispatchMemberInfo *pMemberInf PCODE pConvertCode; { GCX_PREEMP(); - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ElementVt, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ElementVt, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); } OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF *)pSrcObj, *V_ARRAYREF(pRefVar), ElementVt, pElementMT, pConvertCode); diff --git a/src/coreclr/vm/dispparammarshaler.cpp b/src/coreclr/vm/dispparammarshaler.cpp index 39057c73f3bb92..5fa45e44808d7c 100644 --- a/src/coreclr/vm/dispparammarshaler.cpp +++ b/src/coreclr/vm/dispparammarshaler.cpp @@ -228,8 +228,8 @@ DispParamArrayMarshaler::DispParamArrayMarshaler(VARTYPE ElementVT, MethodTable if (ElementVT != VT_EMPTY && pElementMT != NULL) { - m_pConvertContentsToManagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); - m_pConvertContentsToUnmanagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + m_pConvertContentsToManagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); + m_pConvertContentsToUnmanagedCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, ElementVT, pElementMT, FALSE)->GetMultiCallableAddrOfCode(); } } @@ -276,7 +276,7 @@ void DispParamArrayMarshaler::MarshalNativeToManaged(VARIANT *pSrcVar, OBJECTREF if (pConvertCode == NULL) { GCX_PREEMP(); - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); } OleVariant::MarshalArrayRefForSafeArray(pSafeArray, (BASEARRAYREF*)pDestObj, vt, pElemMT, pConvertCode); } @@ -321,7 +321,7 @@ void DispParamArrayMarshaler::MarshalManagedToNative(OBJECTREF *pSrcObj, VARIANT if (pConvertCode == NULL) { GCX_PREEMP(); - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); } OleVariant::MarshalSafeArrayForArrayRef((BASEARRAYREF*)pSrcObj, pSafeArray, vt, pElemMT, pConvertCode); } diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index b197aea55ed978..f32e6205b693fb 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4610,12 +4610,12 @@ void ILSafeArrayMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) // Resolve the instantiated content conversion methods at stub generation time // and emit ldftn to pass their entry points to CreateMarshaler. MethodDesc* pConvertToNativeMD = GetInstantiatedSafeArrayMethod( - METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, + METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, mops.elementType, mops.methodTable, FALSE); pslILEmit->EmitLDFTN(pslILEmit->GetToken(pConvertToNativeMD)); MethodDesc* pConvertToManagedMD = GetInstantiatedSafeArrayMethod( - METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, + METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, mops.elementType, mops.methodTable, FALSE); pslILEmit->EmitLDFTN(pslILEmit->GetToken(pConvertToManagedMD)); diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 35107ebad9c9fa..d6f38d764e07a2 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1477,7 +1477,7 @@ void OleVariant::MarshalArrayVariantOleToObject(const VARIANT* pOleVariant, PCODE pConvertCode; { GCX_PREEMP(); - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); } BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); @@ -1523,7 +1523,7 @@ void OleVariant::MarshalArrayVariantObjectToOle(OBJECTREF * const & pObj, PCODE pConvertCode; { GCX_PREEMP(); - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); } MarshalSafeArrayForArrayRef(pArrayRef, pSafeArray, vt, pElemMT, pConvertCode); @@ -1559,7 +1559,7 @@ void OleVariant::MarshalArrayVariantOleRefToObject(const VARIANT *pOleVariant, PCODE pConvertCode; { GCX_PREEMP(); - pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_SAFE_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); + pConvertCode = GetInstantiatedSafeArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED, vt, pElemMT, FALSE)->GetMultiCallableAddrOfCode(); } BASEARRAYREF pArrayRef = CreateArrayRefForSafeArray(pSafeArray, vt, pElemMT); From bf86ba509f3f382324433644363f225c83e94661 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 11 Apr 2026 22:23:10 -0700 Subject: [PATCH 17/41] Pass element count to array content conversion methods Add a numElements parameter to ConvertArrayContentsToUnmanaged and ConvertArrayContentsToManaged so callers can control how many elements are marshalled independently of the managed array length. ILFixedArrayMarshaler now overrides content conversion to pass the fixed native element count (mops.additive), ensuring only the number of elements that fit in the native buffer are marshalled. ILNativeArrayMarshaler and ILArrayMarshalerBase pass the managed array length via ldlen. The SafeArray path (InvokeArrayContentsConverter and olevariant.cpp) and AsAnyMarshaler are updated to pass dwNumComponents / managedArray.Length respectively. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 29 +++++------ src/coreclr/vm/ilmarshalers.cpp | 50 +++++++++++++++++-- src/coreclr/vm/ilmarshalers.h | 2 + src/coreclr/vm/olevariant.cpp | 4 +- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 95555d168c85ae..e2c78aa4e6cf58 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -893,8 +893,8 @@ private enum BackPropAction private struct ArrayMarshalerMethods { - public unsafe delegate* convertContentsToNative; - public unsafe delegate* convertContentsToManaged; + public unsafe delegate* convertContentsToNative; + public unsafe delegate* convertContentsToManaged; public unsafe delegate* convertSpaceToNative; } @@ -960,14 +960,14 @@ TypeCode.Char when IsAnsi(dwFlags) => [ arrayMarshalerMethods = new ArrayMarshalerMethods { - convertContentsToManaged = (delegate*) + convertContentsToManaged = (delegate*) typeof(StubHelpers) .GetMethod( nameof(StubHelpers.ConvertArrayContentsToManaged), BindingFlags.Public | BindingFlags.Static)! .MakeGenericMethod(marshalerGenericArgs) .MethodHandle.GetFunctionPointer(), - convertContentsToNative = (delegate*) + convertContentsToNative = (delegate*) typeof(StubHelpers) .GetMethod( nameof(StubHelpers.ConvertArrayContentsToUnmanaged), @@ -987,7 +987,8 @@ TypeCode.Char when IsAnsi(dwFlags) => [ if (IsIn(dwFlags)) { - arrayMarshalerMethods.convertContentsToNative((Array)pManagedHome, pNativeHome); + Array managedArray = (Array)pManagedHome; + arrayMarshalerMethods.convertContentsToNative(managedArray, pNativeHome, managedArray.Length); } if (IsOut(dwFlags)) @@ -1180,7 +1181,8 @@ internal unsafe void ConvertToManaged(object pManagedHome, IntPtr pNativeHome) { case BackPropAction.Array: { - arrayMarshalerMethods.convertContentsToManaged((Array)pManagedHome, (byte*)pNativeHome); + Array managedArray = (Array)pManagedHome; + arrayMarshalerMethods.convertContentsToManaged(managedArray, (byte*)pNativeHome, managedArray.Length); break; } @@ -2297,22 +2299,20 @@ internal static unsafe void LayoutTypeConvertToManaged(object* obj, byte* pNativ } } - public static unsafe void ConvertArrayContentsToUnmanaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToUnmanaged(Array managed, byte* pNative, int numElements) where TMarshaler : IArrayElementMarshaler { ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); - int length = managed.Length; - for (int i = 0; i < length; i++) + for (int i = 0; i < numElements; i++) { TMarshaler.ConvertToUnmanaged(ref Unsafe.Add(ref firstElement, i), pNative); pNative += TMarshaler.UnmanagedSize; } } - public static unsafe void ConvertArrayContentsToManaged(Array managed, byte* pNative) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToManaged(Array managed, byte* pNative, int numElements) where TMarshaler : IArrayElementMarshaler { ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); - int length = managed.Length; - for (int i = 0; i < length; i++) + for (int i = 0; i < numElements; i++) { TMarshaler.ConvertToManaged(ref Unsafe.Add(ref firstElement, i), pNative); pNative += TMarshaler.UnmanagedSize; @@ -2323,12 +2323,13 @@ public static unsafe void ConvertArrayContentsToManaged(Array man internal static unsafe void InvokeArrayContentsConverter( Array* pManagedArray, byte* pNative, - delegate* pConvertMethod, + int numElements, + delegate* pConvertMethod, Exception* pException) { try { - pConvertMethod(*pManagedArray, pNative); + pConvertMethod(*pManagedArray, pNative, numElements); } catch (Exception ex) { diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index f32e6205b693fb..efadfe7d35fa17 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4218,7 +4218,10 @@ void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILE EmitLoadManagedValue(pslILEmit); EmitLoadNativeValue(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDLEN(); + pslILEmit->EmitCONV_I4(); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); } void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) @@ -4229,7 +4232,10 @@ void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILE EmitLoadManagedValue(pslILEmit); EmitLoadNativeValue(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDLEN(); + pslILEmit->EmitCONV_I4(); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); } void ILNativeArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) @@ -4498,7 +4504,10 @@ void ILArrayMarshalerBase::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmi EmitLoadManagedValue(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDLEN(); + pslILEmit->EmitCONV_I4(); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); } void ILArrayMarshalerBase::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) @@ -4509,7 +4518,10 @@ void ILArrayMarshalerBase::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmi EmitLoadManagedValue(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDLEN(); + pslILEmit->EmitCONV_I4(); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); } // ==================== ILFixedArrayMarshaler ==================== @@ -4553,6 +4565,36 @@ void ILFixedArrayMarshaler::EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) EmitStoreManagedValue(pslILEmit); } +void ILFixedArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + CREATE_MARSHALER_CARRAY_OPERANDS mops; + m_pargs->m_pMarshalInfo->GetMops(&mops); + + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); + + EmitLoadManagedValue(pslILEmit); + EmitLoadNativeHomeAddr(pslILEmit); + pslILEmit->EmitLDC(mops.additive); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); +} + +void ILFixedArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + CREATE_MARSHALER_CARRAY_OPERANDS mops; + m_pargs->m_pMarshalInfo->GetMops(&mops); + + MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); + + EmitLoadManagedValue(pslILEmit); + EmitLoadNativeHomeAddr(pslILEmit); + pslILEmit->EmitLDC(mops.additive); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); +} + void ILFixedArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index c85662e5270e5d..7c3299c7516e47 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3379,6 +3379,8 @@ class ILFixedArrayMarshaler : public ILArrayMarshalerBase void EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) override; void EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) override; + void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; + void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; void EmitLoadNativeSize(ILCodeStream* pslILEmit) override; void EmitClearNative(ILCodeStream* pslILEmit) override; }; diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index d6f38d764e07a2..da8a34cc7c9901 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -2067,7 +2067,7 @@ void OleVariant::MarshalSafeArrayForArrayRef(BASEARRAYREF *pArrayRef, // Use managed IArrayElementMarshaler implementations for content conversion. UnmanagedCallersOnlyCaller invoker(METHOD__STUBHELPERS__INVOKE_ARRAY_CONTENTS_CONVERTER); - invoker.InvokeThrowing(&Array, pSafeArray->pvData, (void*)pConvertContentsCode); + invoker.InvokeThrowing(&Array, pSafeArray->pvData, (INT32)dwNumComponents, (void*)pConvertContentsCode); if (pSafeArray->cDims != 1) { @@ -2124,7 +2124,7 @@ void OleVariant::MarshalArrayRefForSafeArray(SAFEARRAY *pSafeArray, // Use managed IArrayElementMarshaler implementations for content conversion. UnmanagedCallersOnlyCaller invoker(METHOD__STUBHELPERS__INVOKE_ARRAY_CONTENTS_CONVERTER); - invoker.InvokeThrowing(pArrayRef, pSrcData, (void*)pConvertContentsCode); + invoker.InvokeThrowing(pArrayRef, pSrcData, (INT32)dwNumComponents, (void*)pConvertContentsCode); } void OleVariant::ConvertValueClassToVariant(OBJECTREF *pBoxedValueClass, VARIANT *pOleVariant) From 05a4f8cb003b3d3904a94e2377cdb59957702068 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 12 Apr 2026 15:00:06 -0700 Subject: [PATCH 18/41] Rewrite implementation to correctly support ANSI char array marshaling. --- .../src/System/StubHelpers.cs | 187 ++++++++++++------ src/coreclr/vm/corelib.h | 2 +- src/coreclr/vm/ilmarshalers.h | 2 +- src/coreclr/vm/olevariant.cpp | 6 +- 4 files changed, 134 insertions(+), 63 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index e2c78aa4e6cf58..89387d2577e932 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -947,7 +947,7 @@ or TypeCode.Single TypeCode.Char when !IsAnsi(dwFlags) => [typeof(char), typeof(UnicodeCharArrayElementMarshaler)], TypeCode.Char when IsAnsi(dwFlags) => [ typeof(char), - typeof(AnsiCharArrayElementMarshaler<,>).MakeGenericType([ + typeof(AnsiCharArrayMarshaler<,>).MakeGenericType([ IsBestFit(dwFlags) ? typeof(IMarshalerOption.EnabledOption) : typeof(IMarshalerOption.DisabledOption), @@ -1243,19 +1243,84 @@ internal void ClearNative(IntPtr pNativeHome) } } // struct AsAnyMarshaler - internal interface IArrayElementMarshaler where TSelf : IArrayElementMarshaler + internal interface IArrayMarshaler + where TSelf : IArrayMarshaler { + static abstract unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length); + static abstract unsafe void ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length); + static abstract unsafe void FreeContents(byte* unmanaged, int length); + static abstract unsafe byte* AllocateSpaceForUnmanaged(Array? managedArray); + static abstract unsafe Array? AllocateSpaceForManaged(byte* unmanaged, int length); + } + + internal interface IArrayElementMarshaler : IArrayMarshaler + where TSelf : IArrayElementMarshaler, IArrayMarshaler + { + static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) + { + Debug.Assert(managedArray is not null); + Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); + ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)); + for (int i = 0; i < length; i++) + { + TSelf.ConvertToManaged(ref Unsafe.Add(ref firstElement, i), unmanaged); + unmanaged += TSelf.UnmanagedSize; + } + } + + static unsafe void IArrayMarshaler.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) + { + Debug.Assert(managedArray is not null); + Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); + ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)); + for (int i = 0; i < length; i++) + { + TSelf.ConvertToUnmanaged(ref Unsafe.Add(ref firstElement, i), unmanaged); + unmanaged += TSelf.UnmanagedSize; + } + } + + static unsafe void IArrayMarshaler.FreeContents(byte* unmanaged, int length) + { + for (int i = 0; i < length; i++) + { + TSelf.Free(unmanaged); + unmanaged += TSelf.UnmanagedSize; + } + } + + static unsafe byte* IArrayMarshaler.AllocateSpaceForUnmanaged(Array? managedArray) + { + if (managedArray is null) + { + return null; + } + else + { + int nativeBytes = checked(managedArray.Length * (int)TSelf.UnmanagedSize); + byte* pNative = (byte*)Marshal.AllocCoTaskMem(nativeBytes); + NativeMemory.Clear(pNative, (nuint)nativeBytes); + return pNative; + } + } + + static unsafe Array? IArrayMarshaler.AllocateSpaceForManaged(byte* unmanaged, int length) + { + if (unmanaged is null) + { + return null; + } + else + { + return new T[length]; + } + } + static abstract unsafe void ConvertToUnmanaged(ref T managed, byte* unmanaged); static abstract unsafe void ConvertToManaged(ref T managed, byte* unmanaged); static abstract unsafe void Free(byte* unmanaged); static abstract nuint UnmanagedSize { get; } - - // Space to allocate per element. Defaults to UnmanagedSize but may be larger - // when conversion can produce more bytes than the final native layout (e.g. - // ANSI char needs SystemMaxDBCSCharSize bytes during conversion but only - // occupies 1 byte in the native struct). - static virtual nuint AllocationSize => TSelf.UnmanagedSize; } // Constants for direction argument of struct marshalling stub. @@ -1664,27 +1729,62 @@ public static unsafe void Free(byte* unmanaged) static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); } - internal sealed class AnsiCharArrayElementMarshaler : IArrayElementMarshaler> + internal sealed class AnsiCharArrayMarshaler : IArrayMarshaler> where TBestFit : IMarshalerOption where TThrowOnUnmappable : IMarshalerOption { - public static unsafe void ConvertToUnmanaged(ref char managed, byte* unmanaged) + public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - *unmanaged = AnsiCharMarshaler.ConvertToNative(managed, TBestFit.Enabled, TThrowOnUnmappable.Enabled); + Debug.Assert(managedArray is not null); + Debug.Assert(managedArray.GetType().GetElementType() == typeof(char)); + ReadOnlySpan chars = new( + ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), + length); + string str = new(chars); + Marshal.StringToAnsiString(str, unmanaged, length + 1, TBestFit.Enabled, TThrowOnUnmappable.Enabled); } - public static unsafe void ConvertToManaged(ref char managed, byte* unmanaged) + public static unsafe void ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - managed = AnsiCharMarshaler.ConvertToManaged(*unmanaged); + Debug.Assert(managedArray is not null); + Debug.Assert(managedArray.GetType().GetElementType() == typeof(char)); + Span chars = new Span( + ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), + length); + // Use the string(sbyte*) constructor which uses the system default ANSI code page, + // then copy the resulting characters into the array. + string converted = new((sbyte*)unmanaged, 0, length); + converted.CopyTo(chars); } - public static unsafe void Free(byte* unmanaged) + public static unsafe void FreeContents(byte* unmanaged, int length) { } - static nuint IArrayElementMarshaler>.UnmanagedSize => sizeof(byte); + public static unsafe byte* AllocateSpaceForUnmanaged(Array? managedArray) + { + if (managedArray is null) + { + return null; + } + + // Each char can map to at most SystemMaxDBCSCharSize bytes during conversion, + // but the final native layout uses 1 byte per element. + int allocSize = checked(managedArray.Length * Marshal.SystemMaxDBCSCharSize); + byte* pNative = (byte*)Marshal.AllocCoTaskMem(allocSize); + NativeMemory.Clear(pNative, (nuint)allocSize); + return pNative; + } + + public static unsafe Array? AllocateSpaceForManaged(byte* unmanaged, int length) + { + if (unmanaged is null) + { + return null; + } - static nuint IArrayElementMarshaler>.AllocationSize => (nuint)Marshal.SystemMaxDBCSCharSize; + return new char[length]; + } } internal sealed class UnicodeCharArrayElementMarshaler : IArrayElementMarshaler @@ -2299,24 +2399,16 @@ internal static unsafe void LayoutTypeConvertToManaged(object* obj, byte* pNativ } } - public static unsafe void ConvertArrayContentsToUnmanaged(Array managed, byte* pNative, int numElements) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToUnmanaged(Array managed, byte* pNative, int numElements) + where TMarshaler : IArrayMarshaler { - ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); - for (int i = 0; i < numElements; i++) - { - TMarshaler.ConvertToUnmanaged(ref Unsafe.Add(ref firstElement, i), pNative); - pNative += TMarshaler.UnmanagedSize; - } + TMarshaler.ConvertContentsToUnmanaged(managed, pNative, numElements); } - public static unsafe void ConvertArrayContentsToManaged(Array managed, byte* pNative, int numElements) where TMarshaler : IArrayElementMarshaler + public static unsafe void ConvertArrayContentsToManaged(Array managed, byte* pNative, int numElements) + where TMarshaler : IArrayMarshaler { - ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managed)); - for (int i = 0; i < numElements; i++) - { - TMarshaler.ConvertToManaged(ref Unsafe.Add(ref firstElement, i), pNative); - pNative += TMarshaler.UnmanagedSize; - } + TMarshaler.ConvertContentsToManaged(managed, pNative, numElements); } [UnmanagedCallersOnly] @@ -2337,46 +2429,25 @@ internal static unsafe void InvokeArrayContentsConverter( } } - internal static unsafe void FreeArrayContents(byte* pNative, int length) where TMarshaler : IArrayElementMarshaler + internal static unsafe void FreeArrayContents(byte* pNative, int length) where TMarshaler : IArrayMarshaler { - for (int i = 0; i < length; i++) - { - TMarshaler.Free(pNative); - pNative += TMarshaler.UnmanagedSize; - } + TMarshaler.FreeContents(pNative, length); } public static unsafe byte* ConvertArraySpaceToNative(Array? managed) - where TMarshaler : IArrayElementMarshaler + where TMarshaler : IArrayMarshaler { - if (managed is null) - { - return null; - } - else - { - int nativeBytes = checked(managed.Length * (int)TMarshaler.AllocationSize); - byte* pNative = (byte*)Marshal.AllocCoTaskMem(nativeBytes); - NativeMemory.Clear(pNative, (nuint)nativeBytes); - return pNative; - } + return TMarshaler.AllocateSpaceForUnmanaged(managed); } internal static unsafe T[]? ConvertArraySpaceToManaged(byte* pNativeHome, int cElements) - where TMarshaler : IArrayElementMarshaler + where TMarshaler : IArrayMarshaler { - if (pNativeHome == null) - { - return null; - } - else - { - return new T[cElements]; - } + return (T[]?)TMarshaler.AllocateSpaceForManaged(pNativeHome, cElements); } internal static unsafe void ClearArrayNative(byte* pNativeHome, int cElements) - where TMarshaler : IArrayElementMarshaler + where TMarshaler : IArrayMarshaler { if (pNativeHome != null) { diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index bc295c7797e857..e449215e5741e9 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1240,7 +1240,7 @@ DEFINE_CLASS(COMVARIANT, Marshalling, ComVariant) DEFINE_CLASS(VARIANT_BOOL_MARSHALER, StubHelpers, VariantBoolMarshaler) DEFINE_CLASS(BOOL_MARSHALER, StubHelpers, BoolMarshaler`1) DEFINE_CLASS(LPWSTR_MARSHALER, StubHelpers, LPWSTRMarshaler) -DEFINE_CLASS(ANSICHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, AnsiCharArrayElementMarshaler`2) +DEFINE_CLASS(ANSICHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, AnsiCharArrayMarshaler`2) DEFINE_CLASS(UNICODECHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, UnicodeCharArrayElementMarshaler) DEFINE_CLASS(LPSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, LPSTRArrayElementMarshaler`2) DEFINE_CLASS(BSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, BSTRArrayElementMarshaler) diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index 7c3299c7516e47..38d3b0f1e182a9 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3268,7 +3268,7 @@ class ILMngdMarshaler : public ILMarshaler const BinderMethodID m_idClearManaged; }; -// Base class for array marshalers that use managed IArrayElementMarshaler implementations +// Base class for array marshalers that use managed IArrayMarshaler implementations // for element-by-element conversion. Provides shared VT-to-marshaler-type resolution and // generic method instantiation for ConvertArrayContents/FreeArrayContents helpers. class ILArrayMarshalerBase : public ILMarshaler diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index da8a34cc7c9901..e520cd982c2f75 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1832,7 +1832,7 @@ BASEARRAYREF OleVariant::CreateArrayRefForSafeArray(SAFEARRAY *pSafeArray, VARTY namespace { - // Returns the managed IArrayElementMarshaler MethodTable for a given VARTYPE. + // Returns the managed IArrayMarshaler MethodTable for a given VARTYPE. // This mirrors the logic in ILArrayMarshalerBase::GetMarshalerMT for SAFEARRAY-compatible types. MethodTable* GetMarshalerMTForSafeArrayVarType(VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous) { @@ -2065,7 +2065,7 @@ void OleVariant::MarshalSafeArrayForArrayRef(BASEARRAYREF *pArrayRef, Array = *pArrayRef; } - // Use managed IArrayElementMarshaler implementations for content conversion. + // Use managed IArrayMarshaler implementations for content conversion. UnmanagedCallersOnlyCaller invoker(METHOD__STUBHELPERS__INVOKE_ARRAY_CONTENTS_CONVERTER); invoker.InvokeThrowing(&Array, pSafeArray->pvData, (INT32)dwNumComponents, (void*)pConvertContentsCode); @@ -2122,7 +2122,7 @@ void OleVariant::MarshalArrayRefForSafeArray(SAFEARRAY *pSafeArray, pSrcData = (BYTE*)pSafeArray->pvData; } - // Use managed IArrayElementMarshaler implementations for content conversion. + // Use managed IArrayMarshaler implementations for content conversion. UnmanagedCallersOnlyCaller invoker(METHOD__STUBHELPERS__INVOKE_ARRAY_CONTENTS_CONVERTER); invoker.InvokeThrowing(pArrayRef, pSrcData, (INT32)dwNumComponents, (void*)pConvertContentsCode); } From 125083f55ff2538394af3bfb6fe6988531f83c6a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 12 Apr 2026 22:26:08 -0700 Subject: [PATCH 19/41] Remove ILMarshaler method overrides from ILArrayMarshalerBase Both ILNativeArrayMarshaler and ILFixedArrayMarshaler override all ILMarshaler virtual methods that ILArrayMarshalerBase provided, making the base implementations dead code. Remove them and move the remaining functionality: - EmitConvertContentsCLRToNative/NativeToCLR: already overridden by both derived types, remove from base (dead code). - EmitClearNativeContents: move to ILFixedArrayMarshaler (the only consumer; ILNativeArrayMarshaler already had its own override). - GetMarshalerAndElementTypes/GetInstantiatedArrayMethod: convert from member functions to free functions in an anonymous namespace, taking MarshalInfo* as a parameter instead of accessing m_pargs. ILArrayMarshalerBase now only provides the trivial inline overrides (GetNativeType, GetManagedType, NeedsClearNative) and the pure virtual EmitLoadNativeSize. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 4 +- src/coreclr/vm/ilmarshalers.cpp | 324 +++++++++--------- src/coreclr/vm/ilmarshalers.h | 16 +- 3 files changed, 156 insertions(+), 188 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 89387d2577e932..2aa051b4dfac02 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -2440,10 +2440,10 @@ internal static unsafe void FreeArrayContents(byte* pNative, int return TMarshaler.AllocateSpaceForUnmanaged(managed); } - internal static unsafe T[]? ConvertArraySpaceToManaged(byte* pNativeHome, int cElements) + internal static unsafe Array? ConvertArraySpaceToManaged(byte* pNativeHome, int cElements) where TMarshaler : IArrayMarshaler { - return (T[]?)TMarshaler.AllocateSpaceForManaged(pNativeHome, cElements); + return TMarshaler.AllocateSpaceForManaged(pNativeHome, cElements); } internal static unsafe void ClearArrayNative(byte* pNativeHome, int cElements) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index efadfe7d35fa17..ca45b0ab573235 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4118,172 +4118,18 @@ void ILNativeArrayMarshaler::EmitLoadElementCount(ILCodeStream* pslILEmit) } } -void ILNativeArrayMarshaler::EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - if (IsByref(m_dwMarshalFlags)) - { - // Reset the element count in case EmitLoadElementCount throws. - _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); - pslILEmit->EmitLDC(0); - pslILEmit->EmitSTLOC(m_dwSavedSizeArg); - } - - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_MANAGED); - - EmitLoadNativeValue(pslILEmit); - - // Dynamically calculate element count using SizeParamIndex argument - EmitLoadElementCount(pslILEmit); - - if (IsByref(m_dwMarshalFlags)) - { - // Save the native array size and reload it - _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); - pslILEmit->EmitSTLOC(m_dwSavedSizeArg); - pslILEmit->EmitLDLOC(m_dwSavedSizeArg); - } - - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 1); - EmitStoreManagedValue(pslILEmit); -} - -void ILNativeArrayMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - if (IsByref(m_dwMarshalFlags)) - { - _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); - - // Save the array size before converting it to native - EmitLoadManagedValue(pslILEmit); - ILCodeLabel *pManagedHomeIsNull = pslILEmit->NewCodeLabel(); - pslILEmit->EmitBRFALSE(pManagedHomeIsNull); - EmitLoadManagedValue(pslILEmit); - pslILEmit->EmitLDLEN(); - pslILEmit->EmitCONV_OVF_I4(); - pslILEmit->EmitSTLOC(m_dwSavedSizeArg); - - pslILEmit->EmitLabel(pManagedHomeIsNull); - } - - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_NATIVE); - - EmitLoadManagedValue(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 1, 1); - EmitStoreNativeValue(pslILEmit); -} - -void ILNativeArrayMarshaler::EmitClearNative(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CLEAR_ARRAY_NATIVE); - - EmitLoadNativeValue(pslILEmit); - EmitLoadNativeSize(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); -} -void ILNativeArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - if (IsByref(m_dwMarshalFlags)) - { - _ASSERT(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); - pslILEmit->EmitLDLOC(m_dwSavedSizeArg); - } - else - { - pslILEmit->EmitLDC(0); - EmitLoadManagedValue(pslILEmit); - ILCodeLabel *pManagedHomeIsNull = pslILEmit->NewCodeLabel(); - pslILEmit->EmitBRFALSE(pManagedHomeIsNull); - pslILEmit->EmitPOP(); // Pop the 0 on the stack - EmitLoadManagedValue(pslILEmit); - pslILEmit->EmitLDLEN(); - pslILEmit->EmitCONV_OVF_I4(); - pslILEmit->EmitLabel(pManagedHomeIsNull); // Keep the 0 on the stack - } -} - -void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); - - EmitLoadManagedValue(pslILEmit); - EmitLoadNativeValue(pslILEmit); - EmitLoadManagedValue(pslILEmit); - pslILEmit->EmitLDLEN(); - pslILEmit->EmitCONV_I4(); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); -} - -void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); - - EmitLoadManagedValue(pslILEmit); - EmitLoadNativeValue(pslILEmit); - EmitLoadManagedValue(pslILEmit); - pslILEmit->EmitLDLEN(); - pslILEmit->EmitCONV_I4(); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); -} - -void ILNativeArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__FREE_ARRAY_CONTENTS); - - EmitLoadNativeValue(pslILEmit); - EmitLoadNativeSize(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); -} - -void ILArrayMarshalerBase::EmitClearNativeContents(ILCodeStream* pslILEmit) -{ - STANDARD_VM_CONTRACT; - - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__FREE_ARRAY_CONTENTS); - - EmitLoadNativeHomeAddr(pslILEmit); - EmitLoadNativeSize(pslILEmit); - pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); -} - -void ILNativeArrayMarshaler::EmitSetupArgumentForMarshalling(ILCodeStream* pslILEmit) -{ - if (IsByref(m_dwMarshalFlags)) - { - EmitNewSavedSizeArgLocal(pslILEmit); - } -} - -void ILNativeArrayMarshaler::EmitNewSavedSizeArgLocal(ILCodeStream* pslILEmit) +namespace { - STANDARD_VM_CONTRACT; - - _ASSERTE(m_dwSavedSizeArg == LOCAL_NUM_UNUSED); - m_dwSavedSizeArg = pslILEmit->NewLocal(ELEMENT_TYPE_I4); - pslILEmit->EmitLDC(0); - pslILEmit->EmitSTLOC(m_dwSavedSizeArg); -} - -void ILArrayMarshalerBase::GetMarshalerAndElementTypes(MethodTable** ppMarshalerMT, TypeHandle* pElementType) + // Resolve the managed marshaler MethodTable and the element type it marshals. + // Both are returned together to guarantee they are consistent (e.g. for enums, + // the element type is the underlying primitive, matching the marshaler's T). + void GetMarshalerAndElementTypes(MarshalInfo* pMarshalInfo, MethodTable** ppMarshalerMT, TypeHandle* pElementType) { STANDARD_VM_CONTRACT; CREATE_MARSHALER_CARRAY_OPERANDS mops; - m_pargs->m_pMarshalInfo->GetMops(&mops); + pMarshalInfo->GetMops(&mops); VARTYPE vt = mops.elementType; bool bestFit = mops.bestfitmapping != 0; bool throwOnUnmappable = mops.throwonunmappablechar != 0; @@ -4433,7 +4279,7 @@ void ILArrayMarshalerBase::GetMarshalerAndElementTypes(MethodTable** ppMarshaler case VT_UNKNOWN: case VT_DISPATCH: { - TypeHandle arrayElementTypeHandle = m_pargs->m_pMarshalInfo->GetArrayElementTypeHandle(); + TypeHandle arrayElementTypeHandle = pMarshalInfo->GetArrayElementTypeHandle(); if (arrayElementTypeHandle == TypeHandle(g_pObjectClass)) { TypeHandle thDispatch(vt == VT_DISPATCH ? pEnabledMT : pDisabledMT); @@ -4471,7 +4317,8 @@ void ILArrayMarshalerBase::GetMarshalerAndElementTypes(MethodTable** ppMarshaler } -MethodDesc* ILArrayMarshalerBase::GetInstantiatedArrayMethod(BinderMethodID methodId) + // Instantiate one of the generic StubHelpers array methods with the element type and marshaler type. + MethodDesc* GetInstantiatedArrayMethod(MarshalInfo* pMarshalInfo, BinderMethodID methodId) { STANDARD_VM_CONTRACT; @@ -4481,7 +4328,7 @@ MethodDesc* ILArrayMarshalerBase::GetInstantiatedArrayMethod(BinderMethodID meth // to guarantee they are consistent (e.g. enums map to their underlying type). TypeHandle thElementType; MethodTable* pMarshalerMT; - GetMarshalerAndElementTypes(&pMarshalerMT, &thElementType); + GetMarshalerAndElementTypes(pMarshalInfo, &pMarshalerMT, &thElementType); TypeHandle thMarshalerType(pMarshalerMT); TypeHandle thArgs[2] = { thElementType, thMarshalerType }; @@ -4495,35 +4342,158 @@ MethodDesc* ILArrayMarshalerBase::GetInstantiatedArrayMethod(BinderMethodID meth return pInstMD; } +} // anonymous namespace + +void ILNativeArrayMarshaler::EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + if (IsByref(m_dwMarshalFlags)) + { + // Reset the element count in case EmitLoadElementCount throws. + _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); + pslILEmit->EmitLDC(0); + pslILEmit->EmitSTLOC(m_dwSavedSizeArg); + } + + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_MANAGED); + + EmitLoadNativeValue(pslILEmit); + + // Dynamically calculate element count using SizeParamIndex argument + EmitLoadElementCount(pslILEmit); + + if (IsByref(m_dwMarshalFlags)) + { + // Save the native array size and reload it + _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); + pslILEmit->EmitSTLOC(m_dwSavedSizeArg); + pslILEmit->EmitLDLOC(m_dwSavedSizeArg); + } + + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 1); + EmitStoreManagedValue(pslILEmit); +} -void ILArrayMarshalerBase::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) +void ILNativeArrayMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); + if (IsByref(m_dwMarshalFlags)) + { + _ASSERTE(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); + + // Save the array size before converting it to native + EmitLoadManagedValue(pslILEmit); + ILCodeLabel *pManagedHomeIsNull = pslILEmit->NewCodeLabel(); + pslILEmit->EmitBRFALSE(pManagedHomeIsNull); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDLEN(); + pslILEmit->EmitCONV_OVF_I4(); + pslILEmit->EmitSTLOC(m_dwSavedSizeArg); + + pslILEmit->EmitLabel(pManagedHomeIsNull); + } + + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_SPACE_TO_NATIVE); EmitLoadManagedValue(pslILEmit); - EmitLoadNativeHomeAddr(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 1, 1); + EmitStoreNativeValue(pslILEmit); +} + +void ILNativeArrayMarshaler::EmitClearNative(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CLEAR_ARRAY_NATIVE); + + EmitLoadNativeValue(pslILEmit); + EmitLoadNativeSize(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + +void ILNativeArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + if (IsByref(m_dwMarshalFlags)) + { + _ASSERT(m_dwSavedSizeArg != LOCAL_NUM_UNUSED); + pslILEmit->EmitLDLOC(m_dwSavedSizeArg); + } + else + { + pslILEmit->EmitLDC(0); + EmitLoadManagedValue(pslILEmit); + ILCodeLabel *pManagedHomeIsNull = pslILEmit->NewCodeLabel(); + pslILEmit->EmitBRFALSE(pManagedHomeIsNull); + pslILEmit->EmitPOP(); // Pop the 0 on the stack + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDLEN(); + pslILEmit->EmitCONV_OVF_I4(); + pslILEmit->EmitLabel(pManagedHomeIsNull); // Keep the 0 on the stack + } +} + +void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); + + EmitLoadManagedValue(pslILEmit); + EmitLoadNativeValue(pslILEmit); EmitLoadManagedValue(pslILEmit); pslILEmit->EmitLDLEN(); pslILEmit->EmitCONV_I4(); pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); } -void ILArrayMarshalerBase::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) +void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); EmitLoadManagedValue(pslILEmit); - EmitLoadNativeHomeAddr(pslILEmit); + EmitLoadNativeValue(pslILEmit); EmitLoadManagedValue(pslILEmit); pslILEmit->EmitLDLEN(); pslILEmit->EmitCONV_I4(); pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); } +void ILNativeArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__FREE_ARRAY_CONTENTS); + + EmitLoadNativeValue(pslILEmit); + EmitLoadNativeSize(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + +void ILNativeArrayMarshaler::EmitSetupArgumentForMarshalling(ILCodeStream* pslILEmit) +{ + if (IsByref(m_dwMarshalFlags)) + { + EmitNewSavedSizeArgLocal(pslILEmit); + } +} + +void ILNativeArrayMarshaler::EmitNewSavedSizeArgLocal(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + _ASSERTE(m_dwSavedSizeArg == LOCAL_NUM_UNUSED); + m_dwSavedSizeArg = pslILEmit->NewLocal(ELEMENT_TYPE_I4); + pslILEmit->EmitLDC(0); + pslILEmit->EmitSTLOC(m_dwSavedSizeArg); +} + + // ==================== ILFixedArrayMarshaler ==================== void ILFixedArrayMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) @@ -4572,7 +4542,7 @@ void ILFixedArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEm CREATE_MARSHALER_CARRAY_OPERANDS mops; m_pargs->m_pMarshalInfo->GetMops(&mops); - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); EmitLoadManagedValue(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); @@ -4587,7 +4557,7 @@ void ILFixedArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEm CREATE_MARSHALER_CARRAY_OPERANDS mops; m_pargs->m_pMarshalInfo->GetMops(&mops); - MethodDesc* pMD = GetInstantiatedArrayMethod(METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); EmitLoadManagedValue(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); @@ -4604,6 +4574,17 @@ void ILFixedArrayMarshaler::EmitLoadNativeSize(ILCodeStream* pslILEmit) pslILEmit->EmitLDC(mops.additive); } +void ILFixedArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) +{ + STANDARD_VM_CONTRACT; + + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__FREE_ARRAY_CONTENTS); + + EmitLoadNativeHomeAddr(pslILEmit); + EmitLoadNativeSize(pslILEmit); + pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 2, 0); +} + void ILFixedArrayMarshaler::EmitClearNative(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; @@ -4934,3 +4915,4 @@ void ILReferenceCustomMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit pslILEmit->EmitSTLOC(m_dwMngdMarshalerLocalNum); // Store the ICustomMarshaler as our marshaler state } + diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index 38d3b0f1e182a9..eeb34e5f79ae11 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3268,9 +3268,6 @@ class ILMngdMarshaler : public ILMarshaler const BinderMethodID m_idClearManaged; }; -// Base class for array marshalers that use managed IArrayMarshaler implementations -// for element-by-element conversion. Provides shared VT-to-marshaler-type resolution and -// generic method instantiation for ConvertArrayContents/FreeArrayContents helpers. class ILArrayMarshalerBase : public ILMarshaler { protected: @@ -3292,18 +3289,6 @@ class ILArrayMarshalerBase : public ILMarshaler return true; } - void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; - void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; - void EmitClearNativeContents(ILCodeStream* pslILEmit) override; - - // Resolve the managed marshaler MethodTable and the element type it marshals. - // Both are returned together to guarantee they are consistent (e.g. for enums, - // the element type is the underlying primitive, matching the marshaler's T). - void GetMarshalerAndElementTypes(MethodTable** ppMarshalerMT, TypeHandle* pElementType); - - // Instantiate one of the generic StubHelpers array methods with the element type and marshaler type. - MethodDesc* GetInstantiatedArrayMethod(BinderMethodID methodId); - // Load the native element count onto the evaluation stack. // Subclasses implement this differently (dynamic size param vs. fixed count). virtual void EmitLoadNativeSize(ILCodeStream* pslILEmit) = 0; @@ -3382,6 +3367,7 @@ class ILFixedArrayMarshaler : public ILArrayMarshalerBase void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; void EmitLoadNativeSize(ILCodeStream* pslILEmit) override; + void EmitClearNativeContents(ILCodeStream* pslILEmit) override; void EmitClearNative(ILCodeStream* pslILEmit) override; }; From 8c336eec0e7f9d3a22b4c36b61622d73cf342807 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Apr 2026 17:36:33 +0000 Subject: [PATCH 20/41] Support enum arrays in managed array marshalling and fix ANSI char buffer sizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove enum-to-underlying-type normalization from GetMarshalerAndElementTypes; enums now use StructureMarshaler directly. - Update MarshalNative_HasLayout to return true for enums (blittable, sized by underlying type). - Skip intrinsic IL generation for enums in TryGenerateStructMarshallingMethod (same as blittable types — default memcpy body is correct). - Fix AnsiCharArrayMarshaler buffer sizing: bufferLength and allocSize now account for SystemMaxDBCSCharSize per element plus null terminator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/StubHelpers.cs | 9 +++++---- src/coreclr/vm/dllimport.cpp | 6 +++--- src/coreclr/vm/ilmarshalers.cpp | 6 ------ src/coreclr/vm/marshalnative.cpp | 8 ++++++++ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 89387d2577e932..3d908e6aae8e0c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1741,7 +1741,8 @@ public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* u ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), length); string str = new(chars); - Marshal.StringToAnsiString(str, unmanaged, length + 1, TBestFit.Enabled, TThrowOnUnmappable.Enabled); + int bufferLength = checked((length + 1) * Marshal.SystemMaxDBCSCharSize); + Marshal.StringToAnsiString(str, unmanaged, bufferLength, TBestFit.Enabled, TThrowOnUnmappable.Enabled); } public static unsafe void ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) @@ -1768,9 +1769,9 @@ public static unsafe void FreeContents(byte* unmanaged, int length) return null; } - // Each char can map to at most SystemMaxDBCSCharSize bytes during conversion, - // but the final native layout uses 1 byte per element. - int allocSize = checked(managedArray.Length * Marshal.SystemMaxDBCSCharSize); + // Each char can map to at most SystemMaxDBCSCharSize bytes during conversion. + // StringToAnsiString also writes a null terminator, so allocate space for it. + int allocSize = checked((managedArray.Length + 1) * Marshal.SystemMaxDBCSCharSize); byte* pNative = (byte*)Marshal.AllocCoTaskMem(allocSize); NativeMemory.Clear(pNative, (nuint)allocSize); return pNative; diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index 386c1033ea9218..f82683d60bc744 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -4140,10 +4140,10 @@ bool StructMarshalStubs::TryGenerateStructMarshallingMethod(MethodDesc* pMD, Dyn _ASSERTE(pStructMT->IsValueType()); - if (pStructMT->IsBlittable()) + if (pStructMT->IsBlittable() || pStructMT->IsEnum()) { - // No need to generate stubs for blittable types since they can be marshaled by value without any transformation. - // The default IL implementation is correct. + // No need to generate stubs for blittable types or enums since they can be marshaled + // by value without any transformation. The default IL implementation is correct. return false; } diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index efadfe7d35fa17..030b812864ccb6 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4291,12 +4291,6 @@ void ILArrayMarshalerBase::GetMarshalerAndElementTypes(MethodTable** ppMarshaler // Start from the managed element type - this is the authoritative source. MethodTable* pElementMT = mops.methodTable; - // Enums marshal as their underlying primitive type. - if (pElementMT->IsEnum()) - { - pElementMT = CoreLibBinder::GetElementType(pElementMT->GetInternalCorElementType()); - } - TypeHandle thElement(pElementMT); MethodTable* pEnabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_ENABLED); diff --git a/src/coreclr/vm/marshalnative.cpp b/src/coreclr/vm/marshalnative.cpp index db6bfa48942440..23ec1944f4a619 100644 --- a/src/coreclr/vm/marshalnative.cpp +++ b/src/coreclr/vm/marshalnative.cpp @@ -111,6 +111,14 @@ extern "C" BOOL QCALLTYPE MarshalNative_HasLayout(QCall::TypeHandle t, BOOL* pIs *pNativeSize = th.GetMethodTable()->GetNativeSize(); ret = TRUE; } + else if (th.IsEnum()) + { + // Enums don't have native layout info, but they marshal identically + // to their underlying primitive type and are always blittable. + *pIsBlittable = TRUE; + *pNativeSize = th.GetSize(); + ret = TRUE; + } else { *pIsBlittable = FALSE; From 645da021390cdffb886694503a6c631d172b522f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Apr 2026 20:33:04 +0000 Subject: [PATCH 21/41] Fix enum support. --- .../src/System/StubHelpers.cs | 2 +- src/coreclr/vm/marshalnative.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 157e72f4b9310b..6b04adbd95cb7a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1352,7 +1352,7 @@ static unsafe void IArrayElementMarshaler>.Free(byte* u private static class SizeHolder { - public static readonly int UnmanagedSize = Marshal.SizeOf(); + public static readonly int UnmanagedSize = typeof(T).IsEnum ? Marshal.SizeOf(Enum.GetUnderlyingType(typeof(T))) : Marshal.SizeOf(); } private static int UnmanagedSize diff --git a/src/coreclr/vm/marshalnative.cpp b/src/coreclr/vm/marshalnative.cpp index 23ec1944f4a619..b9163b700bad00 100644 --- a/src/coreclr/vm/marshalnative.cpp +++ b/src/coreclr/vm/marshalnative.cpp @@ -105,20 +105,20 @@ extern "C" BOOL QCALLTYPE MarshalNative_HasLayout(QCall::TypeHandle t, BOOL* pIs BEGIN_QCALL; TypeHandle th = t.AsTypeHandle(); + + if (th.IsEnum()) + { + // Enums don't have native layout info, but they marshal identically + // to their underlying primitive type. + th = CoreLibBinder::GetElementType(th.GetVerifierCorElementType()); + } + if (th.HasLayout()) { *pIsBlittable = th.IsBlittable(); *pNativeSize = th.GetMethodTable()->GetNativeSize(); ret = TRUE; } - else if (th.IsEnum()) - { - // Enums don't have native layout info, but they marshal identically - // to their underlying primitive type and are always blittable. - *pIsBlittable = TRUE; - *pNativeSize = th.GetSize(); - ret = TRUE; - } else { *pIsBlittable = FALSE; From df66fbe1c39c5e47bdf2b4ea4513242815768bc0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Apr 2026 20:50:42 +0000 Subject: [PATCH 22/41] Fix AsAny array marshaling: throw correct exception for unsupported types and free buffer on failure - Throw ArgumentException(SR.Arg_PInvokeBadObject) for unsupported element types instead of producing an empty generic args array that causes a reflection ArgumentException. - Wrap convertContentsToNative in try/catch to free the allocated native buffer if content marshaling throws, preventing a CoTaskMem leak. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 6b04adbd95cb7a..032f33971d7c9b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -955,7 +955,7 @@ TypeCode.Char when IsAnsi(dwFlags) => [ ? typeof(IMarshalerOption.EnabledOption) : typeof(IMarshalerOption.DisabledOption)])], TypeCode.Boolean => [typeof(bool), typeof(BoolMarshaler)], - _ => [] + _ => throw new ArgumentException(SR.Arg_PInvokeBadObject) }; arrayMarshalerMethods = new ArrayMarshalerMethods @@ -985,10 +985,18 @@ TypeCode.Char when IsAnsi(dwFlags) => [ byte* pNativeHome = arrayMarshalerMethods.convertSpaceToNative((Array)pManagedHome); - if (IsIn(dwFlags)) + try + { + if (IsIn(dwFlags)) + { + Array managedArray = (Array)pManagedHome; + arrayMarshalerMethods.convertContentsToNative(managedArray, pNativeHome, managedArray.Length); + } + } + catch { - Array managedArray = (Array)pManagedHome; - arrayMarshalerMethods.convertContentsToNative(managedArray, pNativeHome, managedArray.Length); + Marshal.FreeCoTaskMem((IntPtr)pNativeHome); + throw; } if (IsOut(dwFlags)) From acafad8805cd77978277aaecc9ef2fce827526f4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Apr 2026 21:08:04 +0000 Subject: [PATCH 23/41] Fix stale comments about enum normalization in GetMarshalerAndElementTypes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/ilmarshalers.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index b2ac9a457da1e7..9a7d494b3a5edb 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4122,8 +4122,7 @@ void ILNativeArrayMarshaler::EmitLoadElementCount(ILCodeStream* pslILEmit) namespace { // Resolve the managed marshaler MethodTable and the element type it marshals. - // Both are returned together to guarantee they are consistent (e.g. for enums, - // the element type is the underlying primitive, matching the marshaler's T). + // Both are returned together to guarantee they are consistent. void GetMarshalerAndElementTypes(MarshalInfo* pMarshalInfo, MethodTable** ppMarshalerMT, TypeHandle* pElementType) { STANDARD_VM_CONTRACT; @@ -4319,7 +4318,7 @@ namespace MethodDesc* pGenericMD = CoreLibBinder::GetMethod(methodId); // Get both the element type and marshaler type from a single source - // to guarantee they are consistent (e.g. enums map to their underlying type). + // to guarantee they are consistent. TypeHandle thElementType; MethodTable* pMarshalerMT; GetMarshalerAndElementTypes(pMarshalInfo, &pMarshalerMT, &thElementType); From 58df463eb39aa6b8b280adb704a2f9aa77f8536c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Apr 2026 23:20:37 +0000 Subject: [PATCH 24/41] Refactor AsAnyMarshaler to use polymorphic subclasses with constructor-based dispatch Move type dispatch logic into a constructor and ConvertToNative logic into AsAnyMarshalerImplementation subclasses, eliminating reflection-based function pointers and the BackPropAction enum. Each marshaling case (array, string, StringBuilder, layout) is now a sealed subclass with its own ConvertToNative, ConvertToManaged, and ClearNative implementations. The array subclass is generic to avoid reflection entirely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 529 +++++++++--------- src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/ilmarshalers.cpp | 6 + src/coreclr/vm/metasig.h | 1 + 4 files changed, 263 insertions(+), 274 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 032f33971d7c9b..93599ffb8cd66d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -882,372 +882,353 @@ internal static void GetCustomMarshalerInstance(void* pMT, byte* pCookie, int cC internal struct AsAnyMarshaler { - private enum BackPropAction + private AsAnyMarshalerImplementation? _impl; + + private abstract class AsAnyMarshalerImplementation { - None, - Array, - Layout, - StringBuilderAnsi, - StringBuilderUnicode + public abstract IntPtr ConvertToNative(object managed, int dwFlags); + public abstract void ConvertToManaged(object managed, IntPtr native); + public abstract void ClearNative(IntPtr native); } - private struct ArrayMarshalerMethods + private sealed class ArrayImplementation : AsAnyMarshalerImplementation + where TMarshaler : IArrayMarshaler { - public unsafe delegate* convertContentsToNative; - public unsafe delegate* convertContentsToManaged; - public unsafe delegate* convertSpaceToNative; - } + private readonly bool _isOut; - private ArrayMarshalerMethods arrayMarshalerMethods; + public ArrayImplementation(bool isOut) { _isOut = isOut; } - // Type of action to perform after the CLR-to-unmanaged call. - private BackPropAction backPropAction; + public override unsafe IntPtr ConvertToNative(object managed, int dwFlags) + { + Array array = (Array)managed; + byte* pNative = TMarshaler.AllocateSpaceForUnmanaged(array); + try + { + if (IsIn(dwFlags)) + TMarshaler.ConvertContentsToUnmanaged(array, pNative, array.Length); + } + catch + { + Marshal.FreeCoTaskMem((IntPtr)pNative); + throw; + } - // The managed layout type for BackPropAction.Layout. - private Type? layoutType; + return (IntPtr)pNative; + } - // Cleanup list to be destroyed when clearing the native view (for layouts with SafeHandles). - private CleanupWorkListElement? cleanupWorkList; + public override unsafe void ConvertToManaged(object managed, IntPtr native) + { + if (!_isOut) return; + Array array = (Array)managed; + TMarshaler.ConvertContentsToManaged(array, (byte*)native, array.Length); + } - [Flags] - internal enum AsAnyFlags - { - In = 0x10000000, - Out = 0x20000000, - IsAnsi = 0x00FF0000, - IsThrowOn = 0x0000FF00, - IsBestFit = 0x000000FF + public override void ClearNative(IntPtr native) + { + if (native != IntPtr.Zero) + Marshal.FreeCoTaskMem(native); + } } - private static bool IsIn(int dwFlags) => (dwFlags & (int)AsAnyFlags.In) != 0; - private static bool IsOut(int dwFlags) => (dwFlags & (int)AsAnyFlags.Out) != 0; - private static bool IsAnsi(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsAnsi) != 0; - private static bool IsThrowOn(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsThrowOn) != 0; - private static bool IsBestFit(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsBestFit) != 0; + private sealed class StringImplementation : AsAnyMarshalerImplementation + { + public override unsafe IntPtr ConvertToNative(object managed, int dwFlags) + { + string str = (string)managed; - #region ConvertToNative helpers - - private unsafe IntPtr ConvertArrayToNative(object pManagedHome, int dwFlags) - { - Type elementType = pManagedHome.GetType().GetElementType()!; - - Type[] marshalerGenericArgs = Type.GetTypeCode(elementType) switch - { - TypeCode.SByte - or TypeCode.Byte - or TypeCode.Int16 - or TypeCode.UInt16 - or TypeCode.Int32 - or TypeCode.UInt32 - or TypeCode.Int64 - or TypeCode.UInt64 - or TypeCode.Single - or TypeCode.Double => [elementType, typeof(StructureMarshaler<>).MakeGenericType(elementType)], - TypeCode.Object when elementType == typeof(nint) || elementType == typeof(nuint) => [elementType, typeof(StructureMarshaler<>).MakeGenericType(elementType)], - TypeCode.Char when !IsAnsi(dwFlags) => [typeof(char), typeof(UnicodeCharArrayElementMarshaler)], - TypeCode.Char when IsAnsi(dwFlags) => [ - typeof(char), - typeof(AnsiCharArrayMarshaler<,>).MakeGenericType([ - IsBestFit(dwFlags) - ? typeof(IMarshalerOption.EnabledOption) - : typeof(IMarshalerOption.DisabledOption), - IsThrowOn(dwFlags) - ? typeof(IMarshalerOption.EnabledOption) - : typeof(IMarshalerOption.DisabledOption)])], - TypeCode.Boolean => [typeof(bool), typeof(BoolMarshaler)], - _ => throw new ArgumentException(SR.Arg_PInvokeBadObject) - }; + // IsIn, IsOut are ignored for strings - they're always in-only + if (IsAnsi(dwFlags)) + { + return CSTRMarshaler.ConvertToNative( + dwFlags & 0xFFFF, // (throw on unmappable char << 8 | best fit) + str, + IntPtr.Zero); // unmanaged buffer will be allocated + } - arrayMarshalerMethods = new ArrayMarshalerMethods - { - convertContentsToManaged = (delegate*) - typeof(StubHelpers) - .GetMethod( - nameof(StubHelpers.ConvertArrayContentsToManaged), - BindingFlags.Public | BindingFlags.Static)! - .MakeGenericMethod(marshalerGenericArgs) - .MethodHandle.GetFunctionPointer(), - convertContentsToNative = (delegate*) - typeof(StubHelpers) - .GetMethod( - nameof(StubHelpers.ConvertArrayContentsToUnmanaged), - BindingFlags.Public | BindingFlags.Static)! - .MakeGenericMethod(marshalerGenericArgs) - .MethodHandle.GetFunctionPointer(), - convertSpaceToNative = (delegate*) - typeof(StubHelpers) - .GetMethod( - nameof(StubHelpers.ConvertArraySpaceToNative), - BindingFlags.Public | BindingFlags.Static)! - .MakeGenericMethod(marshalerGenericArgs) - .MethodHandle.GetFunctionPointer(), - }; + int allocSize = (str.Length + 1) * 2; + IntPtr pNative = Marshal.AllocCoTaskMem(allocSize); + Buffer.Memmove(ref *(char*)pNative, ref str.GetRawStringData(), (nuint)str.Length + 1); + + return pNative; + } - byte* pNativeHome = arrayMarshalerMethods.convertSpaceToNative((Array)pManagedHome); + public override void ConvertToManaged(object managed, IntPtr native) { } - try + public override void ClearNative(IntPtr native) { - if (IsIn(dwFlags)) - { - Array managedArray = (Array)pManagedHome; - arrayMarshalerMethods.convertContentsToNative(managedArray, pNativeHome, managedArray.Length); - } + if (native != IntPtr.Zero) + Marshal.FreeCoTaskMem(native); } - catch + } + + private sealed class LayoutImplementation : AsAnyMarshalerImplementation + { + private readonly Type _layoutType; + private readonly bool _isOut; + internal CleanupWorkListElement? _cleanupWorkList; + + public LayoutImplementation(Type layoutType, bool isOut) { - Marshal.FreeCoTaskMem((IntPtr)pNativeHome); - throw; + _layoutType = layoutType; + _isOut = isOut; } - if (IsOut(dwFlags)) + public override unsafe IntPtr ConvertToNative(object managed, int dwFlags) { - backPropAction = BackPropAction.Array; - } + // Note that the following call will not throw exception if the type + // of managed is not marshalable. That's intentional because we + // want to maintain the original behavior where this was indicated + // by TypeLoadException during the actual field marshaling. + int allocSize = Marshal.SizeOfHelper((RuntimeType)_layoutType, false); + IntPtr pNative = Marshal.AllocCoTaskMem(allocSize); - return (IntPtr)pNativeHome; - } + if (IsIn(dwFlags)) + { + StubHelpers.LayoutTypeConvertToUnmanaged(managed, (byte*)pNative, ref _cleanupWorkList); + } - private static IntPtr ConvertStringToNative(string pManagedHome, int dwFlags) - { - IntPtr pNativeHome; + return pNative; + } - // IsIn, IsOut are ignored for strings - they're always in-only - if (IsAnsi(dwFlags)) + public override unsafe void ConvertToManaged(object managed, IntPtr native) { - // marshal the object as Ansi string (UnmanagedType.LPStr) - pNativeHome = CSTRMarshaler.ConvertToNative( - dwFlags & 0xFFFF, // (throw on unmappable char << 8 | best fit) - pManagedHome, // - IntPtr.Zero); // unmanaged buffer will be allocated + if (_isOut) + StubHelpers.LayoutTypeConvertToManaged(managed, (byte*)native); } - else + + public override void ClearNative(IntPtr native) { - // marshal the object as Unicode string (UnmanagedType.LPWStr) - int allocSize = (pManagedHome.Length + 1) * 2; - pNativeHome = Marshal.AllocCoTaskMem(allocSize); - unsafe + if (native != IntPtr.Zero) { - Buffer.Memmove(ref *(char*)pNativeHome, ref pManagedHome.GetRawStringData(), (nuint)pManagedHome.Length + 1); + Marshal.DestroyStructure(native, _layoutType); + Marshal.FreeCoTaskMem(native); } + StubHelpers.DestroyCleanupList(ref _cleanupWorkList); } - - return pNativeHome; } - private unsafe IntPtr ConvertStringBuilderToNative(StringBuilder pManagedHome, int dwFlags) + private sealed class StringBuilderAnsiImplementation : AsAnyMarshalerImplementation { - IntPtr pNativeHome; + private readonly bool _isOut; - // P/Invoke can be used to call Win32 apis that don't strictly follow CLR in/out semantics and thus may - // leave garbage in the buffer in circumstances that we can't detect. To prevent us from crashing when - // converting the contents back to managed, put a hidden NULL terminator past the end of the official buffer. + public StringBuilderAnsiImplementation(bool isOut) { _isOut = isOut; } - // Unmanaged layout: - // +====================================+ - // | Extra hidden NULL | - // +====================================+ \ - // | | | - // | [Converted] NULL-terminated string | |- buffer that the target may change - // | | | - // +====================================+ / <-- native home - - // Cache StringBuilder capacity and length to ensure we don't allocate a certain amount of - // native memory and then walk beyond its end if the StringBuilder concurrently grows erroneously. - int pManagedHomeCapacity = pManagedHome.Capacity; - int pManagedHomeLength = pManagedHome.Length; - if (pManagedHomeLength > pManagedHomeCapacity) + public override unsafe IntPtr ConvertToNative(object managed, int dwFlags) { - ThrowHelper.ThrowInvalidOperationException(); - } + StringBuilder sb = (StringBuilder)managed; - // Note that StringBuilder.Capacity is the number of characters NOT including any terminators. + // P/Invoke can be used to call Win32 apis that don't strictly follow CLR in/out semantics and thus may + // leave garbage in the buffer in circumstances that we can't detect. To prevent us from crashing when + // converting the contents back to managed, put a hidden NULL terminator past the end of the official buffer. - if (IsAnsi(dwFlags)) - { - StubHelpers.CheckStringLength(pManagedHomeCapacity); + // Unmanaged layout: + // +====================================+ + // | Extra hidden NULL | + // +====================================+ \ + // | | | + // | [Converted] NULL-terminated string | |- buffer that the target may change + // | | | + // +====================================+ / <-- native home + + // Cache StringBuilder capacity and length to ensure we don't allocate a certain amount of + // native memory and then walk beyond its end if the StringBuilder concurrently grows erroneously. + int capacity = sb.Capacity; + int length = sb.Length; + if (length > capacity) + ThrowHelper.ThrowInvalidOperationException(); - // marshal the object as Ansi string (UnmanagedType.LPStr) - int allocSize = checked((pManagedHomeCapacity * Marshal.SystemMaxDBCSCharSize) + 4); - pNativeHome = Marshal.AllocCoTaskMem(allocSize); + // Note that StringBuilder.Capacity is the number of characters NOT including any terminators. + StubHelpers.CheckStringLength(capacity); - byte* ptr = (byte*)pNativeHome; + int allocSize = checked((capacity * Marshal.SystemMaxDBCSCharSize) + 4); + IntPtr pNative = Marshal.AllocCoTaskMem(allocSize); + + byte* ptr = (byte*)pNative; *(ptr + allocSize - 3) = 0; *(ptr + allocSize - 2) = 0; *(ptr + allocSize - 1) = 0; if (IsIn(dwFlags)) { - int length = Marshal.StringToAnsiString(pManagedHome.ToString(), + int len = Marshal.StringToAnsiString(sb.ToString(), ptr, allocSize, IsBestFit(dwFlags), IsThrowOn(dwFlags)); - Debug.Assert(length < allocSize, "Expected a length less than the allocated size"); - } - if (IsOut(dwFlags)) - { - backPropAction = BackPropAction.StringBuilderAnsi; + Debug.Assert(len < allocSize, "Expected a length less than the allocated size"); } + + return pNative; } - else + + public override unsafe void ConvertToManaged(object managed, IntPtr native) { - // marshal the object as Unicode string (UnmanagedType.LPWStr) - int allocSize = checked((pManagedHomeCapacity * 2) + 4); - pNativeHome = Marshal.AllocCoTaskMem(allocSize); + if (!_isOut) return; + int length = native == IntPtr.Zero ? 0 : string.strlen((byte*)native); + ((StringBuilder)managed).ReplaceBufferAnsiInternal((sbyte*)native, length); + } - byte* ptr = (byte*)pNativeHome; + public override void ClearNative(IntPtr native) + { + if (native != IntPtr.Zero) + Marshal.FreeCoTaskMem(native); + } + } + + private sealed class StringBuilderUnicodeImplementation : AsAnyMarshalerImplementation + { + private readonly bool _isOut; + + public StringBuilderUnicodeImplementation(bool isOut) { _isOut = isOut; } + + public override unsafe IntPtr ConvertToNative(object managed, int dwFlags) + { + StringBuilder sb = (StringBuilder)managed; + + // See StringBuilderAnsiImplementation.ConvertToNative for buffer layout explanation. + + // Cache StringBuilder capacity and length to ensure we don't allocate a certain amount of + // native memory and then walk beyond its end if the StringBuilder concurrently grows erroneously. + int capacity = sb.Capacity; + int length = sb.Length; + if (length > capacity) + ThrowHelper.ThrowInvalidOperationException(); + + // Note that StringBuilder.Capacity is the number of characters NOT including any terminators. + int allocSize = checked((capacity * 2) + 4); + IntPtr pNative = Marshal.AllocCoTaskMem(allocSize); + + byte* ptr = (byte*)pNative; *(ptr + allocSize - 1) = 0; *(ptr + allocSize - 2) = 0; if (IsIn(dwFlags)) { - pManagedHome.InternalCopy(pNativeHome, pManagedHomeLength); + sb.InternalCopy(pNative, length); - // null-terminate the native string - int length = pManagedHomeLength * 2; - *(ptr + length + 0) = 0; - *(ptr + length + 1) = 0; + int byteLen = length * 2; + *(ptr + byteLen + 0) = 0; + *(ptr + byteLen + 1) = 0; } - if (IsOut(dwFlags)) - { - backPropAction = BackPropAction.StringBuilderUnicode; - } - } - return pNativeHome; - } - - private unsafe IntPtr ConvertLayoutToNative(object pManagedHome, int dwFlags) - { - // Note that the following call will not throw exception if the type - // of pManagedHome is not marshalable. That's intentional because we - // want to maintain the original behavior where this was indicated - // by TypeLoadException during the actual field marshaling. - int allocSize = Marshal.SizeOfHelper((RuntimeType)pManagedHome.GetType(), false); - IntPtr pNativeHome = Marshal.AllocCoTaskMem(allocSize); + return pNative; + } - // marshal the object as class with layout (UnmanagedType.LPStruct) - if (IsIn(dwFlags)) + public override unsafe void ConvertToManaged(object managed, IntPtr native) { - StubHelpers.LayoutTypeConvertToUnmanaged(pManagedHome, (byte*)pNativeHome, ref cleanupWorkList); + if (!_isOut) return; + int length = native == IntPtr.Zero ? 0 : string.wcslen((char*)native); + ((StringBuilder)managed).ReplaceBufferInternal((char*)native, length); } - if (IsOut(dwFlags)) + + public override void ClearNative(IntPtr native) { - backPropAction = BackPropAction.Layout; + if (native != IntPtr.Zero) + Marshal.FreeCoTaskMem(native); } - layoutType = pManagedHome.GetType(); + } - return pNativeHome; + [Flags] + internal enum AsAnyFlags + { + In = 0x10000000, + Out = 0x20000000, + IsAnsi = 0x00FF0000, + IsThrowOn = 0x0000FF00, + IsBestFit = 0x000000FF } - #endregion + private static bool IsIn(int dwFlags) => (dwFlags & (int)AsAnyFlags.In) != 0; + private static bool IsOut(int dwFlags) => (dwFlags & (int)AsAnyFlags.Out) != 0; + private static bool IsAnsi(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsAnsi) != 0; + private static bool IsThrowOn(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsThrowOn) != 0; + private static bool IsBestFit(int dwFlags) => (dwFlags & (int)AsAnyFlags.IsBestFit) != 0; - internal IntPtr ConvertToNative(object pManagedHome, int dwFlags) + private static AsAnyMarshalerImplementation CreateAnsiCharArrayImplementation(bool isOut, int dwFlags) { - if (pManagedHome == null) - return IntPtr.Zero; + return (IsBestFit(dwFlags), IsThrowOn(dwFlags)) switch + { + (true, true) => new ArrayImplementation>(isOut), + (true, false) => new ArrayImplementation>(isOut), + (false, true) => new ArrayImplementation>(isOut), + (false, false) => new ArrayImplementation>(isOut), + }; + } + + internal AsAnyMarshaler(object? pManagedHome, int dwFlags) + { + _impl = null; + + if (pManagedHome is null) + return; if (pManagedHome is ArrayWithOffset) throw new ArgumentException(SR.Arg_MarshalAsAnyRestriction); - IntPtr pNativeHome; - if (pManagedHome.GetType().IsArray) { - // array (LPArray) - pNativeHome = ConvertArrayToNative(pManagedHome, dwFlags); + Type elementType = pManagedHome.GetType().GetElementType()!; + bool isOut = IsOut(dwFlags); + _impl = Type.GetTypeCode(elementType) switch + { + TypeCode.SByte => new ArrayImplementation>(isOut), + TypeCode.Byte => new ArrayImplementation>(isOut), + TypeCode.Int16 => new ArrayImplementation>(isOut), + TypeCode.UInt16 => new ArrayImplementation>(isOut), + TypeCode.Int32 => new ArrayImplementation>(isOut), + TypeCode.UInt32 => new ArrayImplementation>(isOut), + TypeCode.Int64 => new ArrayImplementation>(isOut), + TypeCode.UInt64 => new ArrayImplementation>(isOut), + TypeCode.Single => new ArrayImplementation>(isOut), + TypeCode.Double => new ArrayImplementation>(isOut), + TypeCode.Object when elementType == typeof(nint) => new ArrayImplementation>(isOut), + TypeCode.Object when elementType == typeof(nuint) => new ArrayImplementation>(isOut), + TypeCode.Char when !IsAnsi(dwFlags) => new ArrayImplementation(isOut), + TypeCode.Char when IsAnsi(dwFlags) => CreateAnsiCharArrayImplementation(isOut, dwFlags), + TypeCode.Boolean => new ArrayImplementation>(isOut), + _ => throw new ArgumentException(SR.Arg_PInvokeBadObject) + }; + } + else if (pManagedHome is string) + { + _impl = new StringImplementation(); + } + else if (pManagedHome is StringBuilder) + { + bool isOut = IsOut(dwFlags); + _impl = IsAnsi(dwFlags) + ? new StringBuilderAnsiImplementation(isOut) + : new StringBuilderUnicodeImplementation(isOut); + } + else if (pManagedHome.GetType().IsLayoutSequential || pManagedHome.GetType().IsExplicitLayout) + { + _impl = new LayoutImplementation(pManagedHome.GetType(), IsOut(dwFlags)); } else { - if (pManagedHome is string strValue) - { - // string (LPStr or LPWStr) - pNativeHome = ConvertStringToNative(strValue, dwFlags); - } - else if (pManagedHome is StringBuilder sbValue) - { - // StringBuilder (LPStr or LPWStr) - pNativeHome = ConvertStringBuilderToNative(sbValue, dwFlags); - } - else if (pManagedHome.GetType().IsLayoutSequential || pManagedHome.GetType().IsExplicitLayout) - { - // layout (LPStruct) - pNativeHome = ConvertLayoutToNative(pManagedHome, dwFlags); - } - else - { - // this type is not supported for AsAny marshaling - throw new ArgumentException(SR.Arg_PInvokeBadObject); - } + throw new ArgumentException(SR.Arg_PInvokeBadObject); } - - return pNativeHome; } - internal unsafe void ConvertToManaged(object pManagedHome, IntPtr pNativeHome) + internal IntPtr ConvertToNative(object pManagedHome, int dwFlags) { - switch (backPropAction) - { - case BackPropAction.Array: - { - Array managedArray = (Array)pManagedHome; - arrayMarshalerMethods.convertContentsToManaged(managedArray, (byte*)pNativeHome, managedArray.Length); - break; - } - - case BackPropAction.Layout: - { - StubHelpers.LayoutTypeConvertToManaged(pManagedHome, (byte*)pNativeHome); - break; - } - - case BackPropAction.StringBuilderAnsi: - { - int length; - if (pNativeHome == IntPtr.Zero) - { - length = 0; - } - else - { - length = string.strlen((byte*)pNativeHome); - } - - ((StringBuilder)pManagedHome).ReplaceBufferAnsiInternal((sbyte*)pNativeHome, length); - break; - } - - case BackPropAction.StringBuilderUnicode: - { - int length; - if (pNativeHome == IntPtr.Zero) - { - length = 0; - } - else - { - length = string.wcslen((char*)pNativeHome); - } - - ((StringBuilder)pManagedHome).ReplaceBufferInternal((char*)pNativeHome, length); - break; - } + return _impl?.ConvertToNative(pManagedHome, dwFlags) ?? IntPtr.Zero; + } - // nothing to do for BackPropAction.None - } + internal void ConvertToManaged(object pManagedHome, IntPtr pNativeHome) + { + _impl?.ConvertToManaged(pManagedHome, pNativeHome); } internal void ClearNative(IntPtr pNativeHome) { - if (pNativeHome != IntPtr.Zero) + if (_impl is not null) + { + _impl.ClearNative(pNativeHome); + } + else if (pNativeHome != IntPtr.Zero) { - if (layoutType != null) - { - // this must happen regardless of BackPropAction - Marshal.DestroyStructure(pNativeHome, layoutType); - } Marshal.FreeCoTaskMem(pNativeHome); } - StubHelpers.DestroyCleanupList(ref cleanupWorkList); } } // struct AsAnyMarshaler diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index e006d0abf3cd28..68219aec426a72 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1207,6 +1207,7 @@ DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, CLEAR_MANAGED_UCO, ClearM DEFINE_METHOD(MNGD_REF_CUSTOM_MARSHALER, GET_CUSTOM_MARSHALER_INSTANCE, GetCustomMarshalerInstance, SM_PtrVoid_PtrByte_Int_PtrObj_PtrException_RetVoid) DEFINE_CLASS(ASANY_MARSHALER, StubHelpers, AsAnyMarshaler) +DEFINE_METHOD(ASANY_MARSHALER, CTOR, .ctor, IM_Obj_Int_RetVoid) DEFINE_METHOD(ASANY_MARSHALER, CONVERT_TO_NATIVE, ConvertToNative, IM_Obj_Int_RetIntPtr) DEFINE_METHOD(ASANY_MARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, IM_Obj_IntPtr_RetVoid) DEFINE_METHOD(ASANY_MARSHALER, CLEAR_NATIVE, ClearNative, IM_IntPtr_RetVoid) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 9a7d494b3a5edb..0a34db4e1bcbb2 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -3761,6 +3761,12 @@ void ILAsAnyMarshalerBase::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) void ILAsAnyMarshalerBase::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) { + // marshaler = new AsAnyMarshaler(managedValue, flags); + EmitLoadMngdMarshalerAddr(pslILEmit); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitLDC(GetAsAnyFlags()); + pslILEmit->EmitCALL(METHOD__ASANY_MARSHALER__CTOR, 3, 0); + // nativeValue = marshaler.ConvertToNative(managedValue, flags); EmitLoadMngdMarshalerAddr(pslILEmit); EmitLoadManagedValue(pslILEmit); diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 5130deb48819a0..7a7b687044365c 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -336,6 +336,7 @@ DEFINE_METASIG(SM(PtrSByt_RetStr, P(B), s)) DEFINE_METASIG(SM(PtrSByt_Int_Int_RetStr, P(B) i i, s)) DEFINE_METASIG_T(SM(PtrSByt_Int_Int_Encoding_RetStr, P(B) i i C(ENCODING), s)) DEFINE_METASIG(IM(Obj_Int_RetIntPtr, j i, I)) +DEFINE_METASIG(IM(Obj_Int_RetVoid, j i, v)) DEFINE_METASIG(IM(Char_Int_RetVoid, u i, v)) From 75efdbf8ff1f307dd53598ec2686060734ffaf4a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 13 Apr 2026 23:32:38 +0000 Subject: [PATCH 25/41] Add BlittableArrayMarshaler for bulk array copy Introduce BlittableArrayMarshaler that implements IArrayMarshaler directly and copies entire array contents in a single Memmove call, replacing the per-element StructureMarshaler loop for blittable primitive types (sbyte, byte, short, ushort, int, uint, long, ulong, float, double, nint, nuint, decimal). Updated both the VM marshaler selection (GetMarshalerAndElementTypes) and AsAnyMarshaler to use the new marshaler for these types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 64 +++++++++++++++---- src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/ilmarshalers.cpp | 6 +- 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 93599ffb8cd66d..329ce261474d43 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1170,18 +1170,18 @@ internal AsAnyMarshaler(object? pManagedHome, int dwFlags) bool isOut = IsOut(dwFlags); _impl = Type.GetTypeCode(elementType) switch { - TypeCode.SByte => new ArrayImplementation>(isOut), - TypeCode.Byte => new ArrayImplementation>(isOut), - TypeCode.Int16 => new ArrayImplementation>(isOut), - TypeCode.UInt16 => new ArrayImplementation>(isOut), - TypeCode.Int32 => new ArrayImplementation>(isOut), - TypeCode.UInt32 => new ArrayImplementation>(isOut), - TypeCode.Int64 => new ArrayImplementation>(isOut), - TypeCode.UInt64 => new ArrayImplementation>(isOut), - TypeCode.Single => new ArrayImplementation>(isOut), - TypeCode.Double => new ArrayImplementation>(isOut), - TypeCode.Object when elementType == typeof(nint) => new ArrayImplementation>(isOut), - TypeCode.Object when elementType == typeof(nuint) => new ArrayImplementation>(isOut), + TypeCode.SByte => new ArrayImplementation>(isOut), + TypeCode.Byte => new ArrayImplementation>(isOut), + TypeCode.Int16 => new ArrayImplementation>(isOut), + TypeCode.UInt16 => new ArrayImplementation>(isOut), + TypeCode.Int32 => new ArrayImplementation>(isOut), + TypeCode.UInt32 => new ArrayImplementation>(isOut), + TypeCode.Int64 => new ArrayImplementation>(isOut), + TypeCode.UInt64 => new ArrayImplementation>(isOut), + TypeCode.Single => new ArrayImplementation>(isOut), + TypeCode.Double => new ArrayImplementation>(isOut), + TypeCode.Object when elementType == typeof(nint) => new ArrayImplementation>(isOut), + TypeCode.Object when elementType == typeof(nuint) => new ArrayImplementation>(isOut), TypeCode.Char when !IsAnsi(dwFlags) => new ArrayImplementation(isOut), TypeCode.Char when IsAnsi(dwFlags) => CreateAnsiCharArrayImplementation(isOut, dwFlags), TypeCode.Boolean => new ArrayImplementation>(isOut), @@ -1320,6 +1320,46 @@ internal static class MarshalOperation internal const int Free = 2; } + internal sealed class BlittableArrayMarshaler : IArrayMarshaler> + where T : unmanaged + { + static unsafe void IArrayMarshaler>.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) + { + Debug.Assert(managedArray is not null); + SpanHelpers.Memmove(ref *unmanaged, ref MemoryMarshal.GetArrayDataReference(managedArray), (nuint)length * (nuint)sizeof(T)); + } + + static unsafe void IArrayMarshaler>.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) + { + Debug.Assert(managedArray is not null); + SpanHelpers.Memmove(ref MemoryMarshal.GetArrayDataReference(managedArray), ref *unmanaged, (nuint)length * (nuint)sizeof(T)); + } + + static unsafe void IArrayMarshaler>.FreeContents(byte* unmanaged, int length) + { + } + + static unsafe byte* IArrayMarshaler>.AllocateSpaceForUnmanaged(Array? managedArray) + { + if (managedArray is null) + return null; + + int nativeBytes = checked(managedArray.Length * sizeof(T)); + byte* pNative = (byte*)Marshal.AllocCoTaskMem(nativeBytes); + NativeMemory.Clear(pNative, (nuint)nativeBytes); + + return pNative; + } + + static unsafe Array? IArrayMarshaler>.AllocateSpaceForManaged(byte* unmanaged, int length) + { + if (unmanaged is null) + return null; + + return new T[length]; + } + } + internal sealed unsafe class StructureMarshaler : IArrayElementMarshaler> where T : notnull { static unsafe void IArrayElementMarshaler>.ConvertToManaged(ref T managed, byte* unmanaged) diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 68219aec426a72..8bc9b4377dbb77 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1218,6 +1218,7 @@ DEFINE_METHOD(HANDLE_MARSHALER, THROW_SAFEHANDLE_FIELD_CHANGED, ThrowSa DEFINE_METHOD(HANDLE_MARSHALER, THROW_CRITICALHANDLE_FIELD_CHANGED, ThrowCriticalHandleFieldChanged, SM_RetVoid) DEFINE_CLASS(STRUCTURE_MARSHALER, StubHelpers, StructureMarshaler`1) +DEFINE_CLASS(BLITTABLE_ARRAY_MARSHALER, StubHelpers, BlittableArrayMarshaler`1) DEFINE_METHOD(STRUCTURE_MARSHALER, CONVERT_TO_MANAGED, ConvertToManaged, NoSig) DEFINE_METHOD(STRUCTURE_MARSHALER, CONVERT_TO_UNMANAGED, ConvertToUnmanaged, NoSig) DEFINE_METHOD(STRUCTURE_MARSHALER, CONVERT_TO_UNMANAGED_CORE, ConvertToUnmanagedCore, NoSig) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 0a34db4e1bcbb2..6eba1e8d072630 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4209,14 +4209,14 @@ namespace case VT_DECIMAL: { *pElementType = thElement; - *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); return; } case VT_UI2: { // System.Char arrays use a dedicated marshaler; other VT_UI2 types (UInt16) - // use the standard StructureMarshaler. + // use BlittableArrayMarshaler for bulk copy. if (thElement == TypeHandle(CoreLibBinder::GetClass(CLASS__CHAR))) { *pElementType = thElement; @@ -4225,7 +4225,7 @@ namespace else { *pElementType = thElement; - *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); } return; } From 645f9d76838a627e783158183d7f951bcc195e4e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 02:32:46 +0000 Subject: [PATCH 26/41] Use BlittableArrayMarshaler for blittable structs --- src/coreclr/vm/ilmarshalers.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 6eba1e8d072630..42f0ad9c0b695e 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4304,7 +4304,15 @@ namespace case VT_RECORD: { *pElementType = thElement; - *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + + if (thElement.IsBlittable()) + { + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + else + { + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } return; } From 2787a7147913100ddfc5169bdcbbfcd2ced26fb1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 02:36:20 +0000 Subject: [PATCH 27/41] Update SafeArray marshaler selection to use BlittableArrayMarshaler Update GetMarshalerMTForSafeArrayVarType in olevariant.cpp to use BlittableArrayMarshaler for blittable primitive types and blittable structs (VT_RECORD), matching the ilmarshalers.cpp changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/olevariant.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 7cfe400a08ebab..a73527eb6e8e19 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1823,60 +1823,60 @@ namespace case VT_I1: { TypeHandle th = CoreLibBinder::GetClass(CLASS__SBYTE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_UI1: { TypeHandle th = CoreLibBinder::GetClass(CLASS__BYTE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_I2: { TypeHandle th = CoreLibBinder::GetClass(CLASS__INT16); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_UI2: { TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT16); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_I4: case VT_INT: { TypeHandle th = CoreLibBinder::GetClass(CLASS__INT32); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_UI4: case VT_UINT: case VT_ERROR: { TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT32); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_I8: { TypeHandle th = CoreLibBinder::GetClass(CLASS__INT64); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_UI8: { TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT64); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_R4: { TypeHandle th = CoreLibBinder::GetClass(CLASS__SINGLE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_R8: { TypeHandle th = CoreLibBinder::GetClass(CLASS__DOUBLE); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_DECIMAL: { TypeHandle th = CoreLibBinder::GetClass(CLASS__DECIMAL); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_BOOL: return CoreLibBinder::GetClass(CLASS__VARIANT_BOOL_MARSHALER); @@ -1929,7 +1929,14 @@ namespace { _ASSERTE(pElementMT != NULL); TypeHandle thElement(pElementMT); - return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + if (thElement.IsBlittable()) + { + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + else + { + return TypeHandle(CoreLibBinder::GetClass(CLASS__STRUCTURE_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } } default: From 38cccf42b96ee8b21a71fd6d63bb9ab027f83098 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 03:48:13 +0000 Subject: [PATCH 28/41] Fix native element size for fixed arrays and null array handling - Fix ParseNativeType to use elementNativeType for selecting the correct native MethodTable for fixed arrays. With the removal of VTHACK values, ANSI char and WINBOOL element types were falling through to the default case and using the managed element MT, producing incorrect native sizes (2 bytes for ANSI char instead of 1, 1 byte for WINBOOL instead of 4). - Add null guards in ILNativeArrayMarshaler::EmitConvertContentsCLRToNative and EmitConvertContentsNativeToCLR. The old QCall-based implementation checked for null internally; the new IL emits ldlen directly which would NullReferenceException on null array inputs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/fieldmarshaler.cpp | 17 ++++++++++++++++- src/coreclr/vm/ilmarshalers.cpp | 12 ++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/fieldmarshaler.cpp b/src/coreclr/vm/fieldmarshaler.cpp index f35a270971781d..4e8e7be87bc503 100644 --- a/src/coreclr/vm/fieldmarshaler.cpp +++ b/src/coreclr/vm/fieldmarshaler.cpp @@ -390,7 +390,22 @@ VOID ParseNativeType(Module* pModule, pMT = CoreLibBinder::GetElementType(pMT->GetInternalCorElementType()); } - *pNFD = NativeFieldDescriptor(pFD, GetNativeMethodTableForVarType(mops.elementType, pMT), mops.additive); + MethodTable *pNativeMT; + switch (mops.elementNativeType) + { + case NATIVE_TYPE_BOOLEAN: + pNativeMT = CoreLibBinder::GetClass(CLASS__INT32); + break; + case NATIVE_TYPE_I1: + case NATIVE_TYPE_U1: + pNativeMT = CoreLibBinder::GetClass(CLASS__BYTE); + break; + default: + pNativeMT = GetNativeMethodTableForVarType(mops.elementType, pMT); + break; + } + + *pNFD = NativeFieldDescriptor(pFD, pNativeMT, mops.additive); break; } case MarshalInfo::MARSHAL_TYPE_FIXED_CSTR: diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 42f0ad9c0b695e..69eef2f7fd38e3 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4447,6 +4447,10 @@ void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILE { STANDARD_VM_CONTRACT; + ILCodeLabel* pSkipLabel = pslILEmit->NewCodeLabel(); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitBRFALSE(pSkipLabel); + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); EmitLoadManagedValue(pslILEmit); @@ -4455,12 +4459,18 @@ void ILNativeArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILE pslILEmit->EmitLDLEN(); pslILEmit->EmitCONV_I4(); pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); + + pslILEmit->EmitLabel(pSkipLabel); } void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) { STANDARD_VM_CONTRACT; + ILCodeLabel* pSkipLabel = pslILEmit->NewCodeLabel(); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitBRFALSE(pSkipLabel); + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_MANAGED); EmitLoadManagedValue(pslILEmit); @@ -4469,6 +4479,8 @@ void ILNativeArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILE pslILEmit->EmitLDLEN(); pslILEmit->EmitCONV_I4(); pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); + + pslILEmit->EmitLabel(pSkipLabel); } void ILNativeArrayMarshaler::EmitClearNativeContents(ILCodeStream* pslILEmit) From 6a0fdd8f3ece145f0e1c4616257de262694b4af3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 04:12:29 +0000 Subject: [PATCH 29/41] Fix AnsiCharArrayMarshaler buffer overflow, fixed array null guard, and AsAnyMarshaler initialization - Fix buffer overflow in AnsiCharArrayMarshaler.ConvertContentsToUnmanaged: StringToAnsiString was told the buffer is (length+1)*SystemMaxDBCSCharSize bytes, but for fixed arrays the inline native buffer is only 'length' bytes. This caused writes past the buffer (null terminator + multi-byte chars). Use a temporary buffer for the conversion and copy at most 'length' bytes. - Add null guard in ILFixedArrayMarshaler::EmitConvertContentsCLRToNative: The old QCall-based implementation checked for null managed arrays and zeroed the native buffer. The new code would crash on null since ConvertContentsToUnmanaged asserts non-null. Skip conversion on null; the native struct buffer is already zero-initialized by the caller. - Fix ReadValueSlow/WriteValueSlow in Marshal.CoreCLR.cs to initialize AsAnyMarshaler via constructor instead of default. The constructor-based dispatch requires the constructor to create the implementation object. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../InteropServices/Marshal.CoreCLR.cs | 4 +-- .../src/System/StubHelpers.cs | 25 +++++++++++++++++-- src/coreclr/vm/ilmarshalers.cpp | 6 +++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs index c34e9cd45bfc19..ffa20878cba85e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.cs @@ -110,7 +110,7 @@ private static unsafe T ReadValueSlow(object ptr, int ofs, Func(object ptr, int ofs, T val, Action< (int)AsAnyMarshaler.AsAnyFlags.IsAnsi | (int)AsAnyMarshaler.AsAnyFlags.IsBestFit; - AsAnyMarshaler marshaler = default; + AsAnyMarshaler marshaler = new(ptr, Flags); IntPtr pNativeHome = IntPtr.Zero; diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 329ce261474d43..701255cd3d476e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1788,8 +1788,29 @@ public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* u ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), length); string str = new(chars); - int bufferLength = checked((length + 1) * Marshal.SystemMaxDBCSCharSize); - Marshal.StringToAnsiString(str, unmanaged, bufferLength, TBestFit.Enabled, TThrowOnUnmappable.Enabled); + // Use a temporary buffer for the full conversion (including null terminator that + // StringToAnsiString appends) to avoid writing past the native buffer for fixed + // arrays where the native buffer is exactly 'length' bytes. + int tempBufferLength = checked((length + 1) * Marshal.SystemMaxDBCSCharSize); + byte* heapBuffer = null; + Span tempSpan = tempBufferLength <= 256 + ? stackalloc byte[tempBufferLength] + : new Span(heapBuffer = (byte*)NativeMemory.Alloc((nuint)tempBufferLength), tempBufferLength); + try + { + fixed (byte* tempBuffer = tempSpan) + { + int convertedBytes = Marshal.StringToAnsiString(str, tempBuffer, tempBufferLength, TBestFit.Enabled, TThrowOnUnmappable.Enabled); + // Copy at most 'length' bytes (one byte per element for ANSI char arrays). + int bytesToCopy = Math.Min(convertedBytes, length); + SpanHelpers.Memmove(ref *unmanaged, ref *tempBuffer, (nuint)bytesToCopy); + } + } + finally + { + if (heapBuffer != null) + NativeMemory.Free(heapBuffer); + } } public static unsafe void ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 69eef2f7fd38e3..f471cac5afeaaa 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4561,12 +4561,18 @@ void ILFixedArrayMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEm CREATE_MARSHALER_CARRAY_OPERANDS mops; m_pargs->m_pMarshalInfo->GetMops(&mops); + ILCodeLabel* pSkipLabel = pslILEmit->NewCodeLabel(); + EmitLoadManagedValue(pslILEmit); + pslILEmit->EmitBRFALSE(pSkipLabel); + MethodDesc* pMD = GetInstantiatedArrayMethod(m_pargs->m_pMarshalInfo, METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED); EmitLoadManagedValue(pslILEmit); EmitLoadNativeHomeAddr(pslILEmit); pslILEmit->EmitLDC(mops.additive); pslILEmit->EmitCALL(pslILEmit->GetToken(pMD), 3, 0); + + pslILEmit->EmitLabel(pSkipLabel); } void ILFixedArrayMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) From 2fc2142b2ea9a28dec2b184a86457a5bbd539b74 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 18:12:16 +0000 Subject: [PATCH 30/41] Reduce ref-arithmetic and add more asserts --- .../System.Private.CoreLib/src/System/StubHelpers.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 701255cd3d476e..2de93f5911ff6b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1249,10 +1249,10 @@ static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array mana { Debug.Assert(managedArray is not null); Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); - ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)); + Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { - TSelf.ConvertToManaged(ref Unsafe.Add(ref firstElement, i), unmanaged); + TSelf.ConvertToManaged(ref elements[i], unmanaged); unmanaged += TSelf.UnmanagedSize; } } @@ -1261,10 +1261,10 @@ static unsafe void IArrayMarshaler.ConvertContentsToUnmanaged(Array ma { Debug.Assert(managedArray is not null); Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); - ref T firstElement = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)); + Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { - TSelf.ConvertToUnmanaged(ref Unsafe.Add(ref firstElement, i), unmanaged); + TSelf.ConvertToUnmanaged(ref elements[i], unmanaged); unmanaged += TSelf.UnmanagedSize; } } @@ -1326,12 +1326,14 @@ internal sealed class BlittableArrayMarshaler : IArrayMarshaler>.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { Debug.Assert(managedArray is not null); + Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); SpanHelpers.Memmove(ref *unmanaged, ref MemoryMarshal.GetArrayDataReference(managedArray), (nuint)length * (nuint)sizeof(T)); } static unsafe void IArrayMarshaler>.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { Debug.Assert(managedArray is not null); + Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); SpanHelpers.Memmove(ref MemoryMarshal.GetArrayDataReference(managedArray), ref *unmanaged, (nuint)length * (nuint)sizeof(T)); } From dc7f6df9c47bf7afbe7a3a22f0e0f9dd1137e323 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 11:42:10 -0700 Subject: [PATCH 31/41] Restore VT_BYREF handling for SAFEARRAY of VARIANT marshalling When a SAFEARRAY contains valid VT_BYREF VARIANTs (the fSafeArrayIsValid case, used for unmanaged->managed in/out parameters), the marshaller must write through the byref pointer rather than replacing the VARIANT. The previous implementation always cleared the VARIANT before writing, which destroyed the VT_BYREF flag before ObjectMarshaler.ConvertToNative could check it and route to MarshalOleRefVariantForObject. Add a TNativeDataValid generic parameter to VariantArrayElementMarshaler (following the IMarshalerOption pattern used by LPSTRArrayElementMarshaler). When enabled, ConvertToUnmanaged preserves the existing VARIANT so ConvertToNative can detect VT_BYREF and update the referenced value. When disabled (the common case for freshly allocated buffers), it zeros the destination to prevent garbage VT_BYREF bits from misrouting. The SafeArray IL marshaler now passes the correct flag at stub generation time based on SCSF_NativeDataValid. The P/Invoke and variant marshalling paths default to FALSE (native data not valid). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 15 ++++++++++++--- src/coreclr/vm/corelib.h | 2 +- src/coreclr/vm/ilmarshalers.cpp | 7 +++++-- src/coreclr/vm/olevariant.cpp | 14 ++++++++++---- src/coreclr/vm/olevariant.h | 2 +- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 701255cd3d476e..08ac3660924693 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -2075,11 +2075,20 @@ public static unsafe void Free(byte* unmanaged) static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(IntPtr); } - internal sealed class VariantArrayElementMarshaler : IArrayElementMarshaler + internal sealed class VariantArrayElementMarshaler : IArrayElementMarshaler> + where TNativeDataValid : IMarshalerOption { public static unsafe void ConvertToUnmanaged(ref object? managed, byte* unmanaged) { - ObjectMarshaler.ClearNative((IntPtr)unmanaged); + if (!TNativeDataValid.Enabled) + { + // Native buffer is uninitialized — zero it so ConvertToNative + // doesn't see garbage VT_BYREF bits. + *(ComVariant*)unmanaged = default; + } + // When TNativeDataValid is enabled, the existing VARIANT may have + // VT_BYREF set. ConvertToNative checks vt & VT_BYREF and calls + // MarshalOleRefVariantForObject to write through the byref pointer. ObjectMarshaler.ConvertToNative(managed!, (IntPtr)unmanaged); } @@ -2093,7 +2102,7 @@ public static unsafe void Free(byte* unmanaged) ObjectMarshaler.ClearNative((IntPtr)unmanaged); } - static unsafe nuint IArrayElementMarshaler.UnmanagedSize => (nuint)sizeof(ComVariant); + static unsafe nuint IArrayElementMarshaler>.UnmanagedSize => (nuint)sizeof(ComVariant); } #endif // FEATURE_COMINTEROP diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 8bc9b4377dbb77..8558b6fe87a357 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1251,7 +1251,7 @@ DEFINE_CLASS(CURRENCY_ARRAY_ELEMENT_MARSHALER, StubHelpers, CurrencyArrayElemen DEFINE_CLASS(INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, InterfaceArrayElementMarshaler`1) DEFINE_CLASS(TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, TypedInterfaceArrayElementMarshaler`1) DEFINE_CLASS(HETEROGENEOUS_INTERFACE_ARRAY_ELEMENT_MARSHALER, StubHelpers, HeterogeneousInterfaceArrayElementMarshaler) -DEFINE_CLASS(VARIANT_ARRAY_ELEMENT_MARSHALER, StubHelpers, VariantArrayElementMarshaler) +DEFINE_CLASS(VARIANT_ARRAY_ELEMENT_MARSHALER, StubHelpers, VariantArrayElementMarshaler`1) #endif // FEATURE_COMINTEROP DEFINE_CLASS(MARSHALER_OPTION_ENABLED, StubHelpers, IMarshalerOption+EnabledOption) DEFINE_CLASS(MARSHALER_OPTION_DISABLED, StubHelpers, IMarshalerOption+DisabledOption) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index f471cac5afeaaa..3ff7f7fff18522 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4296,7 +4296,9 @@ namespace case VT_VARIANT: { *pElementType = TypeHandle(g_pObjectClass); - *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); + MethodTable* pDisabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); + TypeHandle thDisabled(pDisabledMT); + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDisabled, 1)).AsMethodTable(); return; } #endif // FEATURE_COMINTEROP @@ -4657,9 +4659,10 @@ void ILSafeArrayMarshaler::EmitCreateMngdMarshaler(ILCodeStream* pslILEmit) // Resolve the instantiated content conversion methods at stub generation time // and emit ldftn to pass their entry points to CreateMarshaler. + BOOL bNativeDataValid = !!(fStatic & MngdSafeArrayMarshaler::SCSF_NativeDataValid); MethodDesc* pConvertToNativeMD = GetInstantiatedSafeArrayMethod( METHOD__STUBHELPERS__CONVERT_ARRAY_CONTENTS_TO_UNMANAGED, - mops.elementType, mops.methodTable, FALSE); + mops.elementType, mops.methodTable, FALSE, bNativeDataValid); pslILEmit->EmitLDFTN(pslILEmit->GetToken(pConvertToNativeMD)); MethodDesc* pConvertToManagedMD = GetInstantiatedSafeArrayMethod( diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index a73527eb6e8e19..47d008d4e4c3e9 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1814,7 +1814,7 @@ namespace { // Returns the managed IArrayMarshaler MethodTable for a given VARTYPE. // This mirrors the logic in ILArrayMarshalerBase::GetMarshalerMT for SAFEARRAY-compatible types. - MethodTable* GetMarshalerMTForSafeArrayVarType(VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous) + MethodTable* GetMarshalerMTForSafeArrayVarType(VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous, BOOL bNativeDataValid) { STANDARD_VM_CONTRACT; @@ -1923,7 +1923,13 @@ namespace } case VT_VARIANT: - return CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER); + { + MethodTable* pOptionMT = bNativeDataValid + ? CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_ENABLED) + : CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); + TypeHandle thOption(pOptionMT); + return TypeHandle(CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thOption, 1)).AsMethodTable(); + } case VT_RECORD: { @@ -1989,14 +1995,14 @@ namespace } } -MethodDesc* GetInstantiatedSafeArrayMethod(BinderMethodID methodId, VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous) +MethodDesc* GetInstantiatedSafeArrayMethod(BinderMethodID methodId, VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous, BOOL bNativeDataValid) { STANDARD_VM_CONTRACT; MethodDesc* pGenericMD = CoreLibBinder::GetMethod(methodId); TypeHandle thElementType = GetElementTypeForSafeArrayVarType(vt, pElementMT); - TypeHandle thMarshalerType(GetMarshalerMTForSafeArrayVarType(vt, pElementMT, bHeterogeneous)); + TypeHandle thMarshalerType(GetMarshalerMTForSafeArrayVarType(vt, pElementMT, bHeterogeneous, bNativeDataValid)); TypeHandle thArgs[2] = { thElementType, thMarshalerType }; diff --git a/src/coreclr/vm/olevariant.h b/src/coreclr/vm/olevariant.h index dfc9bd536a9f6e..88b97361e6694d 100644 --- a/src/coreclr/vm/olevariant.h +++ b/src/coreclr/vm/olevariant.h @@ -106,7 +106,7 @@ class OleVariant // Returns the instantiated MethodDesc for a StubHelpers array marshalling method // (e.g. ConvertArrayContentsToUnmanaged/ConvertArrayContentsToManaged) for a given // SAFEARRAY VARTYPE and element MethodTable. -MethodDesc* GetInstantiatedSafeArrayMethod(BinderMethodID methodId, VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous); +MethodDesc* GetInstantiatedSafeArrayMethod(BinderMethodID methodId, VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous, BOOL bNativeDataValid = FALSE); extern "C" void QCALLTYPE Variant_ConvertValueTypeToRecord(QCall::ObjectHandleOnStack obj, VARIANT* pOle); From cf6b0b083ff9686b71b79875fd4310d181c594ae Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 12:17:59 -0700 Subject: [PATCH 32/41] Fix ANSI char conversion, remove ILArrayMarshalerBase, fix variable shadowing - AnsiCharArrayMarshaler.ConvertContentsToManaged: Replace string(sbyte*) constructor with direct MultiByteToWideChar (Windows) / UTF8.GetChars (Unix) to correctly handle DBCS code pages where a single ANSI character may be encoded as multiple bytes. - Remove ILArrayMarshalerBase: Inline the trivial overrides (GetNativeType, GetManagedType, NeedsClearNative) into both ILNativeArrayMarshaler and ILFixedArrayMarshaler. The base class had no remaining non-trivial shared implementation. - Fix variable shadowing in GetMarshalerAndElementTypes: The VT_VARIANT case declared a local pDisabledMT that shadowed the outer variable. Reuse the outer variable instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 18 +++-- src/coreclr/vm/ilmarshalers.cpp | 5 +- src/coreclr/vm/ilmarshalers.h | 70 +++++++++++-------- src/coreclr/vm/olevariant.cpp | 2 +- 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 82ac562f07edc2..e1772346e7fe6f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1822,10 +1822,20 @@ public static unsafe void ConvertContentsToManaged(Array managedArray, byte* unm Span chars = new Span( ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), length); - // Use the string(sbyte*) constructor which uses the system default ANSI code page, - // then copy the resulting characters into the array. - string converted = new((sbyte*)unmanaged, 0, length); - converted.CopyTo(chars); + fixed (char* pChars = chars) + { +#if TARGET_WINDOWS + Interop.Kernel32.MultiByteToWideChar( + Interop.Kernel32.CP_ACP, + Interop.Kernel32.MB_PRECOMPOSED, + unmanaged, + length, + pChars, + length); +#else + Encoding.UTF8.GetChars(unmanaged, length, pChars, length); +#endif + } } public static unsafe void FreeContents(byte* unmanaged, int length) diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 3ff7f7fff18522..faa6576b1aac70 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4184,7 +4184,7 @@ namespace } default: - _ASSERTE(!"Unsupported CorNativeType for ILArrayMarshalerBase"); + _ASSERTE(!"Unsupported CorNativeType for GetMarshalerAndElementTypes"); COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); return; } @@ -4296,7 +4296,6 @@ namespace case VT_VARIANT: { *pElementType = TypeHandle(g_pObjectClass); - MethodTable* pDisabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); TypeHandle thDisabled(pDisabledMT); *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__VARIANT_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDisabled, 1)).AsMethodTable(); return; @@ -4319,7 +4318,7 @@ namespace } default: - _ASSERTE(!"Unsupported VT for ILArrayMarshalerBase"); + _ASSERTE(!"Unsupported VT for GetMarshalerAndElementTypes"); COMPlusThrow(kArgumentException, IDS_EE_COM_UNSUPPORTED_SIG); return; } diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index eeb34e5f79ae11..e8e0fe0e2896a7 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3268,33 +3268,7 @@ class ILMngdMarshaler : public ILMarshaler const BinderMethodID m_idClearManaged; }; -class ILArrayMarshalerBase : public ILMarshaler -{ -protected: - LocalDesc GetNativeType() override - { - LIMITED_METHOD_CONTRACT; - return LocalDesc(ELEMENT_TYPE_I); - } - - LocalDesc GetManagedType() override - { - LIMITED_METHOD_CONTRACT; - return LocalDesc(ELEMENT_TYPE_OBJECT); - } - - bool NeedsClearNative() override - { - LIMITED_METHOD_CONTRACT; - return true; - } - - // Load the native element count onto the evaluation stack. - // Subclasses implement this differently (dynamic size param vs. fixed count). - virtual void EmitLoadNativeSize(ILCodeStream* pslILEmit) = 0; -}; - -class ILNativeArrayMarshaler : public ILArrayMarshalerBase +class ILNativeArrayMarshaler : public ILMarshaler { public: enum @@ -3328,18 +3302,36 @@ class ILNativeArrayMarshaler : public ILArrayMarshalerBase protected: + LocalDesc GetNativeType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_I); + } + + LocalDesc GetManagedType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_OBJECT); + } + + bool NeedsClearNative() override + { + LIMITED_METHOD_CONTRACT; + return true; + } + BOOL CheckSizeParamIndexArg(const CREATE_MARSHALER_CARRAY_OPERANDS &mops, CorElementType *pElementType); // Calculate element count and load it on evaluation stack void EmitLoadElementCount(ILCodeStream* pslILEmit); - void EmitLoadNativeSize(ILCodeStream* pslILEmit) override; + void EmitLoadNativeSize(ILCodeStream* pslILEmit); void EmitNewSavedSizeArgLocal(ILCodeStream* pslILEmit); DWORD m_dwSavedSizeArg; }; -class ILFixedArrayMarshaler : public ILArrayMarshalerBase +class ILFixedArrayMarshaler : public ILMarshaler { public: enum @@ -3362,11 +3354,29 @@ class ILFixedArrayMarshaler : public ILArrayMarshalerBase protected: + LocalDesc GetNativeType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_I); + } + + LocalDesc GetManagedType() override + { + LIMITED_METHOD_CONTRACT; + return LocalDesc(ELEMENT_TYPE_OBJECT); + } + + bool NeedsClearNative() override + { + LIMITED_METHOD_CONTRACT; + return true; + } + void EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit) override; void EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit) override; void EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit) override; void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override; - void EmitLoadNativeSize(ILCodeStream* pslILEmit) override; + void EmitLoadNativeSize(ILCodeStream* pslILEmit); void EmitClearNativeContents(ILCodeStream* pslILEmit) override; void EmitClearNative(ILCodeStream* pslILEmit) override; }; diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 47d008d4e4c3e9..b75be1a6a969cc 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1813,7 +1813,7 @@ BASEARRAYREF OleVariant::CreateArrayRefForSafeArray(SAFEARRAY *pSafeArray, VARTY namespace { // Returns the managed IArrayMarshaler MethodTable for a given VARTYPE. - // This mirrors the logic in ILArrayMarshalerBase::GetMarshalerMT for SAFEARRAY-compatible types. + // This mirrors the logic in GetMarshalerAndElementTypes for SAFEARRAY-compatible types. MethodTable* GetMarshalerMTForSafeArrayVarType(VARTYPE vt, MethodTable* pElementMT, BOOL bHeterogeneous, BOOL bNativeDataValid) { STANDARD_VM_CONTRACT; From 0e07d6c00291f8e8f7308c4b9f4f0b3b10bf213a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 12:32:31 -0700 Subject: [PATCH 33/41] Use WideCharToMultiByte/MultiByteToWideChar directly in AnsiCharArrayMarshaler Replace string-based ANSI conversion with direct Win32 API calls: - ConvertContentsToUnmanaged: Use WideCharToMultiByte (Windows) or Encoding.UTF8.GetBytes (Unix) to convert chars directly to ANSI bytes without constructing an intermediate string or temp buffer. - ConvertContentsToManaged: Already using MultiByteToWideChar/UTF8.GetChars from the previous commit. - AllocateSpaceForUnmanaged: Remove the +1 null terminator space since WideCharToMultiByte doesn't append a null terminator when given an explicit output length. The WideCharToMultiByte call correctly handles best-fit mapping (WC_NO_BEST_FIT_CHARS flag) and throw-on-unmappable (lpUsedDefaultChar parameter) matching the old native implementation behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 70 ++++++++----------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index e1772346e7fe6f..2678efed8bccfa 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1786,56 +1786,45 @@ public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* u { Debug.Assert(managedArray is not null); Debug.Assert(managedArray.GetType().GetElementType() == typeof(char)); - ReadOnlySpan chars = new( - ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), - length); - string str = new(chars); - // Use a temporary buffer for the full conversion (including null terminator that - // StringToAnsiString appends) to avoid writing past the native buffer for fixed - // arrays where the native buffer is exactly 'length' bytes. - int tempBufferLength = checked((length + 1) * Marshal.SystemMaxDBCSCharSize); - byte* heapBuffer = null; - Span tempSpan = tempBufferLength <= 256 - ? stackalloc byte[tempBufferLength] - : new Span(heapBuffer = (byte*)NativeMemory.Alloc((nuint)tempBufferLength), tempBufferLength); - try - { - fixed (byte* tempBuffer = tempSpan) - { - int convertedBytes = Marshal.StringToAnsiString(str, tempBuffer, tempBufferLength, TBestFit.Enabled, TThrowOnUnmappable.Enabled); - // Copy at most 'length' bytes (one byte per element for ANSI char arrays). - int bytesToCopy = Math.Min(convertedBytes, length); - SpanHelpers.Memmove(ref *unmanaged, ref *tempBuffer, (nuint)bytesToCopy); - } - } - finally + char* pChars = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(managedArray)); +#if TARGET_WINDOWS + uint flags = TBestFit.Enabled ? 0 : Interop.Kernel32.WC_NO_BEST_FIT_CHARS; + Interop.BOOL defaultCharUsed = Interop.BOOL.FALSE; + Interop.Kernel32.WideCharToMultiByte( + Interop.Kernel32.CP_ACP, + flags, + pChars, + length, + unmanaged, + length, + null, + TThrowOnUnmappable.Enabled ? &defaultCharUsed : null); + + if (defaultCharUsed != Interop.BOOL.FALSE) { - if (heapBuffer != null) - NativeMemory.Free(heapBuffer); + throw new ArgumentException(SR.Interop_Marshal_Unmappable_Char); } +#else + Encoding.UTF8.GetBytes(pChars, length, unmanaged, length); +#endif } public static unsafe void ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { Debug.Assert(managedArray is not null); Debug.Assert(managedArray.GetType().GetElementType() == typeof(char)); - Span chars = new Span( - ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), - length); - fixed (char* pChars = chars) - { + char* pChars = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(managedArray)); #if TARGET_WINDOWS - Interop.Kernel32.MultiByteToWideChar( - Interop.Kernel32.CP_ACP, - Interop.Kernel32.MB_PRECOMPOSED, - unmanaged, - length, - pChars, - length); + Interop.Kernel32.MultiByteToWideChar( + Interop.Kernel32.CP_ACP, + Interop.Kernel32.MB_PRECOMPOSED, + unmanaged, + length, + pChars, + length); #else - Encoding.UTF8.GetChars(unmanaged, length, pChars, length); + Encoding.UTF8.GetChars(unmanaged, length, pChars, length); #endif - } } public static unsafe void FreeContents(byte* unmanaged, int length) @@ -1850,8 +1839,7 @@ public static unsafe void FreeContents(byte* unmanaged, int length) } // Each char can map to at most SystemMaxDBCSCharSize bytes during conversion. - // StringToAnsiString also writes a null terminator, so allocate space for it. - int allocSize = checked((managedArray.Length + 1) * Marshal.SystemMaxDBCSCharSize); + int allocSize = checked(managedArray.Length * Marshal.SystemMaxDBCSCharSize); byte* pNative = (byte*)Marshal.AllocCoTaskMem(allocSize); NativeMemory.Clear(pNative, (nuint)allocSize); return pNative; From ac2aeb0ba48260c80105053dc6ad286179ca8167 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 14:05:58 -0700 Subject: [PATCH 34/41] Check ANSI conversion return values and remove dead fSafeArrayIsValid parameter - AnsiCharArrayMarshaler: Check WideCharToMultiByte and MultiByteToWideChar return values and throw on failure, matching the old native code behavior (COMPlusThrowWin32 on MultiByteToWideChar failure, InternalWideToAnsi error checking on WideCharToMultiByte failure). - Remove the dead fSafeArrayIsValid parameter from MarshalSafeArrayForArrayRef. The VT_BYREF VARIANT handling is now done at stub generation time via the TNativeDataValid generic parameter on VariantArrayElementMarshaler, making the runtime parameter unnecessary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 14 ++++++++++++-- src/coreclr/vm/ilmarshalers.cpp | 3 +-- src/coreclr/vm/olevariant.cpp | 3 +-- src/coreclr/vm/olevariant.h | 3 +-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 2678efed8bccfa..246d7289f9e4c7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1790,7 +1790,7 @@ public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* u #if TARGET_WINDOWS uint flags = TBestFit.Enabled ? 0 : Interop.Kernel32.WC_NO_BEST_FIT_CHARS; Interop.BOOL defaultCharUsed = Interop.BOOL.FALSE; - Interop.Kernel32.WideCharToMultiByte( + int result = Interop.Kernel32.WideCharToMultiByte( Interop.Kernel32.CP_ACP, flags, pChars, @@ -1800,6 +1800,11 @@ public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* u null, TThrowOnUnmappable.Enabled ? &defaultCharUsed : null); + if (result == 0 && length > 0) + { + throw new ArgumentException(SR.Interop_Marshal_Unmappable_Char); + } + if (defaultCharUsed != Interop.BOOL.FALSE) { throw new ArgumentException(SR.Interop_Marshal_Unmappable_Char); @@ -1815,13 +1820,18 @@ public static unsafe void ConvertContentsToManaged(Array managedArray, byte* unm Debug.Assert(managedArray.GetType().GetElementType() == typeof(char)); char* pChars = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(managedArray)); #if TARGET_WINDOWS - Interop.Kernel32.MultiByteToWideChar( + int result = Interop.Kernel32.MultiByteToWideChar( Interop.Kernel32.CP_ACP, Interop.Kernel32.MB_PRECOMPOSED, unmanaged, length, pChars, length); + + if (result == 0 && length > 0) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } #else Encoding.UTF8.GetChars(unmanaged, length, pChars, length); #endif diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index faa6576b1aac70..87c7361b4886fa 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4790,8 +4790,7 @@ extern "C" void QCALLTYPE MngdSafeArrayMarshaler_ConvertContentsToNative(MngdSaf (SAFEARRAY*)*pNativeHome, pThis->m_vt, pThis->m_pElementMT, - pThis->m_pConvertContentsToNativeCode, - (pThis->m_fStatic & MngdSafeArrayMarshaler::SCSF_NativeDataValid)); + pThis->m_pConvertContentsToNativeCode); } GCPROTECT_END(); diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index b75be1a6a969cc..d4a50c4ea16309 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -2022,8 +2022,7 @@ void OleVariant::MarshalSafeArrayForArrayRef(BASEARRAYREF *pArrayRef, SAFEARRAY *pSafeArray, VARTYPE vt, MethodTable *pInterfaceMT, - PCODE pConvertContentsCode, - BOOL fSafeArrayIsValid /*= TRUE*/) + PCODE pConvertContentsCode) { CONTRACTL { diff --git a/src/coreclr/vm/olevariant.h b/src/coreclr/vm/olevariant.h index 88b97361e6694d..a3dd84ab287a88 100644 --- a/src/coreclr/vm/olevariant.h +++ b/src/coreclr/vm/olevariant.h @@ -41,8 +41,7 @@ class OleVariant SAFEARRAY* pSafeArray, VARTYPE vt, MethodTable* pInterfaceMT, - PCODE pConvertContentsCode, - BOOL fSafeArrayIsValid = TRUE); + PCODE pConvertContentsCode); static void MarshalArrayRefForSafeArray(SAFEARRAY* pSafeArray, BASEARRAYREF* pArrayRef, From 04ed2769faffc01fdd8cadaddc7d1b889e383169 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 14:26:12 -0700 Subject: [PATCH 35/41] Remove redundant SkipOnMono attributes from SafeArray tests The ConditionalFact with IsBuiltInComEnabled already skips these tests on Mono, making the SkipOnMono attributes unnecessary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs index 5f1a8a3513d6e8..293e89883a982c 100644 --- a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs +++ b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayTest.cs @@ -89,7 +89,6 @@ public static int TestEntryPoint() } [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] - [SkipOnMono("Requires COM support")] public static void MultidimensionalIntArray() { const int rows = 3; @@ -110,7 +109,6 @@ public static void MultidimensionalIntArray() } [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] - [SkipOnMono("Requires COM support")] public static void MultidimensionalIntArrayRoundTrip() { const int rows = 3; @@ -122,7 +120,6 @@ public static void MultidimensionalIntArrayRoundTrip() } [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] - [SkipOnMono("Requires COM support")] public static void MultidimensionalBoolArray() { const int rows = 2; @@ -143,7 +140,6 @@ public static void MultidimensionalBoolArray() } [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] - [SkipOnMono("Requires COM support")] public static void MultidimensionalBoolArrayRoundTrip() { const int rows = 2; @@ -155,7 +151,6 @@ public static void MultidimensionalBoolArrayRoundTrip() } [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] - [SkipOnMono("Requires COM support")] public static void MultidimensionalStringArray() { const int rows = 2; @@ -176,7 +171,6 @@ public static void MultidimensionalStringArray() } [ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsBuiltInComEnabled))] - [SkipOnMono("Requires COM support")] public static void MultidimensionalStringArrayRoundTrip() { const int rows = 2; From 64c3e5db5f17a0f5a0496bf6b67e10bf7261840e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 14:59:14 -0700 Subject: [PATCH 36/41] Restore MAX_SIZE_FOR_INTEROP limit and fix ANSI allocation size Address PR review feedback: - Restore the MAX_SIZE_FOR_INTEROP (0x7ffffff0) size cap in IArrayElementMarshaler.AllocateSpaceForUnmanaged and BlittableArrayMarshaler.AllocateSpaceForUnmanaged. The old native QCalls enforced this limit to prevent extremely large CoTaskMem allocations; the new managed code was missing it. - Fix AnsiCharArrayMarshaler.AllocateSpaceForUnmanaged to allocate exactly 'length' bytes (1 byte per element for ANSI char native layout) instead of 'length * SystemMaxDBCSCharSize'. The WideCharToMultiByte call caps its output at 'length' bytes, so the extra allocation was wasted and the comment was misleading. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 246d7289f9e4c7..20870ee421b7a9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1286,9 +1286,12 @@ static unsafe void IArrayMarshaler.FreeContents(byte* unmanaged, int l } else { - int nativeBytes = checked(managedArray.Length * (int)TSelf.UnmanagedSize); - byte* pNative = (byte*)Marshal.AllocCoTaskMem(nativeBytes); - NativeMemory.Clear(pNative, (nuint)nativeBytes); + const nuint MaxSizeForInterop = 0x7ffffff0u; + nuint nativeBytes = (nuint)(uint)managedArray.Length * TSelf.UnmanagedSize; + if (nativeBytes > MaxSizeForInterop) + throw new ArgumentException(SR.Argument_StructArrayTooLarge); + byte* pNative = (byte*)Marshal.AllocCoTaskMem((int)nativeBytes); + NativeMemory.Clear(pNative, nativeBytes); return pNative; } } @@ -1346,9 +1349,12 @@ static unsafe void IArrayMarshaler>.FreeContents(b if (managedArray is null) return null; - int nativeBytes = checked(managedArray.Length * sizeof(T)); - byte* pNative = (byte*)Marshal.AllocCoTaskMem(nativeBytes); - NativeMemory.Clear(pNative, (nuint)nativeBytes); + const nuint MaxSizeForInterop = 0x7ffffff0u; + nuint nativeBytes = (nuint)(uint)managedArray.Length * (nuint)sizeof(T); + if (nativeBytes > MaxSizeForInterop) + throw new ArgumentException(SR.Argument_StructArrayTooLarge); + byte* pNative = (byte*)Marshal.AllocCoTaskMem((int)nativeBytes); + NativeMemory.Clear(pNative, nativeBytes); return pNative; } @@ -1848,8 +1854,8 @@ public static unsafe void FreeContents(byte* unmanaged, int length) return null; } - // Each char can map to at most SystemMaxDBCSCharSize bytes during conversion. - int allocSize = checked(managedArray.Length * Marshal.SystemMaxDBCSCharSize); + // Native layout for ANSI char arrays uses 1 byte per element. + int allocSize = managedArray.Length; byte* pNative = (byte*)Marshal.AllocCoTaskMem(allocSize); NativeMemory.Clear(pNative, (nuint)allocSize); return pNative; From 6cd1490551769cc4111c265f04678faf9b97d91d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 14 Apr 2026 22:19:30 -0700 Subject: [PATCH 37/41] Throw InvalidCastException instead of Debug.Assert for array element type mismatch When array content conversion methods receive an array whose element type doesn't match the generic type parameter T, throw InvalidCastException instead of using Debug.Assert (which causes FailFast in checked builds). This fixes the System.Runtime.InteropServices.Tests CI failure where GetNativeVariantForObject with typed COM class arrays (e.g. DualComObject[]) hit the debug assert in BlittableArrayMarshaler.ConvertContentsToUnmanaged. The old native code threw InvalidCastException in this scenario; the new managed code now does the same. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 20870ee421b7a9..e0c8345db33c83 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1247,8 +1247,8 @@ internal interface IArrayElementMarshaler : IArrayMarshaler { static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); - Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); + if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) + throw new InvalidCastException(); Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { @@ -1259,8 +1259,8 @@ static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array mana static unsafe void IArrayMarshaler.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); - Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); + if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) + throw new InvalidCastException(); Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { @@ -1328,15 +1328,15 @@ internal sealed class BlittableArrayMarshaler : IArrayMarshaler>.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); - Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); + if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) + throw new InvalidCastException(); SpanHelpers.Memmove(ref *unmanaged, ref MemoryMarshal.GetArrayDataReference(managedArray), (nuint)length * (nuint)sizeof(T)); } static unsafe void IArrayMarshaler>.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); - Debug.Assert(managedArray.GetType().GetElementType() == typeof(T)); + if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) + throw new InvalidCastException(); SpanHelpers.Memmove(ref MemoryMarshal.GetArrayDataReference(managedArray), ref *unmanaged, (nuint)length * (nuint)sizeof(T)); } From ef753496acb363d30a26cf6c762474de6c91631f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 15 Apr 2026 12:24:24 -0700 Subject: [PATCH 38/41] Fix COM class array marshalling in SafeArray and P/Invoke paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For VT_UNKNOWN/VT_DISPATCH with class element types, resolve the default COM interface via GetDefaultInterfaceMTForClass and use that interface with TypedInterfaceArrayElementMarshaler. This matches the old native MarshalInterfaceArrayComToOleHelper behavior which called GetDefaultInterfaceMTForClass per-element. When GetDefaultInterfaceMTForClass returns NULL (IUnknown/AutoDispatch class interface types), fall back to InterfaceArrayElementMarshaler with the appropriate IDispatch/IUnknown flag. Update both GetMarshalerMTForSafeArrayVarType (olevariant.cpp) and GetMarshalerAndElementTypes (ilmarshalers.cpp) consistently, along with GetElementTypeForSafeArrayVarType to return the resolved interface type for the generic method instantiation. Revert element type mismatch checks to Debug.Assert — the strict InvalidCastException throws were too restrictive for valid interop scenarios (enum arrays, covariant reference arrays). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 12 +++---- src/coreclr/vm/ilmarshalers.cpp | 18 +++++++++++ src/coreclr/vm/olevariant.cpp | 31 ++++++++++++++++++- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index e0c8345db33c83..fee5d2c80f2e0d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1247,8 +1247,7 @@ internal interface IArrayElementMarshaler : IArrayMarshaler { static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) - throw new InvalidCastException(); + Debug.Assert(managedArray is not null); Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { @@ -1259,8 +1258,7 @@ static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array mana static unsafe void IArrayMarshaler.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) - throw new InvalidCastException(); + Debug.Assert(managedArray is not null); Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { @@ -1328,15 +1326,13 @@ internal sealed class BlittableArrayMarshaler : IArrayMarshaler>.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) - throw new InvalidCastException(); + Debug.Assert(managedArray is not null); SpanHelpers.Memmove(ref *unmanaged, ref MemoryMarshal.GetArrayDataReference(managedArray), (nuint)length * (nuint)sizeof(T)); } static unsafe void IArrayMarshaler>.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - if (managedArray is null || managedArray.GetType().GetElementType() != typeof(T)) - throw new InvalidCastException(); + Debug.Assert(managedArray is not null); SpanHelpers.Memmove(ref MemoryMarshal.GetArrayDataReference(managedArray), ref *unmanaged, (nuint)length * (nuint)sizeof(T)); } diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 87c7361b4886fa..146fbb5d603f32 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4285,6 +4285,24 @@ namespace *pElementType = TypeHandle(g_pObjectClass); *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); } + else if (!arrayElementTypeHandle.IsInterface()) + { + // For class types, resolve the default COM interface. + BOOL bDispatch = FALSE; + MethodTable* pDefaultItfMT = GetDefaultInterfaceMTForClass(arrayElementTypeHandle.AsMethodTable(), &bDispatch); + if (pDefaultItfMT != NULL) + { + TypeHandle thItf(pDefaultItfMT); + *pElementType = thItf; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thItf, 1)).AsMethodTable(); + } + else + { + TypeHandle thDispatch(bDispatch ? pEnabledMT : pDisabledMT); + *pElementType = TypeHandle(g_pObjectClass); + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); + } + } else { *pElementType = arrayElementTypeHandle; diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index d4a50c4ea16309..58b57bdc92c3dc 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1915,6 +1915,26 @@ namespace TypeHandle thDispatch(vt == VT_DISPATCH ? pEnabledMT : pDisabledMT); return TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); } + else if (!pElementMT->IsInterface()) + { + // For class types, resolve the default COM interface. + BOOL bDispatch = FALSE; + MethodTable* pDefaultItfMT = GetDefaultInterfaceMTForClass(pElementMT, &bDispatch); + if (pDefaultItfMT != NULL) + { + // Use the resolved interface type. + TypeHandle thElement(pDefaultItfMT); + return TypeHandle(CoreLibBinder::GetClass(CLASS__TYPED_INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); + } + else + { + // No specific interface — use untyped IDispatch or IUnknown. + MethodTable* pEnabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_ENABLED); + MethodTable* pDisabledMT = CoreLibBinder::GetClass(CLASS__MARSHALER_OPTION_DISABLED); + TypeHandle thDispatch(bDispatch ? pEnabledMT : pDisabledMT); + return TypeHandle(CoreLibBinder::GetClass(CLASS__INTERFACE_ARRAY_ELEMENT_MARSHALER)).Instantiate(Instantiation(&thDispatch, 1)).AsMethodTable(); + } + } else { TypeHandle thElement(pElementMT); @@ -1984,7 +2004,16 @@ namespace case VT_DISPATCH: if (pElementMT == NULL || pElementMT == g_pObjectClass) return TypeHandle(g_pObjectClass); - return TypeHandle(pElementMT); + if (pElementMT->IsInterface()) + return TypeHandle(pElementMT); + { + // For class types, resolve to the default interface type. + BOOL bDispatch = FALSE; + MethodTable* pDefaultItfMT = GetDefaultInterfaceMTForClass(pElementMT, &bDispatch); + if (pDefaultItfMT != NULL) + return TypeHandle(pDefaultItfMT); + return TypeHandle(g_pObjectClass); + } case VT_RECORD: _ASSERTE(pElementMT != NULL); return TypeHandle(pElementMT); From 56e784a06ab97f321e9a59f434afad0344738d19 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 15 Apr 2026 16:04:29 -0700 Subject: [PATCH 39/41] Fix SafeArray element type resolution for char, IntPtr, and UIntPtr arrays GetElementTypeForSafeArrayVarType and GetMarshalerMTForSafeArrayVarType must return element types that match the actual managed array element type, not just the default type for the VARTYPE. When the caller-provided pElementMT is char (for VT_UI2), IntPtr (for VT_I4/VT_I8), or UIntPtr (for VT_UI4/VT_UI8), return that type instead of the default integer type. This ensures the generic method instantiation's T parameter matches the actual array element type, which is validated by Debug.Assert in ConvertArrayContentsToUnmanaged/Managed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 22 +++++---- src/coreclr/vm/olevariant.cpp | 46 +++++++++++++++---- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index fee5d2c80f2e0d..fb06bf73c6818e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1247,7 +1247,6 @@ internal interface IArrayElementMarshaler : IArrayMarshaler { static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { @@ -1258,7 +1257,6 @@ static unsafe void IArrayMarshaler.ConvertContentsToManaged(Array mana static unsafe void IArrayMarshaler.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); Span elements = new(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(managedArray)), managedArray.Length); for (int i = 0; i < length; i++) { @@ -1326,13 +1324,11 @@ internal sealed class BlittableArrayMarshaler : IArrayMarshaler>.ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); SpanHelpers.Memmove(ref *unmanaged, ref MemoryMarshal.GetArrayDataReference(managedArray), (nuint)length * (nuint)sizeof(T)); } static unsafe void IArrayMarshaler>.ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); SpanHelpers.Memmove(ref MemoryMarshal.GetArrayDataReference(managedArray), ref *unmanaged, (nuint)length * (nuint)sizeof(T)); } @@ -1786,8 +1782,6 @@ internal sealed class AnsiCharArrayMarshaler : IAr { public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); - Debug.Assert(managedArray.GetType().GetElementType() == typeof(char)); char* pChars = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(managedArray)); #if TARGET_WINDOWS uint flags = TBestFit.Enabled ? 0 : Interop.Kernel32.WC_NO_BEST_FIT_CHARS; @@ -1818,8 +1812,6 @@ public static unsafe void ConvertContentsToUnmanaged(Array managedArray, byte* u public static unsafe void ConvertContentsToManaged(Array managedArray, byte* unmanaged, int length) { - Debug.Assert(managedArray is not null); - Debug.Assert(managedArray.GetType().GetElementType() == typeof(char)); char* pChars = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(managedArray)); #if TARGET_WINDOWS int result = Interop.Kernel32.MultiByteToWideChar( @@ -2475,14 +2467,26 @@ internal static unsafe void LayoutTypeConvertToManaged(object* obj, byte* pNativ } public static unsafe void ConvertArrayContentsToUnmanaged(Array managed, byte* pNative, int numElements) - where TMarshaler : IArrayMarshaler + where TMarshaler : IArrayMarshaler { + // Assert that the array is actually an array of compatible type. + // This assert should only fire if a caller manually used Unsafe.As to cast an object of incompatible type + // before passing to a P/Invoke or COM stub. + // Any other instances where it fires should be a bug in the interop stack. + Debug.Assert(managed is not null); + Debug.Assert(managed.GetType().GetElementType()!.MakeArrayType().IsAssignableTo(typeof(T[])), $"Managed array type {managed.GetType()} is not compatible with expected element type {typeof(T)}"); TMarshaler.ConvertContentsToUnmanaged(managed, pNative, numElements); } public static unsafe void ConvertArrayContentsToManaged(Array managed, byte* pNative, int numElements) where TMarshaler : IArrayMarshaler { + // Assert that the array is actually an array of compatible type. + // This assert should only fire if a caller manually used Unsafe.As to cast an object of incompatible type + // before passing to a P/Invoke or COM stub. + // Any other instances where it fires should be a bug in the interop stack. + Debug.Assert(managed is not null); + Debug.Assert(managed.GetType().GetElementType()!.MakeArrayType().IsAssignableTo(typeof(T[])), $"Managed array type {managed.GetType()} is not compatible with expected element type {typeof(T)}"); TMarshaler.ConvertContentsToManaged(managed, pNative, numElements); } diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 58b57bdc92c3dc..48e02afa0b217c 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1837,30 +1837,43 @@ namespace } case VT_UI2: { + if (pElementMT == CoreLibBinder::GetClass(CLASS__CHAR)) + { + TypeHandle th = CoreLibBinder::GetClass(CLASS__CHAR); + return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); + } TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT16); return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_I4: case VT_INT: { - TypeHandle th = CoreLibBinder::GetClass(CLASS__INT32); + TypeHandle th = (pElementMT == CoreLibBinder::GetClass(CLASS__INTPTR)) + ? CoreLibBinder::GetClass(CLASS__INTPTR) + : CoreLibBinder::GetClass(CLASS__INT32); return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_UI4: case VT_UINT: case VT_ERROR: { - TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT32); + TypeHandle th = (pElementMT == CoreLibBinder::GetClass(CLASS__UINTPTR)) + ? CoreLibBinder::GetClass(CLASS__UINTPTR) + : CoreLibBinder::GetClass(CLASS__UINT32); return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_I8: { - TypeHandle th = CoreLibBinder::GetClass(CLASS__INT64); + TypeHandle th = (pElementMT == CoreLibBinder::GetClass(CLASS__INTPTR)) + ? CoreLibBinder::GetClass(CLASS__INTPTR) + : CoreLibBinder::GetClass(CLASS__INT64); return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_UI8: { - TypeHandle th = CoreLibBinder::GetClass(CLASS__UINT64); + TypeHandle th = (pElementMT == CoreLibBinder::GetClass(CLASS__UINTPTR)) + ? CoreLibBinder::GetClass(CLASS__UINTPTR) + : CoreLibBinder::GetClass(CLASS__UINT64); return TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&th, 1)).AsMethodTable(); } case VT_R4: @@ -1983,14 +1996,29 @@ namespace case VT_I1: return TypeHandle(CoreLibBinder::GetClass(CLASS__SBYTE)); case VT_UI1: return TypeHandle(CoreLibBinder::GetClass(CLASS__BYTE)); case VT_I2: return TypeHandle(CoreLibBinder::GetClass(CLASS__INT16)); - case VT_UI2: return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT16)); + case VT_UI2: + if (pElementMT == CoreLibBinder::GetClass(CLASS__CHAR)) + return TypeHandle(CoreLibBinder::GetClass(CLASS__CHAR)); + return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT16)); case VT_I4: - case VT_INT: return TypeHandle(CoreLibBinder::GetClass(CLASS__INT32)); + case VT_INT: + if (pElementMT == CoreLibBinder::GetClass(CLASS__INTPTR)) + return TypeHandle(CoreLibBinder::GetClass(CLASS__INTPTR)); + return TypeHandle(CoreLibBinder::GetClass(CLASS__INT32)); case VT_UI4: case VT_UINT: - case VT_ERROR: return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT32)); - case VT_I8: return TypeHandle(CoreLibBinder::GetClass(CLASS__INT64)); - case VT_UI8: return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT64)); + case VT_ERROR: + if (pElementMT == CoreLibBinder::GetClass(CLASS__UINTPTR)) + return TypeHandle(CoreLibBinder::GetClass(CLASS__UINTPTR)); + return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT32)); + case VT_I8: + if (pElementMT == CoreLibBinder::GetClass(CLASS__INTPTR)) + return TypeHandle(CoreLibBinder::GetClass(CLASS__INTPTR)); + return TypeHandle(CoreLibBinder::GetClass(CLASS__INT64)); + case VT_UI8: + if (pElementMT == CoreLibBinder::GetClass(CLASS__UINTPTR)) + return TypeHandle(CoreLibBinder::GetClass(CLASS__UINTPTR)); + return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT64)); case VT_R4: return TypeHandle(CoreLibBinder::GetClass(CLASS__SINGLE)); case VT_R8: return TypeHandle(CoreLibBinder::GetClass(CLASS__DOUBLE)); case VT_DECIMAL: return TypeHandle(CoreLibBinder::GetClass(CLASS__DECIMAL)); From 17cc9b36f0443d276a7f54e667800c8e8160316f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 15 Apr 2026 16:20:24 -0700 Subject: [PATCH 40/41] Remove UnicodeCharArrayElementMarshaler, use BlittableArrayMarshaler char arrays are blittable (same layout as UInt16 arrays), so the per-element UnicodeCharArrayElementMarshaler is unnecessary. Replace all usages with BlittableArrayMarshaler for bulk memcpy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 21 +------------------ src/coreclr/vm/corelib.h | 1 - src/coreclr/vm/ilmarshalers.cpp | 14 ++----------- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index fb06bf73c6818e..e194c7ecad8748 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1182,7 +1182,7 @@ internal AsAnyMarshaler(object? pManagedHome, int dwFlags) TypeCode.Double => new ArrayImplementation>(isOut), TypeCode.Object when elementType == typeof(nint) => new ArrayImplementation>(isOut), TypeCode.Object when elementType == typeof(nuint) => new ArrayImplementation>(isOut), - TypeCode.Char when !IsAnsi(dwFlags) => new ArrayImplementation(isOut), + TypeCode.Char when !IsAnsi(dwFlags) => new ArrayImplementation>(isOut), TypeCode.Char when IsAnsi(dwFlags) => CreateAnsiCharArrayImplementation(isOut, dwFlags), TypeCode.Boolean => new ArrayImplementation>(isOut), _ => throw new ArgumentException(SR.Arg_PInvokeBadObject) @@ -1860,25 +1860,6 @@ public static unsafe void FreeContents(byte* unmanaged, int length) } } - internal sealed class UnicodeCharArrayElementMarshaler : IArrayElementMarshaler - { - public static unsafe void ConvertToUnmanaged(ref char managed, byte* unmanaged) - { - *(char*)unmanaged = managed; - } - - public static unsafe void ConvertToManaged(ref char managed, byte* unmanaged) - { - managed = *(char*)unmanaged; - } - - public static unsafe void Free(byte* unmanaged) - { - } - - static nuint IArrayElementMarshaler.UnmanagedSize => sizeof(char); - } - internal sealed class LPSTRArrayElementMarshaler : IArrayElementMarshaler> where TBestFit : IMarshalerOption where TThrowOnUnmappable : IMarshalerOption diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 8558b6fe87a357..5a4c3f229606ab 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -1243,7 +1243,6 @@ DEFINE_CLASS(VARIANT_BOOL_MARSHALER, StubHelpers, VariantBoolMarshale DEFINE_CLASS(BOOL_MARSHALER, StubHelpers, BoolMarshaler`1) DEFINE_CLASS(LPWSTR_MARSHALER, StubHelpers, LPWSTRMarshaler) DEFINE_CLASS(ANSICHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, AnsiCharArrayMarshaler`2) -DEFINE_CLASS(UNICODECHAR_ARRAY_ELEMENT_MARSHALER, StubHelpers, UnicodeCharArrayElementMarshaler) DEFINE_CLASS(LPSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, LPSTRArrayElementMarshaler`2) DEFINE_CLASS(BSTR_ARRAY_ELEMENT_MARSHALER, StubHelpers, BSTRArrayElementMarshaler) #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 146fbb5d603f32..e6ac569784ea1b 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -4215,18 +4215,8 @@ namespace case VT_UI2: { - // System.Char arrays use a dedicated marshaler; other VT_UI2 types (UInt16) - // use BlittableArrayMarshaler for bulk copy. - if (thElement == TypeHandle(CoreLibBinder::GetClass(CLASS__CHAR))) - { - *pElementType = thElement; - *ppMarshalerMT = CoreLibBinder::GetClass(CLASS__UNICODECHAR_ARRAY_ELEMENT_MARSHALER); - } - else - { - *pElementType = thElement; - *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); - } + *pElementType = thElement; + *ppMarshalerMT = TypeHandle(CoreLibBinder::GetClass(CLASS__BLITTABLE_ARRAY_MARSHALER)).Instantiate(Instantiation(&thElement, 1)).AsMethodTable(); return; } From cf46ddfd248df85fbb27fa147f5881d1d7a8dbc1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 15 Apr 2026 16:28:07 -0700 Subject: [PATCH 41/41] Address PR review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move VT_ERROR to map to Int32 (not UInt32) in both GetMarshalerMTForSafeArrayVarType and GetElementTypeForSafeArrayVarType, matching COM interop conventions where SCODE/HRESULT is signed 32-bit. - Fix overflow-safe multiplication in IArrayElementMarshaler and BlittableArrayMarshaler AllocateSpaceForUnmanaged: use division-based overflow check (elementSize > MaxSizeForInterop / elementCount) instead of unchecked multiplication that could wrap on 32-bit. - Add negative input validation to Create2D*SafeArray test helpers to prevent wrapping to huge ULONG values in SAFEARRAYBOUND.cElements. - Fix misleading comment in Create2DIntSafeArray about column-major storage — SafeArrayPutElement uses logical indices so the value pattern is independent of physical storage layout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/StubHelpers.cs | 12 ++++++++---- src/coreclr/vm/olevariant.cpp | 4 ++-- .../ArrayMarshalling/SafeArray/SafeArrayNative.cpp | 13 ++++++++++--- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index e194c7ecad8748..110bd9d12a5736 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1283,9 +1283,11 @@ static unsafe void IArrayMarshaler.FreeContents(byte* unmanaged, int l else { const nuint MaxSizeForInterop = 0x7ffffff0u; - nuint nativeBytes = (nuint)(uint)managedArray.Length * TSelf.UnmanagedSize; - if (nativeBytes > MaxSizeForInterop) + nuint elementCount = (nuint)(uint)managedArray.Length; + nuint elementSize = TSelf.UnmanagedSize; + if (elementCount != 0 && elementSize > MaxSizeForInterop / elementCount) throw new ArgumentException(SR.Argument_StructArrayTooLarge); + nuint nativeBytes = elementCount * elementSize; byte* pNative = (byte*)Marshal.AllocCoTaskMem((int)nativeBytes); NativeMemory.Clear(pNative, nativeBytes); return pNative; @@ -1342,9 +1344,11 @@ static unsafe void IArrayMarshaler>.FreeContents(b return null; const nuint MaxSizeForInterop = 0x7ffffff0u; - nuint nativeBytes = (nuint)(uint)managedArray.Length * (nuint)sizeof(T); - if (nativeBytes > MaxSizeForInterop) + nuint elementCount = (nuint)(uint)managedArray.Length; + nuint elementSize = (nuint)sizeof(T); + if (elementCount != 0 && elementSize > MaxSizeForInterop / elementCount) throw new ArgumentException(SR.Argument_StructArrayTooLarge); + nuint nativeBytes = elementCount * elementSize; byte* pNative = (byte*)Marshal.AllocCoTaskMem((int)nativeBytes); NativeMemory.Clear(pNative, nativeBytes); diff --git a/src/coreclr/vm/olevariant.cpp b/src/coreclr/vm/olevariant.cpp index 48e02afa0b217c..7dbd1e31f4b7b9 100644 --- a/src/coreclr/vm/olevariant.cpp +++ b/src/coreclr/vm/olevariant.cpp @@ -1847,6 +1847,7 @@ namespace } case VT_I4: case VT_INT: + case VT_ERROR: { TypeHandle th = (pElementMT == CoreLibBinder::GetClass(CLASS__INTPTR)) ? CoreLibBinder::GetClass(CLASS__INTPTR) @@ -1855,7 +1856,6 @@ namespace } case VT_UI4: case VT_UINT: - case VT_ERROR: { TypeHandle th = (pElementMT == CoreLibBinder::GetClass(CLASS__UINTPTR)) ? CoreLibBinder::GetClass(CLASS__UINTPTR) @@ -2002,12 +2002,12 @@ namespace return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT16)); case VT_I4: case VT_INT: + case VT_ERROR: if (pElementMT == CoreLibBinder::GetClass(CLASS__INTPTR)) return TypeHandle(CoreLibBinder::GetClass(CLASS__INTPTR)); return TypeHandle(CoreLibBinder::GetClass(CLASS__INT32)); case VT_UI4: case VT_UINT: - case VT_ERROR: if (pElementMT == CoreLibBinder::GetClass(CLASS__UINTPTR)) return TypeHandle(CoreLibBinder::GetClass(CLASS__UINTPTR)); return TypeHandle(CoreLibBinder::GetClass(CLASS__UINT32)); diff --git a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp index 15cc679287deb5..c815ce15755f9c 100644 --- a/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp +++ b/src/tests/Interop/ArrayMarshalling/SafeArray/SafeArrayNative.cpp @@ -311,12 +311,13 @@ extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE XorBoolArrayInStruct(StructWithS } // Creates a 2D SAFEARRAY of VT_I4 with dimensions [rows x cols]. -// Data is filled with value = row * cols + col (column-major storage in SAFEARRAY). +// Data is filled by logical indices with value = row * cols + col. extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Create2DIntSafeArray(int rows, int cols, SAFEARRAY** ppResult) { + if (rows < 0 || cols < 0) + return E_INVALIDARG; + SAFEARRAYBOUND bounds[2]; - // SAFEARRAY dimension order: first bound is leftmost dimension. - // In column-major (Fortran) layout, the first dimension varies fastest. bounds[0].lLbound = 0; bounds[0].cElements = (ULONG)rows; bounds[1].lLbound = 0; @@ -378,6 +379,9 @@ extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Verify2DIntSafeArray(SAFEARRAY* // Value at [r,c] = ((r + c) % 2 == 0) ? VARIANT_TRUE : VARIANT_FALSE. extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Create2DBoolSafeArray(int rows, int cols, SAFEARRAY** ppResult) { + if (rows < 0 || cols < 0) + return E_INVALIDARG; + SAFEARRAYBOUND bounds[2]; bounds[0].lLbound = 0; bounds[0].cElements = (ULONG)rows; @@ -439,6 +443,9 @@ extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Verify2DBoolSafeArray(SAFEARRAY* // Value at [r,c] = "r,c". extern "C" DLL_EXPORT HRESULT STDMETHODCALLTYPE Create2DStringSafeArray(int rows, int cols, SAFEARRAY** ppResult) { + if (rows < 0 || cols < 0) + return E_INVALIDARG; + SAFEARRAYBOUND bounds[2]; bounds[0].lLbound = 0; bounds[0].cElements = (ULONG)rows;