diff --git a/src/tests/Interop/COM/ComWrappers/API/Program.cs b/src/tests/Interop/COM/ComWrappers/API/Program.cs index da49300f166e61..21017033f1d1c5 100644 --- a/src/tests/Interop/COM/ComWrappers/API/Program.cs +++ b/src/tests/Interop/COM/ComWrappers/API/Program.cs @@ -33,6 +33,8 @@ static TestComWrappers() fpWrappedQueryInterface = MockReferenceTrackerRuntime.WrapQueryInterface(fpQueryInterface); } + public bool UseManualReleaseITestObjectWrapper { get; init; } + protected unsafe override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) { ComInterfaceEntry* entryRaw = null; @@ -133,7 +135,14 @@ protected override object CreateObject(IntPtr externalComObject, CreateObjectFla hr = Marshal.QueryInterface(externalComObject, typeof(ITest).GUID, out iTest); if (hr == 0) { - return new ITestObjectWrapper(iTest); + if (UseManualReleaseITestObjectWrapper) + { + return new ManualReleaseITestObjectWrapper(iTest); + } + else + { + return new ITestObjectWrapper(iTest); + } } Assert.Fail("The COM object should support ITrackerObject or ITest for all tests in this test suite."); @@ -418,9 +427,9 @@ unsafe static void CallSetValue(TestComWrappers wrappers, Test testInstance, int [MethodImpl(MethodImplOptions.NoInlining)] [ActiveIssue("Not supported on Mono", TestRuntimes.Mono)] [Fact] - public void ValidateResurrection() + public void ValidateManagedObjectWrapperResurrection() { - Console.WriteLine($"Running {nameof(ValidateResurrection)}..."); + Console.WriteLine($"Running {nameof(ValidateManagedObjectWrapperResurrection)}..."); var wrappers = new TestComWrappers(); @@ -621,6 +630,72 @@ public void ValidateMappingAPIs() Marshal.Release(unmanagedObjIUnknown); } + class Resurrecter() + { + public ManualReleaseITestObjectWrapper? UnmanagedWrapper; + + ~Resurrecter() + { + if (UnmanagedWrapper != null) + { + GC.ReRegisterForFinalize(this); + } + } + } + + + [MethodImpl(MethodImplOptions.NoInlining)] + [ActiveIssue("Not supported on Mono", TestRuntimes.Mono)] + [Fact] + public void ValidateNativeObjectWrapperResurrection() + { + Console.WriteLine($"Running {nameof(ValidateNativeObjectWrapperResurrection)}..."); + + var cw = new TestComWrappers() + { + UseManualReleaseITestObjectWrapper = true, + }; + + WeakGCHandle resurrecter; + nint unmanagedObj = AllocateWrapper(cw, out resurrecter); + Assert.Equal(0, Marshal.QueryInterface(unmanagedObj, IUnknownVtbl.IID_IUnknown, out IntPtr unmanagedObjIUnknown)); + ForceGC(); + AssertNativeObjectWrapperAlive(cw, resurrecter, unmanagedObjIUnknown); + + resurrecter.Dispose(); + Marshal.Release(unmanagedObjIUnknown); + Assert.Equal(0, Marshal.Release(unmanagedObj)); + + [MethodImpl(MethodImplOptions.NoInlining)] + static nint AllocateWrapper(ComWrappers cw, out WeakGCHandle handle) + { + Test test = new(); + nint comWrapper = cw.GetOrCreateComInterfaceForObject(test, CreateComInterfaceFlags.None); + Assert.NotEqual(IntPtr.Zero, comWrapper); + + var unmanagedWrapper = (ManualReleaseITestObjectWrapper)cw.GetOrCreateObjectForComInstance(comWrapper, CreateObjectFlags.UniqueInstance); + Resurrecter resurrecter = new() + { + UnmanagedWrapper = unmanagedWrapper, + }; + handle = new WeakGCHandle(resurrecter, true); + return comWrapper; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void AssertNativeObjectWrapperAlive(ComWrappers cw, WeakGCHandle handle, IntPtr unmanagedObj) + { + Assert.True(handle.TryGetTarget(out Resurrecter resurrecter)); + ManualReleaseITestObjectWrapper? unmanagedWrapper = resurrecter.UnmanagedWrapper; + Assert.NotNull(resurrecter); + Assert.True(ComWrappers.TryGetComInstance(unmanagedWrapper, out IntPtr unmanagedObjOther)); + Assert.Equal(unmanagedObj, unmanagedObjOther); + resurrecter.UnmanagedWrapper = null; + Marshal.Release(unmanagedObjOther); + unmanagedWrapper.FinalRelease(); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] [ActiveIssue("Not supported on Mono", TestRuntimes.Mono)] [Fact] diff --git a/src/tests/Interop/COM/ComWrappers/Common.cs b/src/tests/Interop/COM/ComWrappers/Common.cs index ca32876a04a2ba..03d98749076873 100644 --- a/src/tests/Interop/COM/ComWrappers/Common.cs +++ b/src/tests/Interop/COM/ComWrappers/Common.cs @@ -94,13 +94,13 @@ public static int SetValueInternal(IntPtr dispatchPtr, int i) } } - public class ITestObjectWrapper : ITest + public class ITestObjectWrapperBase : ITest { private readonly ITestVtbl._SetValue _setValue; - private readonly IntPtr _ptr; - private bool _released; + protected readonly IntPtr _ptr; + protected bool _released; - public ITestObjectWrapper(IntPtr ptr) + public ITestObjectWrapperBase(IntPtr ptr) { _ptr = ptr; VtblPtr inst = Marshal.PtrToStructure(ptr); @@ -117,6 +117,24 @@ public int FinalRelease() return count; } + public void SetValue(int i) => _setValue(_ptr, i); + } + + public class ManualReleaseITestObjectWrapper : ITestObjectWrapperBase + { + public ManualReleaseITestObjectWrapper(IntPtr ptr) + : base(ptr) + { + } + } + + public class ITestObjectWrapper : ITestObjectWrapperBase + { + public ITestObjectWrapper(IntPtr ptr) + : base(ptr) + { + } + ~ITestObjectWrapper() { if (_ptr != IntPtr.Zero && !_released) @@ -124,8 +142,6 @@ public int FinalRelease() Marshal.Release(_ptr); } } - - public void SetValue(int i) => _setValue(_ptr, i); } //