diff --git a/src/coreclr/vm/ilmarshalers.h b/src/coreclr/vm/ilmarshalers.h index f3c9f31628f156..61ff10ac2b2b86 100644 --- a/src/coreclr/vm/ilmarshalers.h +++ b/src/coreclr/vm/ilmarshalers.h @@ -3138,39 +3138,13 @@ class ILMngdMarshaler : public ILMarshaler void EmitClearNative(ILCodeStream* pslILEmit) override { WRAPPER_NO_CONTRACT; - ILCodeLabel* pNoManagedValueLabel = nullptr; - if (IsFieldMarshal(m_dwMarshalFlags)) - { - pNoManagedValueLabel = pslILEmit->NewCodeLabel(); - pslILEmit->EmitLDARG(StructMarshalStubs::MANAGED_STRUCT_ARGIDX); - pslILEmit->EmitBRFALSE(pNoManagedValueLabel); - } - EmitCallMngdMarshalerMethod(pslILEmit, GetClearNativeMethod()); - - if (IsFieldMarshal(m_dwMarshalFlags)) - { - pslILEmit->EmitLabel(pNoManagedValueLabel); - } } void EmitClearNativeContents(ILCodeStream* pslILEmit) override { WRAPPER_NO_CONTRACT; - ILCodeLabel* pNoManagedValueLabel = nullptr; - if (IsFieldMarshal(m_dwMarshalFlags)) - { - pNoManagedValueLabel = pslILEmit->NewCodeLabel(); - pslILEmit->EmitLDARG(StructMarshalStubs::MANAGED_STRUCT_ARGIDX); - pslILEmit->EmitBRFALSE(pNoManagedValueLabel); - } - EmitCallMngdMarshalerMethod(pslILEmit, GetClearNativeContentsMethod()); - - if (IsFieldMarshal(m_dwMarshalFlags)) - { - pslILEmit->EmitLabel(pNoManagedValueLabel); - } } bool NeedsClearCLR() override diff --git a/src/tests/Interop/ArrayMarshalling/StructArray/MarshalStructArrayTest.cs b/src/tests/Interop/ArrayMarshalling/StructArray/MarshalStructArrayTest.cs new file mode 100644 index 00000000000000..421af3979bbace --- /dev/null +++ b/src/tests/Interop/ArrayMarshalling/StructArray/MarshalStructArrayTest.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using TestLibrary; +using Xunit; + +public static unsafe class MarshalStructArrayTest +{ + + [SkipOnMono("Mono doesn't support built-in COM interop, which this test requires")] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBuiltInComEnabled))] + public static void ArrayElementsInStructFreed() + { + // Validate that we free the native resources of fields that use "managed" marshallers in CoreCLR's IL Stub + // marshalling system when they are fields of structs and we don't have the managed value avaliable at the time we are + // releasing the resources. + + // To test this, we are using a structure with a "ByValArray" field that contains a runtime-implemented RCW. + // The marshaller for the ByValArray unmanaged type uses the marshaller kind under test. + // Using an RCW as the element type allows us to hook the release mechanism to validate that the value has been freed. + // Since the runtime will unwrap an RCW when passing it down to native code (instead of re-wrapping in a CCW), this will + // allow us to directly test with the custom release mechanism we have implemented without needing to do GC.Collect calls. + object underlyingObject = new(); + SimpleComWrappers wrappers = new(); + nint unk = wrappers.GetOrCreateComInterfaceForObject(underlyingObject, CreateComInterfaceFlags.CallerDefinedIUnknown); + object builtinWrapper = Marshal.GetUniqueObjectForIUnknown(unk); + Marshal.Release(unk); + + StructWithObjectArrayField str = new() { objs = [builtinWrapper] }; + + IntPtr ptr = (IntPtr)NativeMemory.Alloc((nuint)Marshal.SizeOf()); + Marshal.StructureToPtr(str, ptr, false); + SimpleComWrappers.ReleaseCalled = false; + Marshal.DestroyStructure(ptr); + + Assert.True(SimpleComWrappers.ReleaseCalled); + NativeMemory.Free((void*)ptr); + + // Make sure that the runtime-implemented RCW isn't collected (and Release called) + // when we are trying to test that DestroyStructure calls release. + GC.KeepAlive(builtinWrapper); + } +} + +public struct StructWithObjectArrayField +{ + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public object[] objs; +} + +public sealed unsafe class SimpleComWrappers : ComWrappers +{ + private static readonly ComInterfaceEntry* customIUnknown = CreateCustomIUnknownInterfaceEntry(); + + private static ComInterfaceEntry* CreateCustomIUnknownInterfaceEntry() + { + ComInterfaceEntry* entry = (ComInterfaceEntry*)NativeMemory.AllocZeroed((nuint)sizeof(ComInterfaceEntry)); + entry->IID = Guid.Parse("00000000-0000-0000-C000-000000000046"); + nint* vtable = (nint*)NativeMemory.Alloc((nuint)(sizeof(void*) * 3)); + GetIUnknownImpl(out vtable[0], out vtable[1], out _); + vtable[2] = (nint)(delegate* unmanaged)&Release; + entry->Vtable = (nint)vtable; + return entry; + } + + protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) + { + Assert.True(flags.HasFlag(CreateComInterfaceFlags.CallerDefinedIUnknown)); + count = 1; + return customIUnknown; + } + + protected override object? CreateObject(nint externalComObject, CreateObjectFlags flags) + { + throw new NotImplementedException(); + } + + protected override void ReleaseObjects(IEnumerable objects) + { + throw new NotImplementedException(); + } + + [ThreadStatic] + public static bool ReleaseCalled = false; + + [UnmanagedCallersOnly] + private static uint Release(nint thisPtr) + { + ReleaseCalled = true; + GetIUnknownImpl(out _, out _, out var release); + return ((delegate* unmanaged)release)(thisPtr); + } +} \ No newline at end of file diff --git a/src/tests/Interop/ArrayMarshalling/StructArray/MarshalStructArrayTest.csproj b/src/tests/Interop/ArrayMarshalling/StructArray/MarshalStructArrayTest.csproj new file mode 100644 index 00000000000000..a309408302d26d --- /dev/null +++ b/src/tests/Interop/ArrayMarshalling/StructArray/MarshalStructArrayTest.csproj @@ -0,0 +1,12 @@ + + + Exe + true + + + + + + + +