From ae4823ed5c45d5f0f2e38338b987399d3118c486 Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Sat, 16 Jan 2021 16:49:43 -0800 Subject: [PATCH 1/3] Tactical mem leak fix for aggregation --- src/Directory.Build.targets | 1 + ...UI.Threading.DispatcherQueueSyncContext.cs | 47 + src/Tests/TestComponentCSharp/Composable.cpp | 15 + src/Tests/TestComponentCSharp/Composable.h | 20 + .../TestComponentCSharp.idl | 7 + .../TestComponentCSharp.vcxproj | 2 + .../TestComponentCSharp.vcxproj.filters | 2 + .../UnitTest/TestComponentCSharp_Tests.cs | 301 ++-- src/WinRT.Runtime/ComWrappersSupport.cs | 1328 ++++++++--------- src/WinRT.Runtime/ComWrappersSupport.net5.cs | 221 ++- .../MatchingRefApiCompatBaseline.net5.0.txt | 5 +- src/WinRT.Runtime/ObjectReference.cs | 613 ++++---- src/WinRT.Runtime/WinRT.Runtime.csproj | 1 + src/cswinrt/code_writers.h | 34 +- 14 files changed, 1504 insertions(+), 1093 deletions(-) create mode 100644 src/Projections/WinUI/Microsoft.UI.Threading.DispatcherQueueSyncContext.cs create mode 100644 src/Tests/TestComponentCSharp/Composable.cpp create mode 100644 src/Tests/TestComponentCSharp/Composable.h diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index b331972808..5b1a35523b 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -7,6 +7,7 @@ https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json; https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json; https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json; + $(RestoreSources) diff --git a/src/Projections/WinUI/Microsoft.UI.Threading.DispatcherQueueSyncContext.cs b/src/Projections/WinUI/Microsoft.UI.Threading.DispatcherQueueSyncContext.cs new file mode 100644 index 0000000000..4e20a4ce8b --- /dev/null +++ b/src/Projections/WinUI/Microsoft.UI.Threading.DispatcherQueueSyncContext.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; +using Microsoft.System; + +namespace Microsoft.UI.Threading +{ + /// + /// DispatcherQueueSyncContext allows developers to await calls and get back onto the + /// UI thread. Needs to be installed on the UI thread through DispatcherQueueSyncContext.SetForCurrentThread + /// + public class DispatcherQueueSyncContext : SynchronizationContext + { + private readonly DispatcherQueue m_dispatcherQueue; + + /// + /// Installs a DispatcherQueueSyncContext on the current thread + /// + public static void SetForCurrentThread() + { + var context = new DispatcherQueueSyncContext(DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + } + + internal DispatcherQueueSyncContext(DispatcherQueue dispatcherQueue) + { + m_dispatcherQueue = dispatcherQueue; + } + + public override void Post(SendOrPostCallback d, object state) + { + if (d == null) + throw new ArgumentNullException(nameof(d)); + + m_dispatcherQueue.TryEnqueue(() => d(state)); + } + + public override void Send(SendOrPostCallback d, object state) + { + throw new NotSupportedException("Send not supported"); + } + + public override SynchronizationContext CreateCopy() + { + return new DispatcherQueueSyncContext(m_dispatcherQueue); + } + } +} \ No newline at end of file diff --git a/src/Tests/TestComponentCSharp/Composable.cpp b/src/Tests/TestComponentCSharp/Composable.cpp new file mode 100644 index 0000000000..3d0db1b755 --- /dev/null +++ b/src/Tests/TestComponentCSharp/Composable.cpp @@ -0,0 +1,15 @@ +#include "pch.h" +#include "Composable.h" +#include "Composable.g.cpp" + +namespace winrt::TestComponentCSharp::implementation +{ + winrt::event_token Composable::StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler) + { + return _stringChanged.add(handler); + } + void Composable::StringPropertyChanged(winrt::event_token const& token) noexcept + { + _stringChanged.remove(token); + } +} diff --git a/src/Tests/TestComponentCSharp/Composable.h b/src/Tests/TestComponentCSharp/Composable.h new file mode 100644 index 0000000000..9c3dc00600 --- /dev/null +++ b/src/Tests/TestComponentCSharp/Composable.h @@ -0,0 +1,20 @@ +#pragma once +#include "Composable.g.h" + +namespace winrt::TestComponentCSharp::implementation +{ + struct Composable : ComposableT + { + Composable() = default; + + winrt::event> _stringChanged; + winrt::event_token StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler); + void StringPropertyChanged(winrt::event_token const& token) noexcept; + }; +} +namespace winrt::TestComponentCSharp::factory_implementation +{ + struct Composable : ComposableT + { + }; +} diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.idl b/src/Tests/TestComponentCSharp/TestComponentCSharp.idl index 6a33379011..7758daf5da 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.idl +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.idl @@ -368,6 +368,13 @@ namespace TestComponentCSharp CustomBindableVectorTest(); } + [default_interface] + unsealed runtimeclass Composable + { + Composable(); + event Windows.Foundation.TypedEventHandler StringPropertyChanged; + } + // SupportedOSPlatform warning tests [contract(Windows.Foundation.UniversalApiContract, 10)] [attributeusage(target_all)] diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj index 3521d99ee3..127af46e77 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj @@ -63,6 +63,7 @@ + @@ -75,6 +76,7 @@ + Create diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters index 75bb7cd5a6..0822856867 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters @@ -15,6 +15,7 @@ + @@ -23,6 +24,7 @@ + diff --git a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs index 8a8e578c4b..00531cc338 100644 --- a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs +++ b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs @@ -74,12 +74,12 @@ public void TestManyBufferExtensionMethods() arrayLen3.CopyTo(1, buffLen4, 0, 1); // copy just the second element of the array to the beginning of the buffer Assert.True(buffLen4.Length == 4); Assert.Throws(() => buffLen4.GetByte(5)); // shouldn't have a 5th element - Assert.True(buffLen4.GetByte(0) == 0x02); // make sure we got the 2nd element of the array - + Assert.True(buffLen4.GetByte(0) == 0x02); // make sure we got the 2nd element of the array + arrayLen3.CopyTo(buffLen4); // Array to Buffer copying Assert.True(buffLen4.Length == 4); Assert.True(buffLen4.GetByte(0) == 0x01); // make sure we updated the first few - Assert.True(buffLen4.GetByte(1) == 0x02); + Assert.True(buffLen4.GetByte(1) == 0x02); Assert.True(buffLen4.GetByte(2) == 0x03); Assert.True(buffLen4.GetByte(3) == 0x14); // and kept the last one @@ -141,14 +141,14 @@ public void TestIsSameDataUsingToArray() public void TestBufferAsStreamUsingAsBuffer() { var arr = new byte[] { 0x01, 0x02 }; - Stream stream = arr.AsBuffer().AsStream(); + Stream stream = arr.AsBuffer().AsStream(); Assert.True(stream != null); Assert.True(stream.Length == 2); } [Fact] public void TestBufferAsStreamWithEmptyBuffer1() - { + { var buffer = new Windows.Storage.Streams.Buffer(0); Stream stream = buffer.AsStream(); Assert.True(stream != null); @@ -270,7 +270,7 @@ public void TestWinRTBufferWithZeroLength() [Fact] public void TestEmptyBufferCopyTo() - { + { var buffer = new Windows.Storage.Streams.Buffer(0); byte[] array = { }; buffer.CopyTo(array); @@ -357,11 +357,11 @@ public void TestStreamWriteAsync() Assert.True(InvokeStreamWriteAsync().Wait(1000)); } - [Fact] - public void TestAsStream() - { - using InMemoryRandomAccessStream winrtStream = new InMemoryRandomAccessStream(); - using Stream normalStream = winrtStream.AsStream(); + [Fact] + public void TestAsStream() + { + using InMemoryRandomAccessStream winrtStream = new InMemoryRandomAccessStream(); + using Stream normalStream = winrtStream.AsStream(); using var memoryStream = new MemoryStream(); normalStream.CopyTo(memoryStream); } @@ -405,7 +405,7 @@ public void TestBuffer() { var arr1 = new byte[] { 0x01, 0x02 }; var buff = arr1.AsBuffer(); - var arr2 = buff.ToArray(0,2); + var arr2 = buff.ToArray(0, 2); Assert.True(arr1[0] == arr2[0]); Assert.True(arr1[1] == arr2[1]); } @@ -454,7 +454,7 @@ async Task InvokeWriteBufferAsync() [Fact] public void TestWriteBuffer() { - Assert.True(InvokeWriteBufferAsync().Wait(1000)); + Assert.True(InvokeWriteBufferAsync().Wait(1000)); } [Fact] @@ -692,26 +692,26 @@ public void TestObjectCasting() var objects = new List() { new ManagedType(), new ManagedType() }; var query = from item in objects select item; - TestObject.ObjectIterableProperty = query; - - TestObject.ObjectProperty = "test"; - Assert.Equal("test", TestObject.ObjectProperty); - - var objectArray = new ManagedType[] { new ManagedType(), new ManagedType() }; - TestObject.ObjectIterableProperty = objectArray; - Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectArray)); - - var strArray = new string[] { "str1", "str2", "str3" }; - TestObject.ObjectIterableProperty = strArray; - Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(strArray)); - - var uriArray = new Uri[] { new Uri("http://aka.ms/cswinrt"), new Uri("https://github.com") }; - TestObject.ObjectIterableProperty = uriArray; - Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(uriArray)); - - var objectUriArray = new object[] { new Uri("https://github.com") }; - TestObject.ObjectIterableProperty = objectUriArray; - Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectUriArray)); + TestObject.ObjectIterableProperty = query; + + TestObject.ObjectProperty = "test"; + Assert.Equal("test", TestObject.ObjectProperty); + + var objectArray = new ManagedType[] { new ManagedType(), new ManagedType() }; + TestObject.ObjectIterableProperty = objectArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectArray)); + + var strArray = new string[] { "str1", "str2", "str3" }; + TestObject.ObjectIterableProperty = strArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(strArray)); + + var uriArray = new Uri[] { new Uri("http://aka.ms/cswinrt"), new Uri("https://github.com") }; + TestObject.ObjectIterableProperty = uriArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(uriArray)); + + var objectUriArray = new object[] { new Uri("https://github.com") }; + TestObject.ObjectIterableProperty = objectUriArray; + Assert.True(TestObject.ObjectIterableProperty.SequenceEqual(objectUriArray)); } [Fact] @@ -768,10 +768,10 @@ public void TestFactories() var cls1 = new Class(); var cls2 = new Class(42); - Assert.Equal(42, cls2.IntProperty); - + Assert.Equal(42, cls2.IntProperty); + var cls3 = new Class(42, "foo"); - Assert.Equal(42, cls3.IntProperty); + Assert.Equal(42, cls3.IntProperty); Assert.Equal("foo", cls3.StringProperty); } @@ -1615,11 +1615,11 @@ public void TestPointTypeMapping() Assert.True(TestObject.PointProperty == pt); Assert.Equal(pt, TestObject.GetPointReference().Value); - var vector2 = TestObject.PointProperty.ToVector2(); + var vector2 = TestObject.PointProperty.ToVector2(); Assert.Equal(pt.X, vector2.X); Assert.Equal(pt.Y, vector2.Y); - TestObject.PointProperty = vector2.ToPoint(); + TestObject.PointProperty = vector2.ToPoint(); Assert.Equal(pt.X, TestObject.PointProperty.X); Assert.Equal(pt.Y, TestObject.PointProperty.Y); } @@ -1643,13 +1643,13 @@ public void TestSizeTypeMapping() TestObject.SizeProperty = size; Assert.Equal(size.Height, TestObject.SizeProperty.Height); Assert.Equal(size.Width, TestObject.SizeProperty.Width); - Assert.True(TestObject.SizeProperty == size); - - var vector2 = TestObject.SizeProperty.ToVector2(); + Assert.True(TestObject.SizeProperty == size); + + var vector2 = TestObject.SizeProperty.ToVector2(); Assert.Equal(size.Width, vector2.X); Assert.Equal(size.Height, vector2.Y); - TestObject.SizeProperty = vector2.ToSize(); + TestObject.SizeProperty = vector2.ToSize(); Assert.Equal(size.Width, TestObject.SizeProperty.Width); Assert.Equal(size.Height, TestObject.SizeProperty.Height); } @@ -1765,7 +1765,7 @@ public void TestMatrix3DTypeMapping() M11 = 11, M12 = 12, M13 = 13, M14 = 14, M21 = 21, M22 = 22, M23 = 23, M24 = 24, M31 = 31, M32 = 32, M33 = 33, M34 = 34, - OffsetX = 41, OffsetY = 42, OffsetZ = 43,M44 = 44 }; + OffsetX = 41, OffsetY = 42, OffsetZ = 43, M44 = 44 }; TestObject.Matrix3DProperty = matrix3D; Assert.Equal(matrix3D.M11, TestObject.Matrix3DProperty.M11); @@ -2201,8 +2201,8 @@ static Object MakeObject() static void TestObject() => MakeObject(); - static (IInitializeWithWindow, IWindowNative) MakeImports() - { + static (IInitializeWithWindow, IWindowNative) MakeImports() + { var obj = MakeObject(); var initializeWithWindow = obj.As(); var windowNative = obj.As(); @@ -2211,8 +2211,8 @@ static Object MakeObject() static void TestImports() { - var (initializeWithWindow, windowNative) = MakeImports(); - + var (initializeWithWindow, windowNative) = MakeImports(); + GC.Collect(); GC.WaitForPendingFinalizers(); @@ -2230,7 +2230,7 @@ static void TestImports() GC.Collect(); GC.WaitForPendingFinalizers(); Assert.Equal(0, ComImports.NumObjects); - } + } [Fact] public void TestInterfaceObjectMarshalling() @@ -2253,96 +2253,145 @@ public void TestNonProjectedRuntimeClass() Assert.NotNull(cryptoKey); } - [Fact(Skip="Operation not supported")] - public void TestIBindableIterator() - { - CustomBindableIteratorTest bindableIterator = new CustomBindableIteratorTest(); - Assert.True(bindableIterator.MoveNext()); - Assert.True(bindableIterator.HasCurrent); - Assert.Equal(27861, bindableIterator.Current); + [Fact(Skip = "Operation not supported")] + public void TestIBindableIterator() + { + CustomBindableIteratorTest bindableIterator = new CustomBindableIteratorTest(); + Assert.True(bindableIterator.MoveNext()); + Assert.True(bindableIterator.HasCurrent); + Assert.Equal(27861, bindableIterator.Current); } [Fact] - public void TestIDisposable() - { - CustomDisposableTest disposable = new CustomDisposableTest(); - disposable.Dispose(); + public void TestIDisposable() + { + CustomDisposableTest disposable = new CustomDisposableTest(); + disposable.Dispose(); } [Fact] - public void TestIBindableVector() - { - CustomBindableVectorTest vector = new CustomBindableVectorTest(); - Assert.NotNull(vector); + public void TestIBindableVector() + { + CustomBindableVectorTest vector = new CustomBindableVectorTest(); + Assert.NotNull(vector); + } + + [Fact] + public void TestCovariance() + { + var listOfListOfPoints = new List>() { + new List{ new Point(1, 1), new Point(1, 2), new Point(1, 3) }, + new List{ new Point(2, 1), new Point(2, 2), new Point(2, 3) }, + new List{ new Point(3, 1), new Point(3, 2), new Point(3, 3) } + }; + TestObject.IterableOfPointIterablesProperty = listOfListOfPoints; + Assert.True(TestObject.IterableOfPointIterablesProperty.SequenceEqual(listOfListOfPoints)); + + var listOfListOfUris = new List>() { + new List{ new Uri("http://aka.ms/cswinrt"), new Uri("https://github.com") }, + new List{ new Uri("http://aka.ms/cswinrt") }, + new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://microsoft.com") } + }; + TestObject.IterableOfObjectIterablesProperty = listOfListOfUris; + Assert.True(TestObject.IterableOfObjectIterablesProperty.SequenceEqual(listOfListOfUris)); } - [Fact] - public void TestCovariance() - { - var listOfListOfPoints = new List>() { - new List{ new Point(1, 1), new Point(1, 2), new Point(1, 3) }, - new List{ new Point(2, 1), new Point(2, 2), new Point(2, 3) }, - new List{ new Point(3, 1), new Point(3, 2), new Point(3, 3) } +#if NET5_0 + [Fact] + public void TestReferenceTrackingGarbageCollection() + { + static WeakReference CreateObject(Action customize = null) where T : new() + { + var obj = new T(); + if (customize != null) + { + customize(obj); + } + return new WeakReference(obj); }; - TestObject.IterableOfPointIterablesProperty = listOfListOfPoints; - Assert.True(TestObject.IterableOfPointIterablesProperty.SequenceEqual(listOfListOfPoints)); - var listOfListOfUris = new List>() { - new List{ new Uri("http://aka.ms/cswinrt"), new Uri("https://github.com") }, - new List{ new Uri("http://aka.ms/cswinrt") }, - new List{ new Uri("http://aka.ms/cswinrt"), new Uri("http://microsoft.com") } + static bool IsCollectible(WeakReference weakReference) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + return !weakReference.IsAlive; }; - TestObject.IterableOfObjectIterablesProperty = listOfListOfUris; - Assert.True(TestObject.IterableOfObjectIterablesProperty.SequenceEqual(listOfListOfUris)); - } -#if NET5_0 - [TestComponentCSharp.Warning] // NO warning CA1416 - class WarningManaged { }; - - class WarningSubclass : WarningClass - { - void InvokeOverridableWarnings() - { - WarningOverridableMethod(); // warning CA1416 - WarningOverridableProperty = 0; // warning CA1416 - // see https://github.com/microsoft/cppwinrt/issues/782 - //WarningOverridableEvent += (object s, Int32 v) => { }; // warning CA1416 - } + // Test collection of non-composable with no delegate + var nonComposableNoDelegate = CreateObject(); + Assert.True(IsCollectible(nonComposableNoDelegate)); + + // Test collection of composable without self-referential delegate + var composableNoDelegate = CreateObject(); + Assert.True(IsCollectible(composableNoDelegate)); + + // Test collection of non-composable with self-referential delegate + var nonComposableWithDelegate = CreateObject((obj) => { + obj.StringPropertyChanged += (Class sender, string value) => Assert.Equal(sender, obj); + }); + // TODO: implement IReferenceTracker to prevent delegate creating cycle + Assert.True(!IsCollectible(nonComposableWithDelegate)); + + // Test collection of composable with self-referential delegate + var composableWithDelegate = CreateObject((obj) => { + obj.StringPropertyChanged += (Composable sender, string value) => Assert.Equal(sender, obj); + }); + // TODO: implement IReferenceTracker to prevent delegate creating cycle + Assert.True(!IsCollectible(composableWithDelegate)); } - - // Manual for now - verify that all APIs targeting 19041 generate a warning - private void TestSupportedOSPlatformWarnings() - { - // Types - var a = new WarningAttribute(); // warning CA1416 - Assert.NotNull(a); - var w = new WarningStruct{ i32 = 0 }; // warning CA1416 - Assert.Equal(0, w.i32); // warning CA1416 - var v = WarningEnum.Value; - Assert.NotEqual(WarningEnum.WarningValue, v); // warning CA1416 - - // Members - var o = new WarningClass(); // warning CA1416 - o = new WarningClass(WarningEnum.Value); // warning CA1416 - o.WarningMethod(); // warning CA1416 - var p = o.WarningProperty; // warning CA1416 - o.WarningProperty = 0; // warning CA1416 - p = o.WarningPropertySetter; - o.WarningPropertySetter = 0; // warning CA1416 - o.WarningEvent += (object s, Int32 v) => { }; // warning CA1416 - o.WarningInterfaceMethod(); // warning CA1416 - p = o.WarningInterfaceProperty; // warning CA1416 - o.WarningInterfaceProperty = 0; // warning CA1416 - p = o.WarningInterfacePropertySetter; - o.WarningInterfacePropertySetter = 0; // warning CA1416 - o.WarningInterfaceEvent += (object s, Int32 v) => { }; // warning CA1416 - - // Attributed statics - WarningStatic.WarningMethod(); // warning CA1416 - WarningStatic.WarningProperty = 0; // warning CA1416 - WarningStatic.WarningEvent += (object s, Int32 v) => { }; // warning CA1416 +#endif + } + +#if NET5_0 + public class TestSupportedOSPlatformWarnings + { + [TestComponentCSharp.Warning] // NO warning CA1416 + class WarningManaged { }; + + class WarningSubclass : WarningClass + { + void InvokeOverridableWarnings() + { + WarningOverridableMethod(); // warning CA1416 + WarningOverridableProperty = 0; // warning CA1416 + // see https://github.com/microsoft/cppwinrt/issues/782 + //WarningOverridableEvent += (object s, Int32 v) => { }; // warning CA1416 + } + } + + // Manual for now - verify that all APIs targeting 19041 generate a warning + // Consider invoking roslyn and scraping/analyzing output as an automated test + private void Test() + { + // Types + var a = new WarningAttribute(); // warning CA1416 + Assert.NotNull(a); + var w = new WarningStruct{ i32 = 0 }; // warning CA1416 + Assert.Equal(0, w.i32); // warning CA1416 + var v = WarningEnum.Value; + Assert.NotEqual(WarningEnum.WarningValue, v); // warning CA1416 + + // Members + var o = new WarningClass(); // warning CA1416 + o = new WarningClass(WarningEnum.Value); // warning CA1416 + o.WarningMethod(); // warning CA1416 + var p = o.WarningProperty; // warning CA1416 + o.WarningProperty = 0; // warning CA1416 + p = o.WarningPropertySetter; + o.WarningPropertySetter = 0; // warning CA1416 + o.WarningEvent += (object s, Int32 v) => { }; // warning CA1416 + o.WarningInterfaceMethod(); // warning CA1416 + p = o.WarningInterfaceProperty; // warning CA1416 + o.WarningInterfaceProperty = 0; // warning CA1416 + p = o.WarningInterfacePropertySetter; + o.WarningInterfacePropertySetter = 0; // warning CA1416 + o.WarningInterfaceEvent += (object s, Int32 v) => { }; // warning CA1416 + + // Attributed statics + WarningStatic.WarningMethod(); // warning CA1416 + WarningStatic.WarningProperty = 0; // warning CA1416 + WarningStatic.WarningEvent += (object s, Int32 v) => { }; // warning CA1416 } -#endif } -} +#endif +} diff --git a/src/WinRT.Runtime/ComWrappersSupport.cs b/src/WinRT.Runtime/ComWrappersSupport.cs index 82058a9eec..b83affdb08 100644 --- a/src/WinRT.Runtime/ComWrappersSupport.cs +++ b/src/WinRT.Runtime/ComWrappersSupport.cs @@ -1,667 +1,667 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Numerics; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Linq.Expressions; -using WinRT.Interop; -using ABI.Windows.Foundation; -using ABI.Microsoft.UI.Xaml.Data; - -#if !NETSTANDARD2_0 -using ComInterfaceEntry = System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry; -#endif - -#pragma warning disable 0169 // The field 'xxx' is never used -#pragma warning disable 0649 // Field 'xxx' is never assigned to, and will always have its default value - -namespace WinRT -{ - public static partial class ComWrappersSupport - { - private readonly static ConcurrentDictionary> TypedObjectFactoryCache = new ConcurrentDictionary>(); - private readonly static ConditionalWeakTable CCWTable = new ConditionalWeakTable(); - - public static TReturn MarshalDelegateInvoke(IntPtr thisPtr, Func invoke) - where TDelegate : class, Delegate - { - using (new Mono.ThreadContext()) - { - var target_invoke = FindObject(thisPtr); - if (target_invoke != null) - { - return invoke(target_invoke); - } - return default; - } - } - - public static void MarshalDelegateInvoke(IntPtr thisPtr, Action invoke) - where T : class, Delegate - { - using (new Mono.ThreadContext()) - { - var target_invoke = FindObject(thisPtr); - if (target_invoke != null) - { - invoke(target_invoke); - } - } - } - - public static IObjectReference GetObjectReferenceForInterface(IntPtr externalComObject) - { - using var unknownRef = ObjectReference.FromAbi(externalComObject); - - if (unknownRef.TryAs(typeof(ABI.WinRT.Interop.IAgileObject.Vftbl).GUID, out var agileRef) >= 0) - { - agileRef.Dispose(); - return unknownRef.As(); - } - else - { - return new ObjectReferenceWithContext( - unknownRef.GetRef(), - Context.GetContextCallback()); - } - } - - public static void RegisterProjectionAssembly(Assembly assembly) => TypeNameSupport.RegisterProjectionAssembly(assembly); - - internal static object GetRuntimeClassCCWTypeIfAny(object obj) - { - var type = obj.GetType(); - var ccwType = type.GetRuntimeClassCCWType(); - if (ccwType != null) - { - return CCWTable.GetValue(obj, obj => { - var ccwConstructor = ccwType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.Instance, null, new[] { type }, null); - return ccwConstructor.Invoke(new[] { obj }); - }); - } - - return obj; - } - - internal static List GetInterfaceTableEntries(object obj) - { +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Numerics; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Linq.Expressions; +using WinRT.Interop; +using ABI.Windows.Foundation; +using ABI.Microsoft.UI.Xaml.Data; + +#if !NETSTANDARD2_0 +using ComInterfaceEntry = System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry; +#endif + +#pragma warning disable 0169 // The field 'xxx' is never used +#pragma warning disable 0649 // Field 'xxx' is never assigned to, and will always have its default value + +namespace WinRT +{ + public static partial class ComWrappersSupport + { + private readonly static ConcurrentDictionary> TypedObjectFactoryCache = new ConcurrentDictionary>(); + private readonly static ConditionalWeakTable CCWTable = new ConditionalWeakTable(); + + public static TReturn MarshalDelegateInvoke(IntPtr thisPtr, Func invoke) + where TDelegate : class, Delegate + { + using (new Mono.ThreadContext()) + { + var target_invoke = FindObject(thisPtr); + if (target_invoke != null) + { + return invoke(target_invoke); + } + return default; + } + } + + public static void MarshalDelegateInvoke(IntPtr thisPtr, Action invoke) + where T : class, Delegate + { + using (new Mono.ThreadContext()) + { + var target_invoke = FindObject(thisPtr); + if (target_invoke != null) + { + invoke(target_invoke); + } + } + } + + public static IObjectReference GetObjectReferenceForInterface(IntPtr externalComObject) + { + using var unknownRef = ObjectReference.FromAbi(externalComObject); + + if (unknownRef.TryAs(typeof(ABI.WinRT.Interop.IAgileObject.Vftbl).GUID, out var agileRef) >= 0) + { + agileRef.Dispose(); + return unknownRef.As(); + } + else + { + return new ObjectReferenceWithContext( + unknownRef.GetRef(), + Context.GetContextCallback()); + } + } + + public static void RegisterProjectionAssembly(Assembly assembly) => TypeNameSupport.RegisterProjectionAssembly(assembly); + + internal static object GetRuntimeClassCCWTypeIfAny(object obj) + { + var type = obj.GetType(); + var ccwType = type.GetRuntimeClassCCWType(); + if (ccwType != null) + { + return CCWTable.GetValue(obj, obj => { + var ccwConstructor = ccwType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.Instance, null, new[] { type }, null); + return ccwConstructor.Invoke(new[] { obj }); + }); + } + + return obj; + } + + internal static List GetInterfaceTableEntries(object obj) + { var entries = new List(); var objType = obj.GetType().GetRuntimeClassCCWType() ?? obj.GetType(); - var interfaces = objType.GetInterfaces(); - foreach (var iface in interfaces) - { - if (Projections.IsTypeWindowsRuntimeType(iface)) - { - var ifaceAbiType = iface.FindHelperType(); - entries.Add(new ComInterfaceEntry - { - IID = GuidGenerator.GetIID(ifaceAbiType), - Vtable = (IntPtr)ifaceAbiType.GetAbiToProjectionVftblPtr() - }); - } - - if (iface.IsConstructedGenericType - && Projections.TryGetCompatibleWindowsRuntimeTypesForVariantType(iface, out var compatibleIfaces)) - { - foreach (var compatibleIface in compatibleIfaces) - { - var compatibleIfaceAbiType = compatibleIface.FindHelperType(); - entries.Add(new ComInterfaceEntry - { - IID = GuidGenerator.GetIID(compatibleIfaceAbiType), - Vtable = (IntPtr)compatibleIfaceAbiType.GetAbiToProjectionVftblPtr() - }); - } - } - } - - if (obj is Delegate) - { - entries.Add(new ComInterfaceEntry - { - IID = GuidGenerator.GetIID(obj.GetType()), - Vtable = (IntPtr)obj.GetType().GetHelperType().GetAbiToProjectionVftblPtr() - }); - } - - if (objType.IsGenericType && objType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>)) - { - var ifaceAbiType = objType.FindHelperType(); - entries.Add(new ComInterfaceEntry - { - IID = GuidGenerator.GetIID(ifaceAbiType), - Vtable = (IntPtr)ifaceAbiType.GetAbiToProjectionVftblPtr() - }); - } - else if (ShouldProvideIReference(obj)) - { - entries.Add(IPropertyValueEntry); - entries.Add(ProvideIReference(obj)); - } - else if (ShouldProvideIReferenceArray(obj)) - { - entries.Add(IPropertyValueEntry); - entries.Add(ProvideIReferenceArray(obj)); - } - - entries.Add(new ComInterfaceEntry - { - IID = typeof(ManagedIStringableVftbl).GUID, - Vtable = ManagedIStringableVftbl.AbiToProjectionVftablePtr - }); - - entries.Add(new ComInterfaceEntry - { - IID = typeof(ManagedCustomPropertyProviderVftbl).GUID, - Vtable = ManagedCustomPropertyProviderVftbl.AbiToProjectionVftablePtr - }); - - entries.Add(new ComInterfaceEntry - { - IID = typeof(ABI.WinRT.Interop.IWeakReferenceSource.Vftbl).GUID, - Vtable = ABI.WinRT.Interop.IWeakReferenceSource.Vftbl.AbiToProjectionVftablePtr - }); - - // Add IAgileObject to all CCWs - entries.Add(new ComInterfaceEntry - { - IID = typeof(ABI.WinRT.Interop.IAgileObject.Vftbl).GUID, - Vtable = IUnknownVftbl.AbiToProjectionVftblPtr - }); - return entries; - } - - internal static (InspectableInfo inspectableInfo, List interfaceTableEntries) PregenerateNativeTypeInformation(object obj) - { - var interfaceTableEntries = GetInterfaceTableEntries(obj); - var iids = new Guid[interfaceTableEntries.Count]; - for (int i = 0; i < interfaceTableEntries.Count; i++) - { - iids[i] = interfaceTableEntries[i].IID; - } - - Type type = obj.GetType(); - - if (type.FullName.StartsWith("ABI.")) - { - type = Projections.FindCustomPublicTypeForAbiType(type) ?? type.Assembly.GetType(type.FullName.Substring("ABI.".Length)) ?? type; - } - - return ( - new InspectableInfo(type, iids), - interfaceTableEntries); - } - - private static bool IsNullableT(Type implementationType) - { - return implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == typeof(System.Nullable<>); - } - - private static bool IsIReferenceArray(Type implementationType) - { - return implementationType.FullName.StartsWith("Windows.Foundation.IReferenceArray`1"); - } - - private static Func CreateKeyValuePairFactory(Type type) - { - var parms = new[] { Expression.Parameter(typeof(IInspectable), "obj") }; - return Expression.Lambda>( - Expression.Call(type.GetHelperType().GetMethod("CreateRcw", BindingFlags.Public | BindingFlags.Static), - parms), parms).Compile(); - } - - private static Func CreateNullableTFactory(Type implementationType) - { - Type helperType = implementationType.GetHelperType(); - Type vftblType = helperType.FindVftblType(); - - ParameterExpression[] parms = new[] { Expression.Parameter(typeof(IInspectable), "inspectable") }; - var createInterfaceInstanceExpression = Expression.New(helperType.GetConstructor(new[] { typeof(ObjectReference<>).MakeGenericType(vftblType) }), - Expression.Call(parms[0], - typeof(IInspectable).GetMethod(nameof(IInspectable.As)).MakeGenericMethod(vftblType))); - - return Expression.Lambda>( - Expression.Convert(Expression.Property(createInterfaceInstanceExpression, "Value"), typeof(object)), parms).Compile(); - } - - private static Func CreateArrayFactory(Type implementationType) - { - Type helperType = implementationType.GetHelperType(); - Type vftblType = helperType.FindVftblType(); - - ParameterExpression[] parms = new[] { Expression.Parameter(typeof(IInspectable), "inspectable") }; - var createInterfaceInstanceExpression = Expression.New(helperType.GetConstructor(new[] { typeof(ObjectReference<>).MakeGenericType(vftblType) }), - Expression.Call(parms[0], - typeof(IInspectable).GetMethod(nameof(IInspectable.As)).MakeGenericMethod(vftblType))); - - return Expression.Lambda>( - Expression.Property(createInterfaceInstanceExpression, "Value"), parms).Compile(); - } - - internal static Func CreateTypedRcwFactory(string runtimeClassName) - { - // If runtime class name is empty or "Object", then just use IInspectable. - if (string.IsNullOrEmpty(runtimeClassName) || runtimeClassName == "Object") - { - return (IInspectable obj) => obj; - } - // PropertySet and ValueSet can return IReference but Nullable is illegal - if (runtimeClassName == "Windows.Foundation.IReference`1") - { - return (IInspectable obj) => new ABI.System.Nullable(obj.ObjRef); - } - else if (runtimeClassName == "Windows.Foundation.IReference`1") - { - return (IInspectable obj) => new ABI.System.Nullable(obj.ObjRef); - } - - Type implementationType = null; - - try - { - (implementationType, _) = TypeNameSupport.FindTypeByName(runtimeClassName.AsSpan()); - } - catch (TypeLoadException) - { - // If we reach here, then we couldn't find a type that matches the runtime class name. - // Fall back to using IInspectable directly. - return (IInspectable obj) => obj; - } - - if (implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>)) - { - return CreateKeyValuePairFactory(implementationType); - } - - if (implementationType.IsValueType) - { - if (IsNullableT(implementationType)) - { - return CreateNullableTFactory(implementationType); - } - else - { - return CreateNullableTFactory(typeof(System.Nullable<>).MakeGenericType(implementationType)); - } - } - else if (IsIReferenceArray(implementationType)) - { - return CreateArrayFactory(implementationType); - } - - return CreateFactoryForImplementationType(runtimeClassName, implementationType); - } - - internal static string GetRuntimeClassForTypeCreation(IInspectable inspectable, Type staticallyDeterminedType) - { - string runtimeClassName = inspectable.GetRuntimeClassName(noThrow: true); - if (staticallyDeterminedType != null && staticallyDeterminedType != typeof(object)) - { - // We have a static type which we can use to construct the object. But, we can't just use it for all scenarios - // and primarily use it for tear off scenarios and for scenarios where runtimeclass isn't accurate. - // For instance if the static type is an interface, we return an IInspectable to represent the interface. - // But it isn't convertable back to the class via the as operator which would be possible if we use runtimeclass. - // Similarly for composable types, they can be statically retrieved using the parent class, but can then no longer - // be cast to the sub class via as operator even if it is really an instance of it per rutimeclass. - // To handle these scenarios, we use the runtimeclass if we find it is assignable to the statically determined type. - // If it isn't, we use the statically determined type as it is a tear off. - - Type implementationType = null; - if (!string.IsNullOrEmpty(runtimeClassName)) - { - try - { - (implementationType, _) = TypeNameSupport.FindTypeByName(runtimeClassName.AsSpan()); - } - catch (TypeLoadException) - { - } - } - - if (!(implementationType != null && - (staticallyDeterminedType == implementationType || - staticallyDeterminedType.IsAssignableFrom(implementationType) || - staticallyDeterminedType.IsGenericType && implementationType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == staticallyDeterminedType.GetGenericTypeDefinition())))) - { - runtimeClassName = TypeNameSupport.GetNameForType(staticallyDeterminedType, TypeNameGenerationFlags.GenerateBoxedName); - } - } - - return runtimeClassName; - } - - private static bool ShouldProvideIReference(object obj) - { - return obj.GetType().IsValueType || obj is string || obj is Type || obj is Delegate; - } - - - private static ComInterfaceEntry IPropertyValueEntry => - new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(global::Windows.Foundation.IPropertyValue)), - Vtable = ManagedIPropertyValueImpl.AbiToProjectionVftablePtr - }; - - private static ComInterfaceEntry ProvideIReference(object obj) - { - Type type = obj.GetType(); - - if (type == typeof(int)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(string)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(byte)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(short)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(ushort)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(uint)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(long)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(ulong)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(float)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(double)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(char)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(bool)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(Guid)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(DateTimeOffset)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(TimeSpan)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(object)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - if (obj is Type) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), - Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr - }; - } - - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable<>).MakeGenericType(type)), - Vtable = (IntPtr)typeof(BoxedValueIReferenceImpl<>).MakeGenericType(type).GetAbiToProjectionVftblPtr() - }; - } - - private static bool ShouldProvideIReferenceArray(object obj) - { - return obj is Array arr && arr.Rank == 1 && arr.GetLowerBound(0) == 0 && !obj.GetType().GetElementType().IsArray; - } - - private static ComInterfaceEntry ProvideIReferenceArray(object obj) - { - Type type = obj.GetType().GetElementType(); - if (type == typeof(int)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(string)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(byte)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(short)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(ushort)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(uint)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(long)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(ulong)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(float)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(double)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(char)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(bool)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(Guid)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(DateTimeOffset)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(TimeSpan)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (type == typeof(object)) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - if (obj is Type) - { - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), - Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr - }; - } - return new ComInterfaceEntry - { - IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray<>).MakeGenericType(type)), - Vtable = (IntPtr)typeof(BoxedArrayIReferenceArrayImpl<>).MakeGenericType(type).GetAbiToProjectionVftblPtr() - }; - } - - internal class InspectableInfo - { - private readonly Lazy runtimeClassName; - - public Guid[] IIDs { get; } - public string RuntimeClassName => runtimeClassName.Value; - - internal InspectableInfo(Type type, Guid[] iids) - { - runtimeClassName = new Lazy(() => TypeNameSupport.GetNameForType(type, TypeNameGenerationFlags.GenerateBoxedName | TypeNameGenerationFlags.NoCustomTypeName)); - IIDs = iids; - } - - } - } + var interfaces = objType.GetInterfaces(); + foreach (var iface in interfaces) + { + if (Projections.IsTypeWindowsRuntimeType(iface)) + { + var ifaceAbiType = iface.FindHelperType(); + entries.Add(new ComInterfaceEntry + { + IID = GuidGenerator.GetIID(ifaceAbiType), + Vtable = (IntPtr)ifaceAbiType.GetAbiToProjectionVftblPtr() + }); + } + + if (iface.IsConstructedGenericType + && Projections.TryGetCompatibleWindowsRuntimeTypesForVariantType(iface, out var compatibleIfaces)) + { + foreach (var compatibleIface in compatibleIfaces) + { + var compatibleIfaceAbiType = compatibleIface.FindHelperType(); + entries.Add(new ComInterfaceEntry + { + IID = GuidGenerator.GetIID(compatibleIfaceAbiType), + Vtable = (IntPtr)compatibleIfaceAbiType.GetAbiToProjectionVftblPtr() + }); + } + } + } + + if (obj is Delegate) + { + entries.Add(new ComInterfaceEntry + { + IID = GuidGenerator.GetIID(obj.GetType()), + Vtable = (IntPtr)obj.GetType().GetHelperType().GetAbiToProjectionVftblPtr() + }); + } + + if (objType.IsGenericType && objType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>)) + { + var ifaceAbiType = objType.FindHelperType(); + entries.Add(new ComInterfaceEntry + { + IID = GuidGenerator.GetIID(ifaceAbiType), + Vtable = (IntPtr)ifaceAbiType.GetAbiToProjectionVftblPtr() + }); + } + else if (ShouldProvideIReference(obj)) + { + entries.Add(IPropertyValueEntry); + entries.Add(ProvideIReference(obj)); + } + else if (ShouldProvideIReferenceArray(obj)) + { + entries.Add(IPropertyValueEntry); + entries.Add(ProvideIReferenceArray(obj)); + } + + entries.Add(new ComInterfaceEntry + { + IID = typeof(ManagedIStringableVftbl).GUID, + Vtable = ManagedIStringableVftbl.AbiToProjectionVftablePtr + }); + + entries.Add(new ComInterfaceEntry + { + IID = typeof(ManagedCustomPropertyProviderVftbl).GUID, + Vtable = ManagedCustomPropertyProviderVftbl.AbiToProjectionVftablePtr + }); + + entries.Add(new ComInterfaceEntry + { + IID = typeof(ABI.WinRT.Interop.IWeakReferenceSource.Vftbl).GUID, + Vtable = ABI.WinRT.Interop.IWeakReferenceSource.Vftbl.AbiToProjectionVftablePtr + }); + + // Add IAgileObject to all CCWs + entries.Add(new ComInterfaceEntry + { + IID = typeof(ABI.WinRT.Interop.IAgileObject.Vftbl).GUID, + Vtable = IUnknownVftbl.AbiToProjectionVftblPtr + }); + return entries; + } + + internal static (InspectableInfo inspectableInfo, List interfaceTableEntries) PregenerateNativeTypeInformation(object obj) + { + var interfaceTableEntries = GetInterfaceTableEntries(obj); + var iids = new Guid[interfaceTableEntries.Count]; + for (int i = 0; i < interfaceTableEntries.Count; i++) + { + iids[i] = interfaceTableEntries[i].IID; + } + + Type type = obj.GetType(); + + if (type.FullName.StartsWith("ABI.")) + { + type = Projections.FindCustomPublicTypeForAbiType(type) ?? type.Assembly.GetType(type.FullName.Substring("ABI.".Length)) ?? type; + } + + return ( + new InspectableInfo(type, iids), + interfaceTableEntries); + } + + private static bool IsNullableT(Type implementationType) + { + return implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == typeof(System.Nullable<>); + } + + private static bool IsIReferenceArray(Type implementationType) + { + return implementationType.FullName.StartsWith("Windows.Foundation.IReferenceArray`1"); + } + + private static Func CreateKeyValuePairFactory(Type type) + { + var parms = new[] { Expression.Parameter(typeof(IInspectable), "obj") }; + return Expression.Lambda>( + Expression.Call(type.GetHelperType().GetMethod("CreateRcw", BindingFlags.Public | BindingFlags.Static), + parms), parms).Compile(); + } + + private static Func CreateNullableTFactory(Type implementationType) + { + Type helperType = implementationType.GetHelperType(); + Type vftblType = helperType.FindVftblType(); + + ParameterExpression[] parms = new[] { Expression.Parameter(typeof(IInspectable), "inspectable") }; + var createInterfaceInstanceExpression = Expression.New(helperType.GetConstructor(new[] { typeof(ObjectReference<>).MakeGenericType(vftblType) }), + Expression.Call(parms[0], + typeof(IInspectable).GetMethod(nameof(IInspectable.As)).MakeGenericMethod(vftblType))); + + return Expression.Lambda>( + Expression.Convert(Expression.Property(createInterfaceInstanceExpression, "Value"), typeof(object)), parms).Compile(); + } + + private static Func CreateArrayFactory(Type implementationType) + { + Type helperType = implementationType.GetHelperType(); + Type vftblType = helperType.FindVftblType(); + + ParameterExpression[] parms = new[] { Expression.Parameter(typeof(IInspectable), "inspectable") }; + var createInterfaceInstanceExpression = Expression.New(helperType.GetConstructor(new[] { typeof(ObjectReference<>).MakeGenericType(vftblType) }), + Expression.Call(parms[0], + typeof(IInspectable).GetMethod(nameof(IInspectable.As)).MakeGenericMethod(vftblType))); + + return Expression.Lambda>( + Expression.Property(createInterfaceInstanceExpression, "Value"), parms).Compile(); + } + + internal static Func CreateTypedRcwFactory(string runtimeClassName) + { + // If runtime class name is empty or "Object", then just use IInspectable. + if (string.IsNullOrEmpty(runtimeClassName) || runtimeClassName == "Object") + { + return (IInspectable obj) => obj; + } + // PropertySet and ValueSet can return IReference but Nullable is illegal + if (runtimeClassName == "Windows.Foundation.IReference`1") + { + return (IInspectable obj) => new ABI.System.Nullable(obj.ObjRef); + } + else if (runtimeClassName == "Windows.Foundation.IReference`1") + { + return (IInspectable obj) => new ABI.System.Nullable(obj.ObjRef); + } + + Type implementationType = null; + + try + { + (implementationType, _) = TypeNameSupport.FindTypeByName(runtimeClassName.AsSpan()); + } + catch (TypeLoadException) + { + // If we reach here, then we couldn't find a type that matches the runtime class name. + // Fall back to using IInspectable directly. + return (IInspectable obj) => obj; + } + + if (implementationType.IsGenericType && implementationType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.KeyValuePair<,>)) + { + return CreateKeyValuePairFactory(implementationType); + } + + if (implementationType.IsValueType) + { + if (IsNullableT(implementationType)) + { + return CreateNullableTFactory(implementationType); + } + else + { + return CreateNullableTFactory(typeof(System.Nullable<>).MakeGenericType(implementationType)); + } + } + else if (IsIReferenceArray(implementationType)) + { + return CreateArrayFactory(implementationType); + } + + return CreateFactoryForImplementationType(runtimeClassName, implementationType); + } + + internal static string GetRuntimeClassForTypeCreation(IInspectable inspectable, Type staticallyDeterminedType) + { + string runtimeClassName = inspectable.GetRuntimeClassName(noThrow: true); + if (staticallyDeterminedType != null && staticallyDeterminedType != typeof(object)) + { + // We have a static type which we can use to construct the object. But, we can't just use it for all scenarios + // and primarily use it for tear off scenarios and for scenarios where runtimeclass isn't accurate. + // For instance if the static type is an interface, we return an IInspectable to represent the interface. + // But it isn't convertable back to the class via the as operator which would be possible if we use runtimeclass. + // Similarly for composable types, they can be statically retrieved using the parent class, but can then no longer + // be cast to the sub class via as operator even if it is really an instance of it per rutimeclass. + // To handle these scenarios, we use the runtimeclass if we find it is assignable to the statically determined type. + // If it isn't, we use the statically determined type as it is a tear off. + + Type implementationType = null; + if (!string.IsNullOrEmpty(runtimeClassName)) + { + try + { + (implementationType, _) = TypeNameSupport.FindTypeByName(runtimeClassName.AsSpan()); + } + catch (TypeLoadException) + { + } + } + + if (!(implementationType != null && + (staticallyDeterminedType == implementationType || + staticallyDeterminedType.IsAssignableFrom(implementationType) || + staticallyDeterminedType.IsGenericType && implementationType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == staticallyDeterminedType.GetGenericTypeDefinition())))) + { + runtimeClassName = TypeNameSupport.GetNameForType(staticallyDeterminedType, TypeNameGenerationFlags.GenerateBoxedName); + } + } + + return runtimeClassName; + } + + private static bool ShouldProvideIReference(object obj) + { + return obj.GetType().IsValueType || obj is string || obj is Type || obj is Delegate; + } + + + private static ComInterfaceEntry IPropertyValueEntry => + new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(global::Windows.Foundation.IPropertyValue)), + Vtable = ManagedIPropertyValueImpl.AbiToProjectionVftablePtr + }; + + private static ComInterfaceEntry ProvideIReference(object obj) + { + Type type = obj.GetType(); + + if (type == typeof(int)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(string)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(byte)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(short)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(ushort)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(uint)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(long)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(ulong)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(float)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(double)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(char)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(bool)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(Guid)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(DateTimeOffset)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(TimeSpan)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(object)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + if (obj is Type) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable)), + Vtable = BoxedValueIReferenceImpl.AbiToProjectionVftablePtr + }; + } + + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(ABI.System.Nullable<>).MakeGenericType(type)), + Vtable = (IntPtr)typeof(BoxedValueIReferenceImpl<>).MakeGenericType(type).GetAbiToProjectionVftblPtr() + }; + } + + private static bool ShouldProvideIReferenceArray(object obj) + { + return obj is Array arr && arr.Rank == 1 && arr.GetLowerBound(0) == 0 && !obj.GetType().GetElementType().IsArray; + } + + private static ComInterfaceEntry ProvideIReferenceArray(object obj) + { + Type type = obj.GetType().GetElementType(); + if (type == typeof(int)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(string)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(byte)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(short)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(ushort)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(uint)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(long)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(ulong)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(float)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(double)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(char)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(bool)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(Guid)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(DateTimeOffset)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(TimeSpan)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (type == typeof(object)) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + if (obj is Type) + { + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray)), + Vtable = BoxedArrayIReferenceArrayImpl.AbiToProjectionVftablePtr + }; + } + return new ComInterfaceEntry + { + IID = global::WinRT.GuidGenerator.GetIID(typeof(IReferenceArray<>).MakeGenericType(type)), + Vtable = (IntPtr)typeof(BoxedArrayIReferenceArrayImpl<>).MakeGenericType(type).GetAbiToProjectionVftblPtr() + }; + } + + internal class InspectableInfo + { + private readonly Lazy runtimeClassName; + + public Guid[] IIDs { get; } + public string RuntimeClassName => runtimeClassName.Value; + + internal InspectableInfo(Type type, Guid[] iids) + { + runtimeClassName = new Lazy(() => TypeNameSupport.GetNameForType(type, TypeNameGenerationFlags.GenerateBoxedName | TypeNameGenerationFlags.NoCustomTypeName)); + IIDs = iids; + } + + } + } } \ No newline at end of file diff --git a/src/WinRT.Runtime/ComWrappersSupport.net5.cs b/src/WinRT.Runtime/ComWrappersSupport.net5.cs index 39884ba018..af93c41f88 100644 --- a/src/WinRT.Runtime/ComWrappersSupport.net5.cs +++ b/src/WinRT.Runtime/ComWrappersSupport.net5.cs @@ -110,9 +110,14 @@ public static bool TryUnwrapObject(object o, out IObjectReference objRef) return false; } - public static void RegisterObjectForInterface(object obj, IntPtr thisPtr) => TryRegisterObjectForInterface(obj, thisPtr); + public static void RegisterObjectForInterface(object obj, IntPtr thisPtr, CreateObjectFlags createObjectFlags) => + ComWrappers.GetOrRegisterObjectForComInstance(thisPtr, createObjectFlags, obj); + + public static void RegisterObjectForInterface(object obj, IntPtr thisPtr) => + TryRegisterObjectForInterface(obj, thisPtr); - public static object TryRegisterObjectForInterface(object obj, IntPtr thisPtr) => ComWrappers.GetOrRegisterObjectForComInstance(thisPtr, CreateObjectFlags.TrackerObject, obj); + public static object TryRegisterObjectForInterface(object obj, IntPtr thisPtr) => + ComWrappers.GetOrRegisterObjectForComInstance(thisPtr, CreateObjectFlags.TrackerObject, obj); public static IObjectReference CreateCCWForObject(object obj) { @@ -161,7 +166,217 @@ private static Func CreateFactoryForImplementationType(str Expression.Property(parms[0], nameof(WinRT.IInspectable.ObjRef))), parms).Compile(); } - } + } + + public class ComWrappersHelper + { + private static Guid IID_IReferenceTracker = new Guid("11d3b13a-180e-4789-a8be-7712882893e6"); + + [Flags] + public enum ReleaseFlags + { + None = 0, + Instance = 1, + Inner = 2, + ReferenceTracker = 4 + } + + public struct ClassNative + { + public ReleaseFlags Release; + public IntPtr Instance; + public IntPtr Inner; + public IntPtr ReferenceTracker; + } + + public unsafe static void Init( + ref ClassNative classNative, + bool isAggregation, + object thisInstance, + IntPtr newInstance, + IntPtr inner) + { + classNative.Instance = newInstance; + classNative.Inner = inner; + + { + // Determine if the instance supports IReferenceTracker (e.g. WinUI). + // Acquiring this interface is useful for: + // 1) Providing an indication of what value to pass during RCW creation. + // 2) Informing the Reference Tracker runtime during non-aggregation + // scenarios about new references. + // + // If aggregation, query the inner since that will have the implementation + // otherwise the new instance will be used. Since the inner was composed + // it should answer immediately without going through the outer. Either way + // the reference count will go to the new instance. + IntPtr queryForTracker = isAggregation ? classNative.Inner : classNative.Instance; + int hr = Marshal.QueryInterface(queryForTracker, ref IID_IReferenceTracker, out classNative.ReferenceTracker); + if (hr != 0) + { + classNative.ReferenceTracker = default; + } + } + + { + // Determine flags needed for native object wrapper (i.e. RCW) creation. + var createObjectFlags = CreateObjectFlags.None; + IntPtr instanceToWrap = classNative.Instance; + + // Update flags if the native instance is being used in an aggregation scenario. + if (isAggregation) + { + // Indicate the scenario is aggregation + createObjectFlags |= (CreateObjectFlags)4; + + // The instance supports IReferenceTracker. + if (classNative.ReferenceTracker != default(IntPtr)) + { + createObjectFlags |= CreateObjectFlags.TrackerObject; + + // IReferenceTracker is not needed in aggregation scenarios. + // It is not needed because all QueryInterface() calls on an + // object are followed by an immediately release of the returned + // pointer - see below for details. + Marshal.Release(classNative.ReferenceTracker); + + // .NET 5 limitation + // + // For aggregated scenarios involving IReferenceTracker + // the API handles object cleanup. In .NET 5 the API + // didn't expose an option to handle this so we pass the inner + // in order to handle its lifetime. + // + // The API doesn't handle inner lifetime in any other scenario + // in the .NET 5 timeframe. + instanceToWrap = classNative.Inner; + } + } + + // Create a native object wrapper (i.e. RCW). + // + // Note this function will call QueryInterface() on the supplied instance, + // therefore it is important that the enclosing CCW forwards to its inner + // if aggregation is involved. This is typically accomplished through an + // implementation of ICustomQueryInterface. + ComWrappersSupport.RegisterObjectForInterface(thisInstance, instanceToWrap, createObjectFlags); + } + + if (isAggregation) + { + // We release the instance here, but continue to use it since + // ownership was transferred to the API and it will guarantee + // the appropriate lifetime. + Marshal.Release(classNative.Instance); + } + else + { + // In non-aggregation scenarios where an inner exists and + // reference tracker is involved, we release the inner. + // + // .NET 5 limitation - see logic above. + if (classNative.Inner != default(IntPtr) && classNative.ReferenceTracker != default(IntPtr)) + { + Marshal.Release(classNative.Inner); + } + } + + // The following describes the valid local values to consider and details + // on their usage during the object's lifetime. + classNative.Release = ReleaseFlags.None; + if (isAggregation) + { + // Aggregation scenarios should avoid calling AddRef() on the + // newInstance value. This is due to the semantics of COM Aggregation + // and the fact that calling an AddRef() on the instance will increment + // the CCW which in turn will ensure it cannot be cleaned up. Calling + // AddRef() on the instance when passed to unmanaged code is correct + // since unmanaged code is required to call Release() at some point. + if (classNative.ReferenceTracker == default(IntPtr)) + { + // COM scenario + // The pointer to dispatch on for the instance. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // Instance + + // A pointer to the inner that should be queried for + // additional interfaces. Immediately after a QueryInterface() + // a Release() should be called on the returned pointer but the + // pointer can be retained and used. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.Inner; // Inner + } + else + { + // WinUI scenario + // The pointer to dispatch on for the instance. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // Instance + + // A pointer to the inner that should be queried for + // additional interfaces. Immediately after a QueryInterface() + // a Release() should be called on the returned pointer but the + // pointer can be retained and used. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // Inner + + // No longer needed. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // ReferenceTracker + } + } + else + { + if (classNative.ReferenceTracker == default(IntPtr)) + { + // COM scenario + // The pointer to dispatch on for the instance. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.Instance; // Instance + } + else + { + // WinUI scenario + // The pointer to dispatch on for the instance. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.Instance; // Instance + + // This instance should be used to tell the + // Reference Tracker runtime whenever an AddRef()/Release() + // is performed on newInstance. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.ReferenceTracker; // ReferenceTracker + } + } + + if (isAggregation) + { + Marshal.AddRef(newInstance); + var outerRC = Marshal.Release(newInstance); + if (outerRC != 0) + { + // In aggregation scenarios, at this point outer's refcount must be 0 + System.Diagnostics.Debugger.Break(); + } + } + } + + public static void Cleanup(ref ClassNative classNative) + { + if (classNative.Release.HasFlag(ReleaseFlags.Inner)) + { + Marshal.Release(classNative.Inner); + } + if (classNative.Release.HasFlag(ReleaseFlags.Instance)) + { + Marshal.Release(classNative.Instance); + } + if (classNative.Release.HasFlag(ReleaseFlags.ReferenceTracker)) + { + Marshal.Release(classNative.ReferenceTracker); + } + } + } public class DefaultComWrappers : ComWrappers { diff --git a/src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt b/src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt index 9604eab403..f900ba410f 100644 --- a/src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt +++ b/src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt @@ -1,3 +1,6 @@ Compat issues with assembly WinRT.Runtime: TypesMustExist : Type 'System.Numerics.VectorExtensions' does not exist in the reference but it does exist in the implementation. -Total Issues: 1 +TypesMustExist : Type 'WinRT.ComWrappersHelper' does not exist in the reference but it does exist in the implementation. +MembersMustExist : Member 'public void WinRT.ComWrappersSupport.RegisterObjectForInterface(System.Object, System.IntPtr, System.Runtime.InteropServices.CreateObjectFlags)' does not exist in the reference but it does exist in the implementation. +MembersMustExist : Member 'public void WinRT.IObjectReference.Detach()' does not exist in the reference but it does exist in the implementation. +Total Issues: 4 diff --git a/src/WinRT.Runtime/ObjectReference.cs b/src/WinRT.Runtime/ObjectReference.cs index f2883ed5c6..eaec543686 100644 --- a/src/WinRT.Runtime/ObjectReference.cs +++ b/src/WinRT.Runtime/ObjectReference.cs @@ -1,123 +1,137 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using WinRT.Interop; - -#pragma warning disable 0169 // The field 'xxx' is never used -#pragma warning disable 0649 // Field 'xxx' is never assigned to, and will always have its default value - -namespace WinRT -{ - public abstract class IObjectReference : IDisposable - { - protected bool disposed; - private readonly IntPtr _thisPtr; - private object _disposedLock = new object(); - - public IntPtr ThisPtr - { - get - { - ThrowIfDisposed(); - return _thisPtr; - } - } - - protected unsafe IUnknownVftbl VftblIUnknown - { - get - { - ThrowIfDisposed(); - return **(IUnknownVftbl**)ThisPtr; - } - } - - protected IObjectReference(IntPtr thisPtr) - { - if (thisPtr == IntPtr.Zero) - { - throw new ArgumentNullException(nameof(thisPtr)); - } - _thisPtr = thisPtr; - } - - ~IObjectReference() - { - Dispose(false); - } - - public ObjectReference As() => As(GuidGenerator.GetIID(typeof(T))); - public unsafe ObjectReference As(Guid iid) - { - ThrowIfDisposed(); - Marshal.ThrowExceptionForHR(VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr)); - return ObjectReference.Attach(ref thatPtr); - } - - public unsafe TInterface AsInterface() - { - if (typeof(TInterface).GetCustomAttribute(typeof(System.Runtime.InteropServices.ComImportAttribute)) is object) - { - Guid iid = typeof(TInterface).GUID; - Marshal.ThrowExceptionForHR(VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr comPtr)); - try - { - return (TInterface)Marshal.GetObjectForIUnknown(comPtr); - } - finally - { - var vftblPtr = Unsafe.AsRef(comPtr.ToPointer()); - var vftblIUnknown = Marshal.PtrToStructure(vftblPtr.Vftbl); - vftblIUnknown.Release(comPtr); - } - } - -#if NETSTANDARD2_0 - return (TInterface)typeof(TInterface).GetHelperType().GetConstructor(new[] { typeof(IObjectReference) }).Invoke(new object[] { this }); -#else - return (TInterface)(object)new WinRT.IInspectable(this); -#endif - } - - public int TryAs(out ObjectReference objRef) => TryAs(GuidGenerator.GetIID(typeof(T)), out objRef); - - public virtual unsafe int TryAs(Guid iid, out ObjectReference objRef) - { - objRef = null; - ThrowIfDisposed(); - int hr = VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr); - if (hr >= 0) - { - objRef = ObjectReference.Attach(ref thatPtr); - } - return hr; - } - - public unsafe IObjectReference As(Guid iid) => As(iid); - - public T AsType() - { - ThrowIfDisposed(); - var ctor = typeof(T).GetConstructor(new[] { typeof(IObjectReference) }); - if (ctor != null) - { - return (T)ctor.Invoke(new[] { this }); - } - throw new InvalidOperationException("Target type is not a projected interface."); - } - - public IntPtr GetRef() - { - ThrowIfDisposed(); - AddRef(); - return ThisPtr; - } - - protected void ThrowIfDisposed() +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using WinRT.Interop; + +#pragma warning disable 0169 // The field 'xxx' is never used +#pragma warning disable 0649 // Field 'xxx' is never assigned to, and will always have its default value + +namespace WinRT +{ + public abstract class IObjectReference : IDisposable + { + protected bool disposed; + private readonly IntPtr _thisPtr; + private object _disposedLock = new object(); + + public IntPtr ThisPtr + { + get + { + ThrowIfDisposed(); + return _thisPtr; + } + } + +#if DEBUG + private unsafe uint RefCount + { + get + { + VftblIUnknown.AddRef(ThisPtr); + return VftblIUnknown.Release(ThisPtr); + } + } + + private bool BreakOnDispose { get; set; } +#endif + + protected unsafe IUnknownVftbl VftblIUnknown + { + get + { + ThrowIfDisposed(); + return **(IUnknownVftbl**)ThisPtr; + } + } + + protected IObjectReference(IntPtr thisPtr) + { + if (thisPtr == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(thisPtr)); + } + _thisPtr = thisPtr; + } + + ~IObjectReference() + { + Dispose(false); + } + + public ObjectReference As() => As(GuidGenerator.GetIID(typeof(T))); + public unsafe ObjectReference As(Guid iid) + { + ThrowIfDisposed(); + Marshal.ThrowExceptionForHR(VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr)); + return ObjectReference.Attach(ref thatPtr); + } + + public unsafe TInterface AsInterface() + { + if (typeof(TInterface).GetCustomAttribute(typeof(System.Runtime.InteropServices.ComImportAttribute)) is object) + { + Guid iid = typeof(TInterface).GUID; + Marshal.ThrowExceptionForHR(VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr comPtr)); + try + { + return (TInterface)Marshal.GetObjectForIUnknown(comPtr); + } + finally + { + var vftblPtr = Unsafe.AsRef(comPtr.ToPointer()); + var vftblIUnknown = Marshal.PtrToStructure(vftblPtr.Vftbl); + vftblIUnknown.Release(comPtr); + } + } + +#if NETSTANDARD2_0 + return (TInterface)typeof(TInterface).GetHelperType().GetConstructor(new[] { typeof(IObjectReference) }).Invoke(new object[] { this }); +#else + return (TInterface)(object)new WinRT.IInspectable(this); +#endif + } + + public int TryAs(out ObjectReference objRef) => TryAs(GuidGenerator.GetIID(typeof(T)), out objRef); + + public virtual unsafe int TryAs(Guid iid, out ObjectReference objRef) + { + objRef = null; + ThrowIfDisposed(); + int hr = VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr); + if (hr >= 0) + { + objRef = ObjectReference.Attach(ref thatPtr); + } + return hr; + } + + public unsafe IObjectReference As(Guid iid) => As(iid); + + public T AsType() + { + ThrowIfDisposed(); + var ctor = typeof(T).GetConstructor(new[] { typeof(IObjectReference) }); + if (ctor != null) + { + return (T)ctor.Invoke(new[] { this }); + } + throw new InvalidOperationException("Target type is not a projected interface."); + } + + public IntPtr GetRef() + { + ThrowIfDisposed(); + AddRef(); + return ThisPtr; + } + + protected void ThrowIfDisposed() { if (disposed) { @@ -125,181 +139,196 @@ protected void ThrowIfDisposed() { if (disposed) throw new ObjectDisposedException("ObjectReference"); } - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { lock (_disposedLock) { - if (disposed) - { - return; - } - Release(); - disposed = true; + if (disposed) + { + return; + } +#if DEBUG + if (BreakOnDispose) + { + Debugger.Break(); + } +#endif + + Release(); + disposed = true; } - } - - internal bool Resurrect() - { + } + + public void Detach() + { lock (_disposedLock) { - if (!disposed) - { - return false; - } - disposed = false; - AddRef(); - GC.ReRegisterForFinalize(this); + disposed = true; + } + } + + internal bool Resurrect() + { + lock (_disposedLock) + { + if (!disposed) + { + return false; + } + disposed = false; + AddRef(); + GC.ReRegisterForFinalize(this); return true; - } - } - - protected virtual unsafe void AddRef() - { - VftblIUnknown.AddRef(ThisPtr); - } - - protected virtual unsafe void Release() - { - VftblIUnknown.Release(ThisPtr); - } - - internal unsafe bool IsReferenceToManagedObject - { - get - { - return VftblIUnknown.Equals(IUnknownVftbl.AbiToProjectionVftbl); - } - } - } - - public class ObjectReference : IObjectReference - { - private readonly T _vftbl; - public T Vftbl - { - get - { - ThrowIfDisposed(); - return _vftbl; - } - } - - public static ObjectReference Attach(ref IntPtr thisPtr) - { - if (thisPtr == IntPtr.Zero) - { - return null; - } - var obj = new ObjectReference(thisPtr); - thisPtr = IntPtr.Zero; - return obj; - } - - ObjectReference(IntPtr thisPtr, T vftblT) : - base(thisPtr) - { - _vftbl = vftblT; - } - - private protected ObjectReference(IntPtr thisPtr) : - this(thisPtr, GetVtable(thisPtr)) - { - } - - public static unsafe ObjectReference FromAbi(IntPtr thisPtr, T vftblT) - { - if (thisPtr == IntPtr.Zero) - { - return null; - } - var obj = new ObjectReference(thisPtr, vftblT); - obj.VftblIUnknown.AddRef(obj.ThisPtr); - return obj; - } - - public static ObjectReference FromAbi(IntPtr thisPtr) - { - if (thisPtr == IntPtr.Zero) - { - return null; - } - var vftblT = GetVtable(thisPtr); - return FromAbi(thisPtr, vftblT); - } - - private static unsafe T GetVtable(IntPtr thisPtr) - { - var vftblPtr = Unsafe.AsRef(thisPtr.ToPointer()); - T vftblT; - // With our vtable types, the generic vtables will have System.Delegate fields - // and the non-generic types will have only void* fields. - // On .NET 5, we can use RuntimeHelpers.IsReferenceorContainsReferences - // to disambiguate between generic and non-generic vtables since it's a JIT-time constant. - // Since it is a JIT time constant, this function will be branchless on .NET 5. - // On .NET Standard 2.0, the IsReferenceOrContainsReferences method does not exist, - // so we instead fall back to typeof(T).IsGenericType, which sadly is not a JIT-time constant. -#if NETSTANDARD2_0 - if (typeof(T).IsGenericType) -#else - if (RuntimeHelpers.IsReferenceOrContainsReferences()) -#endif - { - vftblT = (T)typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null, new[] { typeof(IntPtr) }, null).Invoke(new object[] { thisPtr }); - } - else - { - vftblT = Unsafe.AsRef(vftblPtr.Vftbl.ToPointer()); - } - return vftblT; - } - } - - internal class ObjectReferenceWithContext : ObjectReference - { - private static readonly Guid IID_ICallbackWithNoReentrancyToApplicationSTA = Guid.Parse("0A299774-3E4E-FC42-1D9D-72CEE105CA57"); - private readonly IntPtr _contextCallbackPtr; - - internal ObjectReferenceWithContext(IntPtr thisPtr, IntPtr contextCallbackPtr) - :base(thisPtr) - { - _contextCallbackPtr = contextCallbackPtr; - } - - protected override unsafe void Release() - { - ComCallData data = default; - IntPtr contextCallbackPtr = _contextCallbackPtr; - - var contextCallback = new ABI.WinRT.Interop.IContextCallback(ObjectReference.Attach(ref contextCallbackPtr)); - - contextCallback.ContextCallback(_ => - { - base.Release(); - return 0; - }, &data, IID_ICallbackWithNoReentrancyToApplicationSTA, 5); - } - - public override unsafe int TryAs(Guid iid, out ObjectReference objRef) - { - objRef = null; - int hr = VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr); - if (hr >= 0) - { - using (var contextCallbackReference = ObjectReference.FromAbi(_contextCallbackPtr)) - { - objRef = new ObjectReferenceWithContext(thatPtr, contextCallbackReference.GetRef()); - } - } - return hr; - } - } -} + } + } + + protected virtual unsafe void AddRef() + { + VftblIUnknown.AddRef(ThisPtr); + } + + protected virtual unsafe void Release() + { + VftblIUnknown.Release(ThisPtr); + } + + internal unsafe bool IsReferenceToManagedObject + { + get + { + return VftblIUnknown.Equals(IUnknownVftbl.AbiToProjectionVftbl); + } + } + } + + public class ObjectReference : IObjectReference + { + private readonly T _vftbl; + public T Vftbl + { + get + { + ThrowIfDisposed(); + return _vftbl; + } + } + + public static ObjectReference Attach(ref IntPtr thisPtr) + { + if (thisPtr == IntPtr.Zero) + { + return null; + } + var obj = new ObjectReference(thisPtr); + thisPtr = IntPtr.Zero; + return obj; + } + + ObjectReference(IntPtr thisPtr, T vftblT) : + base(thisPtr) + { + _vftbl = vftblT; + } + + private protected ObjectReference(IntPtr thisPtr) : + this(thisPtr, GetVtable(thisPtr)) + { + } + + public static unsafe ObjectReference FromAbi(IntPtr thisPtr, T vftblT) + { + if (thisPtr == IntPtr.Zero) + { + return null; + } + var obj = new ObjectReference(thisPtr, vftblT); + obj.VftblIUnknown.AddRef(obj.ThisPtr); + return obj; + } + + public static ObjectReference FromAbi(IntPtr thisPtr) + { + if (thisPtr == IntPtr.Zero) + { + return null; + } + var vftblT = GetVtable(thisPtr); + return FromAbi(thisPtr, vftblT); + } + + private static unsafe T GetVtable(IntPtr thisPtr) + { + var vftblPtr = Unsafe.AsRef(thisPtr.ToPointer()); + T vftblT; + // With our vtable types, the generic vtables will have System.Delegate fields + // and the non-generic types will have only void* fields. + // On .NET 5, we can use RuntimeHelpers.IsReferenceorContainsReferences + // to disambiguate between generic and non-generic vtables since it's a JIT-time constant. + // Since it is a JIT time constant, this function will be branchless on .NET 5. + // On .NET Standard 2.0, the IsReferenceOrContainsReferences method does not exist, + // so we instead fall back to typeof(T).IsGenericType, which sadly is not a JIT-time constant. +#if NETSTANDARD2_0 + if (typeof(T).IsGenericType) +#else + if (RuntimeHelpers.IsReferenceOrContainsReferences()) +#endif + { + vftblT = (T)typeof(T).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null, new[] { typeof(IntPtr) }, null).Invoke(new object[] { thisPtr }); + } + else + { + vftblT = Unsafe.AsRef(vftblPtr.Vftbl.ToPointer()); + } + return vftblT; + } + } + + internal class ObjectReferenceWithContext : ObjectReference + { + private static readonly Guid IID_ICallbackWithNoReentrancyToApplicationSTA = Guid.Parse("0A299774-3E4E-FC42-1D9D-72CEE105CA57"); + private readonly IntPtr _contextCallbackPtr; + + internal ObjectReferenceWithContext(IntPtr thisPtr, IntPtr contextCallbackPtr) + : base(thisPtr) + { + _contextCallbackPtr = contextCallbackPtr; + } + + protected override unsafe void Release() + { + ComCallData data = default; + IntPtr contextCallbackPtr = _contextCallbackPtr; + + var contextCallback = new ABI.WinRT.Interop.IContextCallback(ObjectReference.Attach(ref contextCallbackPtr)); + + contextCallback.ContextCallback(_ => + { + base.Release(); + return 0; + }, &data, IID_ICallbackWithNoReentrancyToApplicationSTA, 5); + } + + public override unsafe int TryAs(Guid iid, out ObjectReference objRef) + { + objRef = null; + int hr = VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr); + if (hr >= 0) + { + using (var contextCallbackReference = ObjectReference.FromAbi(_contextCallbackPtr)) + { + objRef = new ObjectReferenceWithContext(thatPtr, contextCallbackReference.GetRef()); + } + } + return hr; + } + } +} diff --git a/src/WinRT.Runtime/WinRT.Runtime.csproj b/src/WinRT.Runtime/WinRT.Runtime.csproj index 897d91af81..8275539c3f 100644 --- a/src/WinRT.Runtime/WinRT.Runtime.csproj +++ b/src/WinRT.Runtime/WinRT.Runtime.csproj @@ -21,6 +21,7 @@ Copyright (c) Microsoft Corporation. All rights reserved. true key.snk + win-x86;win-x64 diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index a66d76ac47..1a52a53bb3 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -1548,6 +1548,7 @@ ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr); auto cache_object = write_factory_cache_object(w, composable_type, class_type); auto default_interface_name = get_default_interface_name(w, class_type, false); auto default_interface_abi_name = get_default_interface_name(w, class_type); + bool finalizer_written = false; for (auto&& method : composable_type.MethodList()) { @@ -1597,28 +1598,47 @@ MarshalInspectable.DisposeAbi(ptr); } else { + if (!finalizer_written) + { + finalizer_written = true; + w.write(R"( +private ComWrappersHelper.ClassNative classNative; +~%() +{ +if (classNative.Inner != IntPtr.Zero) +{ +// detach inner in aggregation scenario +_inner.Detach(); +} +ComWrappersHelper.Cleanup(ref classNative); +} +)", + class_type.TypeName()); + } + auto platform_attribute = write_platform_attribute_temp(w, composable_type); w.write(R"( %% %(%)% { -object baseInspectable = this.GetType() != typeof(%) ? this : null; -IntPtr composed = %.%(%%baseInspectable, out IntPtr ptr); -using IObjectReference composedRef = ObjectReference.Attach(ref composed); +bool isAggregation = this.GetType() != typeof(%); +object baseInspectable = isAggregation ? this : null; +IntPtr composed = %.%(%%baseInspectable, out IntPtr inner); try { -_inner = ComWrappersSupport.GetObjectReferenceForInterface(ptr); +_inner = ComWrappersSupport.GetObjectReferenceForInterface(inner); if(baseInspectable == null) _inner = _inner.As(GuidGenerator.GetIID(typeof(%).GetHelperType())); _defaultLazy = new Lazy<%>(() => (%)new SingleInterfaceOptimizedObject(typeof(%), _inner)); _lazyInterfaces = new Dictionary() {% }; - -ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr); +classNative = new ComWrappersHelper.ClassNative(); +ComWrappersHelper.Init(ref classNative, isAggregation, this, composed, inner); } finally { -MarshalInspectable.DisposeAbi(ptr); +// prevent inner refcount from creating aggregation reference cycles +Marshal.Release(inner); } } )", From 798b90fe76a38579591c782ad73ec338f7dff045 Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Tue, 19 Jan 2021 12:31:36 -0800 Subject: [PATCH 2/3] feedback --- src/WinRT.Runtime/ComWrappersSupport.net5.cs | 4 +++- src/WinRT.Runtime/ObjectReference.cs | 8 ++++---- src/WinRT.Runtime/WinRT.Runtime.csproj | 1 - 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/WinRT.Runtime/ComWrappersSupport.net5.cs b/src/WinRT.Runtime/ComWrappersSupport.net5.cs index af93c41f88..07ba3254be 100644 --- a/src/WinRT.Runtime/ComWrappersSupport.net5.cs +++ b/src/WinRT.Runtime/ComWrappersSupport.net5.cs @@ -349,16 +349,18 @@ public unsafe static void Init( } } +#if DEBUG if (isAggregation) { Marshal.AddRef(newInstance); var outerRC = Marshal.Release(newInstance); - if (outerRC != 0) + if ((outerRC != 0) && System.Diagnostics.Debugger.IsAttached) { // In aggregation scenarios, at this point outer's refcount must be 0 System.Diagnostics.Debugger.Break(); } } +#endif } public static void Cleanup(ref ClassNative classNative) diff --git a/src/WinRT.Runtime/ObjectReference.cs b/src/WinRT.Runtime/ObjectReference.cs index eaec543686..53debab899 100644 --- a/src/WinRT.Runtime/ObjectReference.cs +++ b/src/WinRT.Runtime/ObjectReference.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -29,7 +28,7 @@ public IntPtr ThisPtr } #if DEBUG - private unsafe uint RefCount + private unsafe uint RefCount { get { @@ -157,9 +156,9 @@ protected virtual void Dispose(bool disposing) return; } #if DEBUG - if (BreakOnDispose) + if (BreakOnDispose && System.Diagnostics.Debugger.IsAttached) { - Debugger.Break(); + System.Diagnostics.Debugger.Break(); } #endif @@ -310,6 +309,7 @@ protected override unsafe void Release() var contextCallback = new ABI.WinRT.Interop.IContextCallback(ObjectReference.Attach(ref contextCallbackPtr)); + // Note: method index of 5 is ignored. See https://devblogs.microsoft.com/oldnewthing/20191128-00/?p=103157 contextCallback.ContextCallback(_ => { base.Release(); diff --git a/src/WinRT.Runtime/WinRT.Runtime.csproj b/src/WinRT.Runtime/WinRT.Runtime.csproj index 8275539c3f..897d91af81 100644 --- a/src/WinRT.Runtime/WinRT.Runtime.csproj +++ b/src/WinRT.Runtime/WinRT.Runtime.csproj @@ -21,7 +21,6 @@ Copyright (c) Microsoft Corporation. All rights reserved. true key.snk - win-x86;win-x64 From cf456883294070170dad92d6415d4a8a8cb5fe67 Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Wed, 17 Mar 2021 16:51:10 -0700 Subject: [PATCH 3/3] remove xaml overhead, add reference tracking --- src/Directory.Build.props | 2 +- src/Samples/WinUIDesktopSample/App.xaml.cs | 266 ++++++++- .../WinUIDesktopSample/LeakScenarios.cs | 538 +++++++++++++++++ src/Samples/WinUIDesktopSample/MainPage.xaml | 13 +- .../WinUIDesktopSample/MainPage.xaml.cs | 38 +- .../Properties/launchSettings.json | 14 +- .../WinUIDesktopSample.csproj | 1 + src/Tests/TestComponentCSharp/ComHelpers.h | 413 +++++++++++++ src/Tests/TestComponentCSharp/Composable.cpp | 15 - src/Tests/TestComponentCSharp/Composable.h | 20 - .../ReferenceTrackerRuntime.cpp | 555 ++++++++++++++++++ .../TestComponentCSharp.idl | 13 +- .../TestComponentCSharp.vcxproj | 7 +- .../TestComponentCSharp.vcxproj.filters | 7 +- .../TrackableComposable.cpp | 15 + .../TestComponentCSharp/TrackableComposable.h | 29 + .../TestComponentCSharp/TrackableSealed.cpp | 15 + .../TestComponentCSharp/TrackableSealed.h | 30 + .../UnitTest/TestComponentCSharp_Tests.cs | 6 +- src/build.cmd | 1 + 20 files changed, 1908 insertions(+), 90 deletions(-) create mode 100644 src/Samples/WinUIDesktopSample/LeakScenarios.cs create mode 100644 src/Tests/TestComponentCSharp/ComHelpers.h delete mode 100644 src/Tests/TestComponentCSharp/Composable.cpp delete mode 100644 src/Tests/TestComponentCSharp/Composable.h create mode 100644 src/Tests/TestComponentCSharp/ReferenceTrackerRuntime.cpp create mode 100644 src/Tests/TestComponentCSharp/TrackableComposable.cpp create mode 100644 src/Tests/TestComponentCSharp/TrackableComposable.h create mode 100644 src/Tests/TestComponentCSharp/TrackableSealed.cpp create mode 100644 src/Tests/TestComponentCSharp/TrackableSealed.h diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 61cc2aec14..eee4c5c30e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -8,7 +8,7 @@ high high - 3.0.0-preview3.201113.0 + 3.0.0-preview4.210210.4 full true preview diff --git a/src/Samples/WinUIDesktopSample/App.xaml.cs b/src/Samples/WinUIDesktopSample/App.xaml.cs index da82603199..c0c7922ceb 100644 --- a/src/Samples/WinUIDesktopSample/App.xaml.cs +++ b/src/Samples/WinUIDesktopSample/App.xaml.cs @@ -2,13 +2,17 @@ using System.Collections.Generic; using System.Configuration; using System.Data; +using System.Windows; using System.Linq; +using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; -using Windows.Web.Http; - +using Windows.Web.Http; +using System.Runtime.InteropServices; + namespace WinUIDesktopSample { /// @@ -16,35 +20,251 @@ namespace WinUIDesktopSample /// public partial class App : Application { + [DllImport("USER32.dll")] + static extern short GetKeyState(VirtualKeyStates nVirtKey); + enum VirtualKeyStates : int + { + VK_LBUTTON = 0x01, + VK_RBUTTON = 0x02, + VK_CANCEL = 0x03, + VK_MBUTTON = 0x04, + // + VK_XBUTTON1 = 0x05, + VK_XBUTTON2 = 0x06, + // + VK_BACK = 0x08, + VK_TAB = 0x09, + // + VK_CLEAR = 0x0C, + VK_RETURN = 0x0D, + // + VK_SHIFT = 0x10, + VK_CONTROL = 0x11, + VK_MENU = 0x12, + VK_PAUSE = 0x13, + VK_CAPITAL = 0x14, + // + VK_KANA = 0x15, + VK_HANGEUL = 0x15, /* old name - should be here for compatibility */ + VK_HANGUL = 0x15, + VK_JUNJA = 0x17, + VK_FINAL = 0x18, + VK_HANJA = 0x19, + VK_KANJI = 0x19, + // + VK_ESCAPE = 0x1B, + // + VK_CONVERT = 0x1C, + VK_NONCONVERT = 0x1D, + VK_ACCEPT = 0x1E, + VK_MODECHANGE = 0x1F, + // + VK_SPACE = 0x20, + VK_PRIOR = 0x21, + VK_NEXT = 0x22, + VK_END = 0x23, + VK_HOME = 0x24, + VK_LEFT = 0x25, + VK_UP = 0x26, + VK_RIGHT = 0x27, + VK_DOWN = 0x28, + VK_SELECT = 0x29, + VK_PRINT = 0x2A, + VK_EXECUTE = 0x2B, + VK_SNAPSHOT = 0x2C, + VK_INSERT = 0x2D, + VK_DELETE = 0x2E, + VK_HELP = 0x2F, + // + VK_LWIN = 0x5B, + VK_RWIN = 0x5C, + VK_APPS = 0x5D, + // + VK_SLEEP = 0x5F, + // + VK_NUMPAD0 = 0x60, + VK_NUMPAD1 = 0x61, + VK_NUMPAD2 = 0x62, + VK_NUMPAD3 = 0x63, + VK_NUMPAD4 = 0x64, + VK_NUMPAD5 = 0x65, + VK_NUMPAD6 = 0x66, + VK_NUMPAD7 = 0x67, + VK_NUMPAD8 = 0x68, + VK_NUMPAD9 = 0x69, + VK_MULTIPLY = 0x6A, + VK_ADD = 0x6B, + VK_SEPARATOR = 0x6C, + VK_SUBTRACT = 0x6D, + VK_DECIMAL = 0x6E, + VK_DIVIDE = 0x6F, + VK_F1 = 0x70, + VK_F2 = 0x71, + VK_F3 = 0x72, + VK_F4 = 0x73, + VK_F5 = 0x74, + VK_F6 = 0x75, + VK_F7 = 0x76, + VK_F8 = 0x77, + VK_F9 = 0x78, + VK_F10 = 0x79, + VK_F11 = 0x7A, + VK_F12 = 0x7B, + VK_F13 = 0x7C, + VK_F14 = 0x7D, + VK_F15 = 0x7E, + VK_F16 = 0x7F, + VK_F17 = 0x80, + VK_F18 = 0x81, + VK_F19 = 0x82, + VK_F20 = 0x83, + VK_F21 = 0x84, + VK_F22 = 0x85, + VK_F23 = 0x86, + VK_F24 = 0x87, + // + VK_NUMLOCK = 0x90, + VK_SCROLL = 0x91, + // + VK_OEM_NEC_EQUAL = 0x92, // '=' key on numpad + // + VK_OEM_FJ_JISHO = 0x92, // 'Dictionary' key + VK_OEM_FJ_MASSHOU = 0x93, // 'Unregister word' key + VK_OEM_FJ_TOUROKU = 0x94, // 'Register word' key + VK_OEM_FJ_LOYA = 0x95, // 'Left OYAYUBI' key + VK_OEM_FJ_ROYA = 0x96, // 'Right OYAYUBI' key + // + VK_LSHIFT = 0xA0, + VK_RSHIFT = 0xA1, + VK_LCONTROL = 0xA2, + VK_RCONTROL = 0xA3, + VK_LMENU = 0xA4, + VK_RMENU = 0xA5, + // + VK_BROWSER_BACK = 0xA6, + VK_BROWSER_FORWARD = 0xA7, + VK_BROWSER_REFRESH = 0xA8, + VK_BROWSER_STOP = 0xA9, + VK_BROWSER_SEARCH = 0xAA, + VK_BROWSER_FAVORITES = 0xAB, + VK_BROWSER_HOME = 0xAC, + // + VK_VOLUME_MUTE = 0xAD, + VK_VOLUME_DOWN = 0xAE, + VK_VOLUME_UP = 0xAF, + VK_MEDIA_NEXT_TRACK = 0xB0, + VK_MEDIA_PREV_TRACK = 0xB1, + VK_MEDIA_STOP = 0xB2, + VK_MEDIA_PLAY_PAUSE = 0xB3, + VK_LAUNCH_MAIL = 0xB4, + VK_LAUNCH_MEDIA_SELECT = 0xB5, + VK_LAUNCH_APP1 = 0xB6, + VK_LAUNCH_APP2 = 0xB7, + // + VK_OEM_1 = 0xBA, // ';:' for US + VK_OEM_PLUS = 0xBB, // '+' any country + VK_OEM_COMMA = 0xBC, // ',' any country + VK_OEM_MINUS = 0xBD, // '-' any country + VK_OEM_PERIOD = 0xBE, // '.' any country + VK_OEM_2 = 0xBF, // '/?' for US + VK_OEM_3 = 0xC0, // '`~' for US + // + VK_OEM_4 = 0xDB, // '[{' for US + VK_OEM_5 = 0xDC, // '\|' for US + VK_OEM_6 = 0xDD, // ']}' for US + VK_OEM_7 = 0xDE, // ''"' for US + VK_OEM_8 = 0xDF, + // + VK_OEM_AX = 0xE1, // 'AX' key on Japanese AX kbd + VK_OEM_102 = 0xE2, // "<>" or "\|" on RT 102-key kbd. + VK_ICO_HELP = 0xE3, // Help key on ICO + VK_ICO_00 = 0xE4, // 00 key on ICO + // + VK_PROCESSKEY = 0xE5, + // + VK_ICO_CLEAR = 0xE6, + // + VK_PACKET = 0xE7, + // + VK_OEM_RESET = 0xE9, + VK_OEM_JUMP = 0xEA, + VK_OEM_PA1 = 0xEB, + VK_OEM_PA2 = 0xEC, + VK_OEM_PA3 = 0xED, + VK_OEM_WSCTRL = 0xEE, + VK_OEM_CUSEL = 0xEF, + VK_OEM_ATTN = 0xF0, + VK_OEM_FINISH = 0xF1, + VK_OEM_COPY = 0xF2, + VK_OEM_AUTO = 0xF3, + VK_OEM_ENLW = 0xF4, + VK_OEM_BACKTAB = 0xF5, + // + VK_ATTN = 0xF6, + VK_CRSEL = 0xF7, + VK_EXSEL = 0xF8, + VK_EREOF = 0xF9, + VK_PLAY = 0xFA, + VK_ZOOM = 0xFB, + VK_NONAME = 0xFC, + VK_PA1 = 0xFD, + VK_OEM_CLEAR = 0xFE + } + private const int KEY_PRESSED = 0x8000; + public App() { } Window myWindow; + LeakScenarios scenarios = new LeakScenarios(); protected override void OnLaunched(LaunchActivatedEventArgs args) - { - var value = DependencyProperty.UnsetValue; - var button = new Button - { - Content = "Click me to load MainPage", - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }; - button.Click += Button_Click; - var window = new Microsoft.UI.Xaml.Window - { - Content = button - }; - - window.Activate(); - - myWindow = window; + { +#if USE_WINDOW + myWindow = new Microsoft.UI.Xaml.Window{ Content = new MainPage() }; + myWindow.Activate(); + return; +#endif + + Debug.Write( +@"Select test function on number keypad: + 1. Grid and handler without capture + 2. Check grid and handler without capture + 3. Grid and handler with capture + 4. Check grid and handler with capture + 5. Allocate various aggregates + 6. Check for leaks of aggregates +"); + var menu = new (VirtualKeyStates Key, Action Action)[] { + new (){ Key=VirtualKeyStates.VK_NUMPAD1, Action=WithoutCapture_Click }, + new (){ Key=VirtualKeyStates.VK_NUMPAD2, Action=WithoutCapture_Check }, + new (){ Key=VirtualKeyStates.VK_NUMPAD3, Action=WithCapture_Click }, + new (){ Key=VirtualKeyStates.VK_NUMPAD4, Action=WithCapture_Check }, + new (){ Key=VirtualKeyStates.VK_NUMPAD5, Action=Alloc_Click }, + new (){ Key=VirtualKeyStates.VK_NUMPAD6, Action=Check_Click }, + }; + while (true) + { + Thread.Sleep(50); + foreach (var item in menu) + { + var keyState = GetKeyState(item.Key); + if ((keyState & KEY_PRESSED) == KEY_PRESSED) + { + Thread.Sleep(500); // debounce + item.Action(); + } + } + } } - private void Button_Click(object sender, RoutedEventArgs e) - { - myWindow.Content = new MainPage(); - } + private void Report(string status) => Debug.WriteLine(status); + private void WithoutCapture_Click() => scenarios.WithoutCapture_Click(Report); + private void WithoutCapture_Check() => scenarios.WithoutCapture_Check(Report); + private void WithCapture_Click() => scenarios.WithCapture_Click(Report); + private void WithCapture_Check() => scenarios.WithCapture_Check(Report); + private void Alloc_Click() => scenarios.Alloc_Click(Report); + private void Check_Click() => scenarios.Check_Click(Report); } public static class Program diff --git a/src/Samples/WinUIDesktopSample/LeakScenarios.cs b/src/Samples/WinUIDesktopSample/LeakScenarios.cs new file mode 100644 index 0000000000..b142cf8730 --- /dev/null +++ b/src/Samples/WinUIDesktopSample/LeakScenarios.cs @@ -0,0 +1,538 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Animation; +using WinRT; + +namespace WinUIDesktopSample +{ + public class DerivedGrid : Grid + { + byte[] bytes = new byte[10_000_000]; + }; + + public class Derived : ARRPage + { + byte[] bytes = new byte[10_000_000]; + }; + + /// + /// Interaction logic for MainPage.xaml + /// + public class LeakScenarios + { + public static object factory; + public static System.Reflection.PropertyInfo propInfo; + public LeakScenarios() + { + var factoryType = Type.GetType("Microsoft.UI.Xaml.Controls.Page+_IPageFactory,WinUI"); + propInfo = factoryType.GetProperty("ThisPtr", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + factory = Activator.CreateInstance(factoryType); + } + + public unsafe static IntPtr CreatePage(IntPtr outer, out IntPtr inner) + { + IntPtr factoryPtr = (IntPtr)propInfo.GetValue(factory); + + IntPtr comp; + int hr = (*(delegate* unmanaged[Stdcall]**)factoryPtr)[6](factoryPtr, outer, out inner, out comp); + return comp; + } + + private WeakReference baseRef; + private WeakReference derivedRef; + private WeakReference gridRef; + private WeakReference derivedGridRef; + private List pressure = new List(); + + static WeakReference CreateObject(bool withCapture) + { + var obj = new Grid(); + var captured = withCapture ? obj : null; + obj.SizeChanged += + (object sender, SizeChangedEventArgs e) => Debug.Assert(sender == captured); + return new WeakReference(obj); + } + + private WeakReference withoutCapture; + public void WithoutCapture_Click(Action report) + { + report("Grid and handler without capture"); + + // Succeeds, as there's no cycle between object and event handler + withoutCapture = CreateObject(withCapture: false); + } + public void WithoutCapture_Check(Action report) + { + report("Check grid and handler without capture"); + GC.Collect(2, GCCollectionMode.Forced, true); + GC.WaitForPendingFinalizers(); + report(withoutCapture.IsAlive ? "Grid leaked" : "Grid collected"); + } + + private WeakReference withCapture; + public void WithCapture_Click(Action report) + { + report("Grid and handler with capture"); + // Fails due to cycle between object and event handler (unlike UWP) + withCapture = CreateObject(withCapture: true); + } + public void WithCapture_Check(Action report) + { + report("Check grid and handler with capture"); + GC.Collect(2, GCCollectionMode.Forced, true); + GC.WaitForPendingFinalizers(); + report(withCapture.IsAlive ? "Grid leaked" : "Grid collected"); + } + + public void Alloc_Click(Action report) + { + report("Allocate various aggregates"); + var page = new ARRPage(); + baseRef = new WeakReference(page); + var derived = new Derived(); + derivedRef = new WeakReference(derived); + var grid = new Grid(); + // Accessing _defaultLazy.Value will also cause a leak by QI-ing through _inner + // Attach/detach fix insufficient - need to protect all accesses to _inner via IReferenceTracker + // Even with backing out 2 AddRefs for _default, grid still leaks with event handler attached + //var ah = grid.ActualHeight; TODO: does this also leak? + grid.SizeChanged += (object sender, SizeChangedEventArgs e) => + { + // uncomment following line to create a reference cycle between grid and delegate, causing leak + if (sender == grid) + throw new NotImplementedException(); + }; + gridRef = new WeakReference(grid); + var derivedGrid = new DerivedGrid(); + derivedGridRef = new WeakReference(derivedGrid); + + report("(click Check Leaks repeatedly)"); + } + + public void Check_Click(Action report) + { + report("Check for leaks of aggregates"); + pressure.Add(new byte[10_000_000]); + for (int i = 0; i < 10; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + var baseStatus = baseRef.IsAlive ? "ARRPage leaked" : "ARRPage collected"; + var derivedStatus = derivedRef.IsAlive ? "Derived leaked" : "Derived collected"; + var gridStatus = gridRef.IsAlive ? "Grid leaked" : "Grid collected"; + var derivedGridStatus = derivedGridRef.IsAlive ? "DerivedGrid leaked" : "DerivedGrid collected"; + report(baseStatus + ", " + derivedStatus + ", " + gridStatus + ", " + derivedGridStatus); + } + } + + public struct VtblPtr + { + public IntPtr Vtbl; + } + + public class ARRPage : ICustomQueryInterface + { + private static readonly ComWrappers cw = new ARRComWrappers(); + private WinRT.IObjectReference _inner = null; // simulate cswinrt class + + private static ComWrappers GCW() + { + return cw; + } + + private static IntPtr CI(IntPtr outer, out IntPtr inner) + { + return LeakScenarios.CreatePage(outer, out inner); + } + + private struct ARRPageVtbl + { + public IntPtr QueryInterface; + public _AddRef AddRef; + public _Release Release; + } + + private delegate int _AddRef(IntPtr This); + private delegate int _Release(IntPtr This); + + private ComWrappersHelper.ClassNative classNative; + private readonly ARRPageVtbl vtable; + + public unsafe ARRPage() + { + ComWrappersHelper.Init(ref this.classNative, this, &GCW, &CI); + + Marshal.AddRef(classNative.Inner); + var rc = Marshal.Release(classNative.Inner); + + // Create and hold wrapper around Inner, as cswinrt base classes do, but + // do not increment COM refcount to prevent aggregation strong reference cycle + _inner = ComWrappersSupport.GetObjectReferenceForInterface(classNative.Inner); + Marshal.Release(classNative.Inner); + +#if USE_TRACKER + if(this.classNative.Tracker != null) + this.classNative.Tracker.AddRefFromTrackerSource(); +#endif + Marshal.AddRef(classNative.Inner); + rc = Marshal.Release(classNative.Inner); + + var inst = Marshal.PtrToStructure(this.classNative.Instance); + this.vtable = Marshal.PtrToStructure(inst.Vtbl); + } + + ~ARRPage() + { +#if USE_TRACKER + if (this.classNative.Tracker != null) + this.classNative.Tracker.ReleaseFromTrackerSource(); +#endif + Marshal.AddRef(classNative.Inner); + _inner.Dispose(); // DANGEROUS! + ComWrappersHelper.Cleanup(ref this.classNative); + } + + CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv) + { + if (this.classNative.Inner == IntPtr.Zero) + { + ppv = IntPtr.Zero; + return CustomQueryInterfaceResult.NotHandled; + } + + const int S_OK = 0; + const int E_NOINTERFACE = unchecked((int)0x80004002); + + int hr = Marshal.QueryInterface(this.classNative.Inner, ref iid, out ppv); + if (hr == S_OK) + { + return CustomQueryInterfaceResult.Handled; + } + + return hr == E_NOINTERFACE + ? CustomQueryInterfaceResult.NotHandled + : CustomQueryInterfaceResult.Failed; + } + } + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("11d3b13a-180e-4789-a8be-7712882893e6")] + interface IReferenceTracker + { + void ConnectFromTrackerSource(); + void DisconnectFromTrackerSource(); + void FindTrackerTargets(IntPtr callback); + void GetReferenceTrackerManager(out IntPtr value); + void AddRefFromTrackerSource(); + void ReleaseFromTrackerSource(); + void PegFromTrackerSource(); + }; + + class ComWrappersHelper + { + private static Guid IID_IReferenceTracker = new Guid("11d3b13a-180e-4789-a8be-7712882893e6"); + + [Flags] + public enum ReleaseFlags + { + None = 0, + Instance = 1, + Inner = 2, + ReferenceTracker = 4 + } + + public struct ClassNative + { + public ReleaseFlags Release; + public IntPtr Instance; + public IntPtr Inner; + public IntPtr ReferenceTracker; +#if USE_TRACKER + // doesn't seem to make a difference in breaking cycles/leaks + public IReferenceTracker Tracker; +#endif + } + + public unsafe static void Init( + ref ClassNative classNative, + object thisInstance, + delegate* GetComWrapper, + delegate* CreateInstance) + { +#if USE_TRACKER + classNative.Tracker = null; +#endif + + bool isAggregation = typeof(T) != thisInstance.GetType(); + + { + IntPtr outer = default; + if (isAggregation) + { + // Create a managed object wrapper (i.e. CCW) to act as the outer. + // Passing the CreateComInterfaceFlags.TrackerSupport can be done if + // IReferenceTracker support is possible. + // + // The outer is now owned in this context. + outer = GetComWrapper().GetOrCreateComInterfaceForObject(thisInstance, CreateComInterfaceFlags.TrackerSupport); + } + + // Create an instance of the COM/WinRT type. + // This is typically accomplished through a call to CoCreateInstance() or RoActivateInstance(). + // + // Ownership of the outer has been transferred to the new instance. + // Some APIs do return a non-null inner even with a null outer. This + // means ownership may now be owned in this context in either aggregation state. + classNative.Instance = CreateInstance(outer, out classNative.Inner); + } + + { + // Determine if the instance supports IReferenceTracker (e.g. WinUI). + // Acquiring this interface is useful for: + // 1) Providing an indication of what value to pass during RCW creation. + // 2) Informing the Reference Tracker runtime during non-aggregation + // scenarios about new references. + // + // If aggregation, query the inner since that will have the implementation + // otherwise the new instance will be used. Since the inner was composed + // it should answer immediately without going through the outer. Either way + // the reference count will go to the new instance. + IntPtr queryForTracker = isAggregation ? classNative.Inner : classNative.Instance; + int hr = Marshal.QueryInterface(queryForTracker, ref IID_IReferenceTracker, out classNative.ReferenceTracker); + if (hr != 0) + { + classNative.ReferenceTracker = default; + } + } + + { + // Determine flags needed for native object wrapper (i.e. RCW) creation. + var createObjectFlags = CreateObjectFlags.None; + IntPtr instanceToWrap = classNative.Instance; + + // Update flags if the native instance is being used in an aggregation scenario. + if (isAggregation) + { + // Indicate the scenario is aggregation + createObjectFlags |= (CreateObjectFlags)4; + + // The instance supports IReferenceTracker. + if (classNative.ReferenceTracker != default(IntPtr)) + { + createObjectFlags |= CreateObjectFlags.TrackerObject; + +#if USE_TRACKER + Marshal.AddRef(classNative.ReferenceTracker); + var rc= Marshal.Release(classNative.ReferenceTracker); + + static IReferenceTracker MarshalReferenceTracker(IntPtr ptr) => (IReferenceTracker)Marshal.GetObjectForIUnknown(ptr); + classNative.Tracker = MarshalReferenceTracker(classNative.ReferenceTracker); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + classNative.Tracker.AddRefFromTrackerSource(); + + Marshal.AddRef(classNative.ReferenceTracker); + rc = Marshal.Release(classNative.ReferenceTracker); +#else + // IReferenceTracker is not needed in aggregation scenarios. + // It is not needed because all QueryInterface() calls on an + // object are followed by an immediately release of the returned + // pointer - see below for details. + Marshal.Release(classNative.ReferenceTracker); +#endif + + // .NET 5 limitation + // + // For aggregated scenarios involving IReferenceTracker + // the API handles object cleanup. In .NET 5 the API + // didn't expose an option to handle this so we pass the inner + // in order to handle its lifetime. + // + // The API doesn't handle inner lifetime in any other scenario + // in the .NET 5 timeframe. + instanceToWrap = classNative.Inner; + } + } + + // Create a native object wrapper (i.e. RCW). + // + // Note this function will call QueryInterface() on the supplied instance, + // therefore it is important that the enclosing CCW forwards to its inner + // if aggregation is involved. This is typically accomplished through an + // implementation of ICustomQueryInterface. + GetComWrapper().GetOrRegisterObjectForComInstance(instanceToWrap, createObjectFlags, thisInstance); + } + + if (isAggregation) + { + // We release the instance here, but continue to use it since + // ownership was transferred to the API and it will guarantee + // the appropriate lifetime. + Marshal.Release(classNative.Instance); + } + else + { + // In non-aggregation scenarios where an inner exists and + // reference tracker is involved, we release the inner. + // + // .NET 5 limitation - see logic above. + if (classNative.Inner != default(IntPtr) && classNative.ReferenceTracker != default(IntPtr)) + { + Marshal.Release(classNative.Inner); + } + } + + // The following describes the valid local values to consider and details + // on their usage during the object's lifetime. + classNative.Release = ReleaseFlags.None; + if (isAggregation) + { + // Aggregation scenarios should avoid calling AddRef() on the + // newInstance value. This is due to the semantics of COM Aggregation + // and the fact that calling an AddRef() on the instance will increment + // the CCW which in turn will ensure it cannot be cleaned up. Calling + // AddRef() on the instance when passed to unmanagec code is correct + // since unmanaged code is required to call Release() at some point. + if (classNative.ReferenceTracker == default(IntPtr)) + { + // COM scenario + // The pointer to dispatch on for the instance. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // Instance + + // A pointer to the inner that should be queried for + // additional interfaces. Immediately after a QueryInterface() + // a Release() should be called on the returned pointer but the + // pointer can be retained and used. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.Inner; // Inner + } + else + { + // WinUI scenario + // The pointer to dispatch on for the instance. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // Instance + + // A pointer to the inner that should be queried for + // additional interfaces. Immediately after a QueryInterface() + // a Release() should be called on the returned pointer but the + // pointer can be retained and used. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // Inner + + // No longer needed. + // ** Never release. + classNative.Release |= ReleaseFlags.None; // ReferenceTracker + //classNative.Release |= ReleaseFlags.ReferenceTracker; + } + } + else + { + if (classNative.ReferenceTracker == default(IntPtr)) + { + // COM scenario + // The pointer to dispatch on for the instance. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.Instance; // Instance + } + else + { + // WinUI scenario + // The pointer to dispatch on for the instance. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.Instance; // Instance + + // This instance should be used to tell the + // Reference Tracker runtime whenever an AddRef()/Release() + // is performed on newInstance. + // ** Release in this class's Finalizer. + classNative.Release |= ReleaseFlags.ReferenceTracker; // ReferenceTracker + } + } + } + + public static void Cleanup(ref ClassNative classNative) + { + if (classNative.Release.HasFlag(ReleaseFlags.Inner)) + { + Marshal.Release(classNative.Inner); + } + if (classNative.Release.HasFlag(ReleaseFlags.Instance)) + { + Marshal.Release(classNative.Instance); + } + if (classNative.Release.HasFlag(ReleaseFlags.ReferenceTracker)) + { + Marshal.Release(classNative.ReferenceTracker); + } + } + } + + class ARRComWrappers : ComWrappers + { + [UnmanagedCallersOnly] + private static int GetIids(IntPtr thisPtr, IntPtr iidCount, IntPtr iids) + { + System.Diagnostics.Trace.WriteLine($"Calling IInspectable::{nameof(GetIids)}"); + throw new NotImplementedException(); + } + [UnmanagedCallersOnly] + private static int GetRuntimeClassName(IntPtr thisPtr, IntPtr className) + { + System.Diagnostics.Trace.WriteLine($"Calling IInspectable::{nameof(GetRuntimeClassName)}"); + throw new NotImplementedException(); + } + [UnmanagedCallersOnly] + private static int GetTrustLevel(IntPtr thisPtr, IntPtr trustLevel) + { + System.Diagnostics.Trace.WriteLine($"Calling IInspectable::{nameof(GetTrustLevel)}"); + throw new NotImplementedException(); + } + + protected unsafe override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) + { + IntPtr fpQueryInteface = default; + IntPtr fpAddRef = default; + IntPtr fpRelease = default; + ComWrappers.GetIUnknownImpl(out fpQueryInteface, out fpAddRef, out fpRelease); + + var vtblRaw = RuntimeHelpers.AllocateTypeAssociatedMemory(obj.GetType(), IntPtr.Size * 6); + var vtable = (IntPtr*)vtblRaw; + vtable[0] = fpQueryInteface; + vtable[1] = fpAddRef; + vtable[2] = fpRelease; + vtable[3] = new IntPtr((delegate* unmanaged)&GetIids); + vtable[4] = new IntPtr((delegate* unmanaged)&GetRuntimeClassName); + vtable[5] = new IntPtr((delegate* unmanaged)&GetTrustLevel); + + ComInterfaceEntry* entryRaw = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(obj.GetType(), sizeof(ComInterfaceEntry)); + entryRaw->IID = new Guid("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90"); + entryRaw->Vtable = vtblRaw; + count = 1; + + return entryRaw; + } + + protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flag) + { + throw new NotImplementedException(); + } + + protected override void ReleaseObjects(System.Collections.IEnumerable objects) + { + } + } +} diff --git a/src/Samples/WinUIDesktopSample/MainPage.xaml b/src/Samples/WinUIDesktopSample/MainPage.xaml index aa1fc22872..49961002b8 100644 --- a/src/Samples/WinUIDesktopSample/MainPage.xaml +++ b/src/Samples/WinUIDesktopSample/MainPage.xaml @@ -6,7 +6,14 @@ xmlns:local="WinUIDesktopSample" mc:Ignorable="d" Height="450" Width="800"> - - - + + + + + + + + + + diff --git a/src/Samples/WinUIDesktopSample/MainPage.xaml.cs b/src/Samples/WinUIDesktopSample/MainPage.xaml.cs index cafcff12cc..c1a3f79b43 100644 --- a/src/Samples/WinUIDesktopSample/MainPage.xaml.cs +++ b/src/Samples/WinUIDesktopSample/MainPage.xaml.cs @@ -1,11 +1,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Animation; +using WinRT; namespace WinUIDesktopSample { @@ -13,15 +18,26 @@ namespace WinUIDesktopSample /// Interaction logic for MainPage.xaml /// public partial class MainPage : Page - { - public MainPage() - { - InitializeComponent(); - this.AddHandler(UIElement.TappedEvent, new TappedEventHandler(Foo_PointerTapped), true /*handledEventsToo*/); - } - - private void Foo_PointerTapped(object sender, TappedRoutedEventArgs e) - { - } - } + { + LeakScenarios scenarios = new LeakScenarios(); + + public MainPage() + { + this.InitializeComponent(); +#if DEBUG + Build.Text = "DEBUG"; +#else + Build.Text = "RELEASE"; +#endif + } + + private void Report(string status) => Status.Text = status; + + private void WithoutCapture_Click(object sender, RoutedEventArgs e) => scenarios.WithoutCapture_Click(Report); + private void WithoutCapture_Check(object sender, RoutedEventArgs e) => scenarios.WithoutCapture_Check(Report); + private void WithCapture_Click(object sender, RoutedEventArgs e) => scenarios.WithCapture_Click(Report); + private void WithCapture_Check(object sender, RoutedEventArgs e) => scenarios.WithCapture_Check(Report); + private void Alloc_Click(object sender, RoutedEventArgs e) => scenarios.Alloc_Click(Report); + private void Check_Click(object sender, RoutedEventArgs e) => scenarios.Check_Click(Report); + } } diff --git a/src/Samples/WinUIDesktopSample/Properties/launchSettings.json b/src/Samples/WinUIDesktopSample/Properties/launchSettings.json index 08500f9d9c..78ff85c7ad 100644 --- a/src/Samples/WinUIDesktopSample/Properties/launchSettings.json +++ b/src/Samples/WinUIDesktopSample/Properties/launchSettings.json @@ -1,8 +1,8 @@ -{ - "profiles": { - "WinUIDesktopSample": { - "commandName": "Project", - "nativeDebugging": false - } - } +{ + "profiles": { + "WinUIDesktopSample": { + "commandName": "Project", + "nativeDebugging": true + } + } } \ No newline at end of file diff --git a/src/Samples/WinUIDesktopSample/WinUIDesktopSample.csproj b/src/Samples/WinUIDesktopSample/WinUIDesktopSample.csproj index fc78a792ce..fc281ff96b 100644 --- a/src/Samples/WinUIDesktopSample/WinUIDesktopSample.csproj +++ b/src/Samples/WinUIDesktopSample/WinUIDesktopSample.csproj @@ -11,6 +11,7 @@ --> DoNotGenerateOtherProviders x86;x64 + true diff --git a/src/Tests/TestComponentCSharp/ComHelpers.h b/src/Tests/TestComponentCSharp/ComHelpers.h new file mode 100644 index 0000000000..1a6bc9a371 --- /dev/null +++ b/src/Tests/TestComponentCSharp/ComHelpers.h @@ -0,0 +1,413 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Common macro for working in COM +#define RETURN_IF_FAILED(exp) { hr = exp; if (FAILED(hr)) { return hr; } } + +namespace Internal +{ + template + HRESULT __QueryInterfaceImpl( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject, + /* [in] */ I obj) + { + if (riid == __uuidof(I)) + { + *ppvObject = static_cast(obj); + } + else + { + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + return S_OK; + } + + template + HRESULT __QueryInterfaceImpl( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject, + /* [in] */ I1 i1, + /* [in] */ IR... remain) + { + if (riid == __uuidof(I1)) + { + *ppvObject = static_cast(i1); + return S_OK; + } + + return __QueryInterfaceImpl(riid, ppvObject, remain...); + } +} + +// Implementation of IUnknown operations +class UnknownImpl +{ +public: + UnknownImpl() = default; + virtual ~UnknownImpl() = default; + + UnknownImpl(const UnknownImpl&) = delete; + UnknownImpl& operator=(const UnknownImpl&) = delete; + + UnknownImpl(UnknownImpl&&) = default; + UnknownImpl& operator=(UnknownImpl&&) = default; + + template + HRESULT DoQueryInterface( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void** ppvObject, + /* [in] */ I1 i1, + /* [in] */ IR... remain) + { + if (ppvObject == nullptr) + return E_POINTER; + + if (riid == __uuidof(IUnknown)) + { + *ppvObject = static_cast(i1); + } + else + { + HRESULT hr = Internal::__QueryInterfaceImpl(riid, ppvObject, i1, remain...); + if (hr != S_OK) + return hr; + } + + DoAddRef(); + return S_OK; + } + + ULONG DoAddRef() + { + assert(_refCount > 0); + return (++_refCount); + } + + ULONG DoRelease() + { + assert(_refCount > 0); + ULONG c = (--_refCount); + if (c == 0) + delete this; + return c; + } + +protected: + ULONG GetRefCount() + { + return _refCount; + } + +private: + std::atomic _refCount = 1; +}; + +// Macro to use for defining ref counting impls +#define DEFINE_REF_COUNTING() \ + STDMETHOD_(ULONG, AddRef)(void) { return UnknownImpl::DoAddRef(); } \ + STDMETHOD_(ULONG, Release)(void) { return UnknownImpl::DoRelease(); } + +#if 0 +// Templated class factory +template +class ClassFactoryBasic : public UnknownImpl, public IClassFactory +{ +public: // static + static HRESULT Create(_In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) + { + try + { + auto cf = new ClassFactoryBasic(); + HRESULT hr = cf->QueryInterface(riid, ppv); + cf->Release(); + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + +public: // IClassFactory + STDMETHOD(CreateInstance)( + _In_opt_ IUnknown* pUnkOuter, + _In_ REFIID riid, + _COM_Outptr_ void** ppvObject) + { + if (pUnkOuter != nullptr) + return CLASS_E_NOAGGREGATION; + + try + { + auto ti = new T(); + HRESULT hr = ti->QueryInterface(riid, ppvObject); + ti->Release(); + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + + STDMETHOD(LockServer)(/* [in] */ BOOL fLock) + { + assert(false && "Not impl"); + return E_NOTIMPL; + } + +public: // IUnknown + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + return DoQueryInterface(riid, ppvObject, static_cast(this)); + } + + DEFINE_REF_COUNTING(); +}; + +// Templated class factory for aggregation +template +class ClassFactoryAggregate : public UnknownImpl, public IClassFactory +{ +public: // static + static HRESULT Create(_In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) + { + try + { + auto cf = new ClassFactoryAggregate(); + HRESULT hr = cf->QueryInterface(riid, ppv); + cf->Release(); + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + +public: // IClassFactory + STDMETHOD(CreateInstance)( + _In_opt_ IUnknown* pUnkOuter, + _In_ REFIID riid, + _COM_Outptr_ void** ppvObject) + { + if (pUnkOuter != nullptr && riid != IID_IUnknown) + return CLASS_E_NOAGGREGATION; + + try + { + auto ti = new T(pUnkOuter); + HRESULT hr = ti->QueryInterface(riid, ppvObject); + ti->Release(); + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + + STDMETHOD(LockServer)(/* [in] */ BOOL fLock) + { + assert(false && "Not impl"); + return E_NOTIMPL; + } + +public: // IUnknown + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + return DoQueryInterface(riid, ppvObject, static_cast(this)); + } + + DEFINE_REF_COUNTING(); +}; + +// Templated class factory +// Supplied type must have the following properties to use this template: +// 1) Have a static method with the following signature: +// - HRESULT RequestLicKey(BSTR *key); +// 2) Have a constructor that takes an optional BSTR value as the key +template +class ClassFactoryLicense : public UnknownImpl, public IClassFactory2 +{ +public: // static + static HRESULT Create(_In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) + { + try + { + auto cf = new ClassFactoryLicense(); + HRESULT hr = cf->QueryInterface(riid, ppv); + cf->Release(); + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + +public: // IClassFactory + STDMETHOD(CreateInstance)( + _In_opt_ IUnknown* pUnkOuter, + _In_ REFIID riid, + _COM_Outptr_ void** ppvObject) + { + return CreateInstanceLic(pUnkOuter, nullptr, riid, nullptr, ppvObject); + } + + STDMETHOD(LockServer)(/* [in] */ BOOL fLock) + { + assert(false && "Not impl"); + return E_NOTIMPL; + } + +public: // IClassFactory2 + STDMETHOD(GetLicInfo)( + /* [out][in] */ __RPC__inout LICINFO* pLicInfo) + { + // The CLR does not call this function and as such, + // returns an error. Note that this is explicitly illegal + // in a proper implementation of IClassFactory2. + return E_UNEXPECTED; + } + + STDMETHOD(RequestLicKey)( + /* [in] */ DWORD dwReserved, + /* [out] */ __RPC__deref_out_opt BSTR* pBstrKey) + { + if (dwReserved != 0) + return E_UNEXPECTED; + + return T::RequestLicKey(pBstrKey); + } + + STDMETHOD(CreateInstanceLic)( + /* [annotation][in] */ _In_opt_ IUnknown* pUnkOuter, + /* [annotation][in] */ _Reserved_ IUnknown* pUnkReserved, + /* [annotation][in] */ __RPC__in REFIID riid, + /* [annotation][in] */ __RPC__in BSTR bstrKey, + /* [annotation][iid_is][out] */ __RPC__deref_out_opt PVOID* ppvObj) + { + if (pUnkOuter != nullptr) + return CLASS_E_NOAGGREGATION; + + if (pUnkReserved != nullptr) + return E_UNEXPECTED; + + try + { + auto ti = new T(bstrKey); + HRESULT hr = ti->QueryInterface(riid, ppvObj); + ti->Release(); + return hr; + } + catch (HRESULT hr) + { + return hr; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + } + +public: // IUnknown + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + return DoQueryInterface(riid, ppvObject, static_cast(this), static_cast(this)); + } + + DEFINE_REF_COUNTING(); +}; +#endif + +template +struct ComSmartPtr +{ + T* p; + + ComSmartPtr() + : p{ nullptr } + { } + + ComSmartPtr(_In_ T* t) + : p{ t } + { + if (p != nullptr) + (void)p->AddRef(); + } + + ComSmartPtr(_In_ const ComSmartPtr&) = delete; + + ComSmartPtr(_Inout_ ComSmartPtr&& other) + : p{ other.Detach() } + { } + + ~ComSmartPtr() + { + Release(); + } + + ComSmartPtr& operator=(_In_ const ComSmartPtr&) = delete; + + ComSmartPtr& operator=(_Inout_ ComSmartPtr&& other) + { + Attach(other.Detach()); + return (*this); + } + + operator T* () + { + return p; + } + + T** operator&() + { + return &p; + } + + T* operator->() + { + return p; + } + + void Attach(_In_opt_ T* t) noexcept + { + Release(); + p = t; + } + + T* Detach() noexcept + { + T* tmp = p; + p = nullptr; + return tmp; + } + + void Release() noexcept + { + if (p != nullptr) + { + (void)p->Release(); + p = nullptr; + } + } +}; \ No newline at end of file diff --git a/src/Tests/TestComponentCSharp/Composable.cpp b/src/Tests/TestComponentCSharp/Composable.cpp deleted file mode 100644 index 3d0db1b755..0000000000 --- a/src/Tests/TestComponentCSharp/Composable.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "pch.h" -#include "Composable.h" -#include "Composable.g.cpp" - -namespace winrt::TestComponentCSharp::implementation -{ - winrt::event_token Composable::StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler) - { - return _stringChanged.add(handler); - } - void Composable::StringPropertyChanged(winrt::event_token const& token) noexcept - { - _stringChanged.remove(token); - } -} diff --git a/src/Tests/TestComponentCSharp/Composable.h b/src/Tests/TestComponentCSharp/Composable.h deleted file mode 100644 index 9c3dc00600..0000000000 --- a/src/Tests/TestComponentCSharp/Composable.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include "Composable.g.h" - -namespace winrt::TestComponentCSharp::implementation -{ - struct Composable : ComposableT - { - Composable() = default; - - winrt::event> _stringChanged; - winrt::event_token StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler); - void StringPropertyChanged(winrt::event_token const& token) noexcept; - }; -} -namespace winrt::TestComponentCSharp::factory_implementation -{ - struct Composable : ComposableT - { - }; -} diff --git a/src/Tests/TestComponentCSharp/ReferenceTrackerRuntime.cpp b/src/Tests/TestComponentCSharp/ReferenceTrackerRuntime.cpp new file mode 100644 index 0000000000..ea7d3d9a7a --- /dev/null +++ b/src/Tests/TestComponentCSharp/ReferenceTrackerRuntime.cpp @@ -0,0 +1,555 @@ +// Note: this source adapted from: +// https://github.com/dotnet/runtime/blob/master/src/tests/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/ReferenceTrackerRuntime.cpp + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +//#include +#include +#include +#include +#include + +namespace API +{ + // Documentation found at https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/ + class DECLSPEC_UUID("64bd43f8-bfee-4ec4-b7eb-2935158dae21") IReferenceTrackerTarget : public IUnknown + { + public: + STDMETHOD_(ULONG, AddRefFromReferenceTracker)() = 0; + STDMETHOD_(ULONG, ReleaseFromReferenceTracker)() = 0; + STDMETHOD(Peg)() = 0; + STDMETHOD(Unpeg)() = 0; + }; + + class DECLSPEC_UUID("29a71c6a-3c42-4416-a39d-e2825a07a773") IReferenceTrackerHost : public IUnknown + { + public: + STDMETHOD(DisconnectUnusedReferenceSources)(_In_ DWORD dwFlags) = 0; + STDMETHOD(ReleaseDisconnectedReferenceSources)() = 0; + STDMETHOD(NotifyEndOfReferenceTrackingOnThread)() = 0; + STDMETHOD(GetTrackerTarget)(_In_ IUnknown * obj, _Outptr_ IReferenceTrackerTarget * *ppNewReference) = 0; + STDMETHOD(AddMemoryPressure)(_In_ UINT64 bytesAllocated) = 0; + STDMETHOD(RemoveMemoryPressure)(_In_ UINT64 bytesAllocated) = 0; + }; + + class DECLSPEC_UUID("3cf184b4-7ccb-4dda-8455-7e6ce99a3298") IReferenceTrackerManager : public IUnknown + { + public: + STDMETHOD(ReferenceTrackingStarted)() = 0; + STDMETHOD(FindTrackerTargetsCompleted)(_In_ BOOL bWalkFailed) = 0; + STDMETHOD(ReferenceTrackingCompleted)() = 0; + STDMETHOD(SetReferenceTrackerHost)(_In_ IReferenceTrackerHost * pCLRServices) = 0; + }; + + class DECLSPEC_UUID("04b3486c-4687-4229-8d14-505ab584dd88") IFindReferenceTargetsCallback : public IUnknown + { + public: + STDMETHOD(FoundTrackerTarget)(_In_ IReferenceTrackerTarget * target) = 0; + }; + + class DECLSPEC_UUID("11d3b13a-180e-4789-a8be-7712882893e6") IReferenceTracker : public IUnknown + { + public: + STDMETHOD(ConnectFromTrackerSource)() = 0; + STDMETHOD(DisconnectFromTrackerSource)() = 0; + STDMETHOD(FindTrackerTargets)(_In_ IFindReferenceTargetsCallback * pCallback) = 0; + STDMETHOD(GetReferenceTrackerManager)(_Outptr_ IReferenceTrackerManager * *ppTrackerManager) = 0; + STDMETHOD(AddRefFromTrackerSource)() = 0; + STDMETHOD(ReleaseFromTrackerSource)() = 0; + STDMETHOD(PegFromTrackerSource)() = 0; + }; +} + +namespace +{ + // Testing types + struct DECLSPEC_UUID("447BB9ED-DA48-4ABC-8963-5BB5C3E0AA09") ITest : public IUnknown + { + STDMETHOD(SetValue)(int i) = 0; + }; + + struct DECLSPEC_UUID("42951130-245C-485E-B60B-4ED4254256F8") ITrackerObject : public IUnknown + { + STDMETHOD(AddObjectRef)(_In_ IUnknown * c, _Out_ int* id) = 0; + STDMETHOD(DropObjectRef)(_In_ int id) = 0; + }; + + struct TrackerObject : public IUnknown, public UnknownImpl + { + static std::atomic AllocationCount; + + static const int32_t DisableTrackedCount = -1; + static const int32_t EnableTrackedCount = 0; + static std::atomic TrackedAllocationCount; + + TrackerObject(_In_ size_t id, _In_opt_ IUnknown* pUnkOuter) + : _outer{ pUnkOuter == nullptr ? static_cast(this) : pUnkOuter } + , _impl{ id, _outer } + { + ++AllocationCount; + + if (TrackedAllocationCount != DisableTrackedCount) + ++TrackedAllocationCount; + } + + ~TrackerObject() + { + // There is a cleanup race when tracking is enabled. + // It is possible previously allocated objects could be + // cleaned up during alloc tracking scenarios - these can be + // ignored. + // + // See the locking around the tracking scenarios in the + // managed P/Invoke usage. + if (TrackedAllocationCount > 0) + --TrackedAllocationCount; + + --AllocationCount; + } + + HRESULT TogglePeg(_In_ bool shouldPeg) + { + HRESULT hr; + + auto curr = std::begin(_impl._elements); + while (curr != std::end(_impl._elements)) + { + ComSmartPtr mowMaybe; + if (S_OK == curr->second->QueryInterface(&mowMaybe)) + { + if (shouldPeg) + { + RETURN_IF_FAILED(mowMaybe->Peg()); + } + else + { + RETURN_IF_FAILED(mowMaybe->Unpeg()); + } + } + ++curr; + } + + // Handle the case for aggregation + // + // Pegging occurs during a GC. We can't QI for this during + // a GC because the COM scenario would fallback to + // ICustomQueryInterface (i.e. managed code). + if (_impl._outerRefTrackerTarget) + { + ComSmartPtr thisTgtMaybe; + if (S_OK == _outer->QueryInterface(&thisTgtMaybe)) + { + if (shouldPeg) + { + RETURN_IF_FAILED(thisTgtMaybe->Peg()); + } + else + { + RETURN_IF_FAILED(thisTgtMaybe->Unpeg()); + } + } + } + + return S_OK; + } + + HRESULT DisconnectFromReferenceTrackerRuntime() + { + HRESULT hr; + + RETURN_IF_FAILED(TogglePeg(/* should peg */ false)); + + // Handle the case for aggregation in the release case. + if (_impl._outerRefTrackerTarget) + { + ComSmartPtr thisTgtMaybe; + if (S_OK == _outer->QueryInterface(&thisTgtMaybe)) + RETURN_IF_FAILED(thisTgtMaybe->ReleaseFromReferenceTracker()); + } + + return S_OK; + } + + struct TrackerObjectImpl : public ITrackerObject, public API::IReferenceTracker + { + IUnknown* _implOuter; + bool _outerRefTrackerTarget; + const size_t _id; + std::atomic _trackerSourceCount; + bool _connected; + std::atomic _elementId; + std::unordered_map> _elements; + + TrackerObjectImpl(_In_ size_t id, _In_ IUnknown* pUnkOuter) + : _implOuter{ pUnkOuter } + , _outerRefTrackerTarget{ false } + , _id{ id } + , _trackerSourceCount{ 0 } + , _connected{ false } + , _elementId{ 1 } + { + // Check if we are aggregating with a tracker target + ComSmartPtr tgt; + if (SUCCEEDED(_implOuter->QueryInterface(&tgt))) + { + _outerRefTrackerTarget = true; + (void)tgt->AddRefFromReferenceTracker(); + if (FAILED(tgt->Peg())) + { + throw std::exception{ "Peg failure" }; + } + } + } + + STDMETHOD(AddObjectRef)(_In_ IUnknown* c, _Out_ int* id) + { + assert(c != nullptr && id != nullptr); + + try + { + *id = _elementId; + if (!_elements.insert(std::make_pair(*id, ComSmartPtr{ c })).second) + return S_FALSE; + + _elementId++; + } + catch (const std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + + ComSmartPtr mowMaybe; + if (S_OK == c->QueryInterface(&mowMaybe)) + (void)mowMaybe->AddRefFromReferenceTracker(); + + return S_OK; + } + + STDMETHOD(DropObjectRef)(_In_ int id) + { + auto iter = _elements.find(id); + if (iter == std::end(_elements)) + return S_FALSE; + + ComSmartPtr mowMaybe; + if (S_OK == iter->second->QueryInterface(&mowMaybe)) + { + (void)mowMaybe->ReleaseFromReferenceTracker(); + (void)mowMaybe->Unpeg(); + } + + _elements.erase(iter); + + return S_OK; + } + + STDMETHOD(ConnectFromTrackerSource)(); + STDMETHOD(DisconnectFromTrackerSource)(); + STDMETHOD(FindTrackerTargets)(_In_ API::IFindReferenceTargetsCallback* pCallback); + STDMETHOD(GetReferenceTrackerManager)(_Outptr_ API::IReferenceTrackerManager** ppTrackerManager); + STDMETHOD(AddRefFromTrackerSource)(); + STDMETHOD(ReleaseFromTrackerSource)(); + STDMETHOD(PegFromTrackerSource)(); + + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + return _implOuter->QueryInterface(riid, ppvObject); + } + STDMETHOD_(ULONG, AddRef)(void) + { + return _implOuter->AddRef(); + } + STDMETHOD_(ULONG, Release)(void) + { + return _implOuter->Release(); + } + }; + + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + if (ppvObject == nullptr) + return E_POINTER; + + IUnknown* tgt; + + // Aggregation implementation. + if (riid == IID_IUnknown) + { + tgt = static_cast(this); + } + else + { + // Send non-IUnknown queries to the implementation. + if (riid == __uuidof(API::IReferenceTracker)) + { + tgt = static_cast(&_impl); + } + else if (riid == __uuidof(ITrackerObject)) + { + tgt = static_cast(&_impl); + } + else + { + *ppvObject = nullptr; + return E_NOINTERFACE; + } + } + + (void)tgt->AddRef(); + *ppvObject = tgt; + return S_OK; + } + + DEFINE_REF_COUNTING(); + + IUnknown* _outer; + TrackerObjectImpl _impl; + }; + + std::atomic TrackerObject::AllocationCount{}; + std::atomic TrackerObject::TrackedAllocationCount{ TrackerObject::DisableTrackedCount }; + std::atomic CurrentObjectId{}; + + class TrackerRuntimeManagerImpl : public API::IReferenceTrackerManager + { + ComSmartPtr _runtimeServices; + std::list> _objects; + + public: + ITrackerObject* RecordObject(_In_ TrackerObject* obj, _Outptr_ IUnknown** inner) + { + _objects.push_back(ComSmartPtr{ obj }); + + if (_runtimeServices != nullptr) + _runtimeServices->AddMemoryPressure(sizeof(TrackerObject)); + + // Perform a QI to get the proper identity. + (void)obj->QueryInterface(IID_IUnknown, (void**)inner); + + // Get the default interface. + ITrackerObject* type; + (void)obj->QueryInterface(__uuidof(ITrackerObject), (void**)&type); + + return type; + } + + void ReleaseObjects() + { + // Unpeg all instances + for (auto& i : _objects) + (void)i->DisconnectFromReferenceTrackerRuntime(); + + size_t count = _objects.size(); + _objects.clear(); + if (_runtimeServices != nullptr) + _runtimeServices->RemoveMemoryPressure(sizeof(TrackerObject) * count); + } + + HRESULT NotifyEndOfReferenceTrackingOnThread() + { + if (_runtimeServices != nullptr) + return _runtimeServices->NotifyEndOfReferenceTrackingOnThread(); + + return S_OK; + } + + public: // IReferenceTrackerManager + STDMETHOD(ReferenceTrackingStarted)() + { + // Unpeg all instances + for (auto& i : _objects) + i->TogglePeg(/* should peg */ false); + + return S_OK; + } + + STDMETHOD(FindTrackerTargetsCompleted)(_In_ BOOL bWalkFailed) + { + // Verify and ensure all connected types are pegged + for (auto& i : _objects) + i->TogglePeg(/* should peg */ true); + + return S_OK; + } + + STDMETHOD(ReferenceTrackingCompleted)() + { + return S_OK; + } + + STDMETHOD(SetReferenceTrackerHost)(_In_ API::IReferenceTrackerHost* pHostServices) + { + assert(pHostServices != nullptr); + return pHostServices->QueryInterface(&_runtimeServices); + } + + // Lifetime maintained by stack - we don't care about ref counts + STDMETHOD_(ULONG, AddRef)() { return 1; } + STDMETHOD_(ULONG, Release)() { return 1; } + + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + if (ppvObject == nullptr) + return E_POINTER; + + if (IsEqualIID(riid, __uuidof(API::IReferenceTrackerManager))) + { + *ppvObject = static_cast(this); + } + else if (IsEqualIID(riid, IID_IUnknown)) + { + *ppvObject = static_cast(this); + } + else + { + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; + } + }; + + TrackerRuntimeManagerImpl TrackerRuntimeManager; + + HRESULT STDMETHODCALLTYPE TrackerObject::TrackerObjectImpl::ConnectFromTrackerSource() + { + _connected = true; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE TrackerObject::TrackerObjectImpl::DisconnectFromTrackerSource() + { + _connected = false; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE TrackerObject::TrackerObjectImpl::FindTrackerTargets(_In_ API::IFindReferenceTargetsCallback* pCallback) + { + assert(pCallback != nullptr); + + ComSmartPtr mowMaybe; + for (auto& e : _elements) + { + if (S_OK == e.second->QueryInterface(&mowMaybe)) + { + (void)pCallback->FoundTrackerTarget(mowMaybe.p); + mowMaybe.Release(); + } + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE TrackerObject::TrackerObjectImpl::GetReferenceTrackerManager(_Outptr_ API::IReferenceTrackerManager** ppTrackerManager) + { + assert(ppTrackerManager != nullptr); + return TrackerRuntimeManager.QueryInterface(__uuidof(API::IReferenceTrackerManager), (void**)ppTrackerManager); + } + + HRESULT STDMETHODCALLTYPE TrackerObject::TrackerObjectImpl::AddRefFromTrackerSource() + { + assert(0 <= _trackerSourceCount); + ++_trackerSourceCount; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE TrackerObject::TrackerObjectImpl::ReleaseFromTrackerSource() + { + assert(0 < _trackerSourceCount); + --_trackerSourceCount; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE TrackerObject::TrackerObjectImpl::PegFromTrackerSource() + { + /* Not used by runtime */ + return E_NOTIMPL; + } +} + +// Create external object +//ITrackerObject * CreateTrackerObject_SkipTrackerRuntime() +//{ +// auto obj = new TrackerObject{ static_cast(-1), nullptr }; +// return &obj->_impl; +//} + +//ITrackerObject * +void* +CreateTrackerObject(IUnknown * outer, IUnknown * *inner) +{ + ComSmartPtr obj; + obj.Attach(new TrackerObject{ CurrentObjectId++, outer }); + + return TrackerRuntimeManager.RecordObject(obj, inner); +} +#if 0 + +extern "C" DLL_EXPORT void STDMETHODCALLTYPE StartTrackerObjectAllocationCount_Unsafe() +{ + TrackerObject::TrackedAllocationCount = TrackerObject::EnableTrackedCount; +} + +extern "C" DLL_EXPORT int32_t STDMETHODCALLTYPE StopTrackerObjectAllocationCount_Unsafe() +{ + int32_t count = TrackerObject::TrackedAllocationCount; + TrackerObject::TrackedAllocationCount = TrackerObject::DisableTrackedCount; + return count; +} + +// Release the reference on all internally held tracker objects +extern "C" DLL_EXPORT void STDMETHODCALLTYPE ReleaseAllTrackerObjects() +{ + TrackerRuntimeManager.ReleaseObjects(); +} + +extern "C" DLL_EXPORT int STDMETHODCALLTYPE Trigger_NotifyEndOfReferenceTrackingOnThread() +{ + return TrackerRuntimeManager.NotifyEndOfReferenceTrackingOnThread(); +} + +extern "C" DLL_EXPORT int STDMETHODCALLTYPE UpdateTestObjectAsIUnknown(IUnknown * obj, int i, IUnknown * *out) +{ + if (obj == nullptr) + return E_POINTER; + + HRESULT hr; + ComSmartPtr testObj; + RETURN_IF_FAILED(obj->QueryInterface(&testObj)); + RETURN_IF_FAILED(testObj->SetValue(i)); + + *out = testObj.Detach(); + return S_OK; +} + +extern "C" DLL_EXPORT int STDMETHODCALLTYPE UpdateTestObjectAsIDispatch(IDispatch * obj, int i, IDispatch * *out) +{ + if (obj == nullptr) + return E_POINTER; + + return UpdateTestObjectAsIUnknown(obj, i, (IUnknown**)out); +} + +extern "C" DLL_EXPORT int STDMETHODCALLTYPE UpdateTestObjectAsInterface(ITest * obj, int i, ITest * *out) +{ + if (obj == nullptr) + return E_POINTER; + + HRESULT hr; + RETURN_IF_FAILED(obj->SetValue(i)); + + obj->AddRef(); + *out = obj; + return S_OK; +} +#endif \ No newline at end of file diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.idl b/src/Tests/TestComponentCSharp/TestComponentCSharp.idl index 7758daf5da..f6d2ec19a9 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.idl +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.idl @@ -369,10 +369,17 @@ namespace TestComponentCSharp } [default_interface] - unsealed runtimeclass Composable + runtimeclass TrackableSealed { - Composable(); - event Windows.Foundation.TypedEventHandler StringPropertyChanged; + TrackableSealed(); + event Windows.Foundation.TypedEventHandler StringPropertyChanged; + } + + [default_interface] + unsealed runtimeclass TrackableComposable + { + TrackableComposable(); + event Windows.Foundation.TypedEventHandler StringPropertyChanged; } // SupportedOSPlatform warning tests diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj index 127af46e77..f03e7a071c 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj @@ -63,7 +63,6 @@ - @@ -71,12 +70,13 @@ + + - Create @@ -86,6 +86,9 @@ + + + diff --git a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters index 0822856867..9bc5462a66 100644 --- a/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters +++ b/src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj.filters @@ -15,7 +15,9 @@ - + + + @@ -24,7 +26,8 @@ - + + diff --git a/src/Tests/TestComponentCSharp/TrackableComposable.cpp b/src/Tests/TestComponentCSharp/TrackableComposable.cpp new file mode 100644 index 0000000000..cda7aae2d5 --- /dev/null +++ b/src/Tests/TestComponentCSharp/TrackableComposable.cpp @@ -0,0 +1,15 @@ +#include "pch.h" +#include "TrackableComposable.h" +#include "TrackableComposable.g.cpp" + +namespace winrt::TestComponentCSharp::implementation +{ + winrt::event_token TrackableComposable::StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler) + { + return _stringChanged.add(handler); + } + void TrackableComposable::StringPropertyChanged(winrt::event_token const& token) noexcept + { + _stringChanged.remove(token); + } +} diff --git a/src/Tests/TestComponentCSharp/TrackableComposable.h b/src/Tests/TestComponentCSharp/TrackableComposable.h new file mode 100644 index 0000000000..d59b3bf7b9 --- /dev/null +++ b/src/Tests/TestComponentCSharp/TrackableComposable.h @@ -0,0 +1,29 @@ +#pragma once +#include "TrackableComposable.g.h" + +extern void* CreateTrackerObject(_In_opt_ IUnknown* outer, _Outptr_ IUnknown** inner); + +namespace winrt::TestComponentCSharp::implementation +{ + struct TrackableComposable : TrackableComposableT + { + TrackableComposable() = default; + + winrt::event> _stringChanged; + winrt::event_token StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler); + void StringPropertyChanged(winrt::event_token const& token) noexcept; + }; +} +namespace winrt::TestComponentCSharp::factory_implementation +{ + struct TrackableComposable : TrackableComposableT + { + auto ActivateInstance() const + { + auto obj = make(); + IUnknown* inner; + auto ret = CreateTrackerObject((IUnknown*)winrt::detach_abi(obj), &inner); + return ret; + } + }; +} diff --git a/src/Tests/TestComponentCSharp/TrackableSealed.cpp b/src/Tests/TestComponentCSharp/TrackableSealed.cpp new file mode 100644 index 0000000000..8daae3d11d --- /dev/null +++ b/src/Tests/TestComponentCSharp/TrackableSealed.cpp @@ -0,0 +1,15 @@ +#include "pch.h" +#include "TrackableSealed.h" +#include "TrackableSealed.g.cpp" + +namespace winrt::TestComponentCSharp::implementation +{ + winrt::event_token TrackableSealed::StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler) + { + return _stringChanged.add(handler); + } + void TrackableSealed::StringPropertyChanged(winrt::event_token const& token) noexcept + { + _stringChanged.remove(token); + } +} diff --git a/src/Tests/TestComponentCSharp/TrackableSealed.h b/src/Tests/TestComponentCSharp/TrackableSealed.h new file mode 100644 index 0000000000..8d8033eda4 --- /dev/null +++ b/src/Tests/TestComponentCSharp/TrackableSealed.h @@ -0,0 +1,30 @@ +#pragma once +#include "TrackableSealed.g.h" +#include "TrackableSealed.g.h" + +extern void* CreateTrackerObject(_In_opt_ IUnknown* outer, _Outptr_ IUnknown** inner); + +namespace winrt::TestComponentCSharp::implementation +{ + struct TrackableSealed : TrackableSealedT + { + TrackableSealed() = default; + + winrt::event> _stringChanged; + winrt::event_token StringPropertyChanged(Windows::Foundation::TypedEventHandler const& handler); + void StringPropertyChanged(winrt::event_token const& token) noexcept; + }; +} +namespace winrt::TestComponentCSharp::factory_implementation +{ + struct TrackableSealed : TrackableSealedT + { + auto ActivateInstance() const + { + auto obj = make(); + IUnknown* inner; + auto ret = CreateTrackerObject((IUnknown*)winrt::detach_abi(obj), &inner); + return ret; + } + }; +} diff --git a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs index 00531cc338..77a8bb91f2 100644 --- a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs +++ b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs @@ -2322,7 +2322,7 @@ static bool IsCollectible(WeakReference weakReference) Assert.True(IsCollectible(nonComposableNoDelegate)); // Test collection of composable without self-referential delegate - var composableNoDelegate = CreateObject(); + var composableNoDelegate = CreateObject(); Assert.True(IsCollectible(composableNoDelegate)); // Test collection of non-composable with self-referential delegate @@ -2333,8 +2333,8 @@ static bool IsCollectible(WeakReference weakReference) Assert.True(!IsCollectible(nonComposableWithDelegate)); // Test collection of composable with self-referential delegate - var composableWithDelegate = CreateObject((obj) => { - obj.StringPropertyChanged += (Composable sender, string value) => Assert.Equal(sender, obj); + var composableWithDelegate = CreateObject((obj) => { + obj.StringPropertyChanged += (TrackableComposable sender, string value) => Assert.Equal(sender, obj); }); // TODO: implement IReferenceTracker to prevent delegate creating cycle Assert.True(!IsCollectible(composableWithDelegate)); diff --git a/src/build.cmd b/src/build.cmd index ef10726174..621993b849 100644 --- a/src/build.cmd +++ b/src/build.cmd @@ -1,6 +1,7 @@ @echo off if /i "%cswinrt_echo%" == "on" @echo on +rem set CsWinRTNet5SdkVersion=6.0.100-preview.2.21155.3 set CsWinRTNet5SdkVersion=5.0.100 set this_dir=%~dp0