diff --git a/src/WinRT.Runtime/ApiCompatBaseline.net5.0.txt b/src/WinRT.Runtime/ApiCompatBaseline.net5.0.txt index e2a288536b..9c4fa26f0c 100644 --- a/src/WinRT.Runtime/ApiCompatBaseline.net5.0.txt +++ b/src/WinRT.Runtime/ApiCompatBaseline.net5.0.txt @@ -1,2 +1,4 @@ +Compat issues with assembly WinRT.Runtime: MembersMustExist : Member 'public WinRT.MarshalString.HStringHeader WinRT.MarshalString.HStringHeader WinRT.MarshalString._header' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'WinRT.MarshalString.HStringHeader' does not exist in the implementation but it does exist in the contract. +Total Issues: 2 diff --git a/src/WinRT.Runtime/ComWrappersSupport.net5.cs b/src/WinRT.Runtime/ComWrappersSupport.net5.cs index 0696e2daa8..003782ec73 100644 --- a/src/WinRT.Runtime/ComWrappersSupport.net5.cs +++ b/src/WinRT.Runtime/ComWrappersSupport.net5.cs @@ -128,7 +128,11 @@ 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) { @@ -190,6 +194,155 @@ private static Func CreateFactoryForImplementationType(str Expression.Property(parms[0], nameof(WinRT.IInspectable.ObjRef))), parms).Compile(); } + } + + public class ComWrappersHelper + { + public unsafe static void Init( + bool isAggregation, + object thisInstance, + IntPtr newInstance, + IntPtr inner, + out IObjectReference objRef) + { + objRef = ComWrappersSupport.GetObjectReferenceForInterface(isAggregation ? inner : newInstance); + + IntPtr referenceTracker; + { + // 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. + Guid iid = typeof(IReferenceTrackerVftbl).GUID; + int hr = Marshal.QueryInterface(objRef.ThisPtr, ref iid, out referenceTracker); + if (hr != 0) + { + referenceTracker = default; + } + } + + { + // Determine flags needed for native object wrapper (i.e. RCW) creation. + var createObjectFlags = CreateObjectFlags.None; + IntPtr instanceToWrap = newInstance; + + // The instance supports IReferenceTracker. + if (referenceTracker != default(IntPtr)) + { + createObjectFlags |= CreateObjectFlags.TrackerObject; + } + + // 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 (referenceTracker != default(IntPtr)) + { + // 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(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 = 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); + } + + // The following sets up the object reference to correctly handle AddRefs and releases + // based on the scenario. + 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. + + // 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. This is determined by the + // IsAggregated and PreventReleaseOnDispose properties on IObjectReference. + objRef.IsAggregated = true; + // In WinUI scenario don't release inner + objRef.PreventReleaseOnDispose = referenceTracker != default(IntPtr); + } + else + { + if (referenceTracker != default(IntPtr)) + { + // WinUI scenario + // This instance should be used to tell the + // Reference Tracker runtime whenever an AddRef()/Release() + // is performed on newInstance. + objRef.ReferenceTrackerPtr = referenceTracker; + + // This instance is already AddRefFromTrackerSource by the CLR, + // so it would also ReleaseFromTrackerSource on destruction. + objRef.PreventReleaseFromTrackerSourceOnDispose = true; + + Marshal.Release(referenceTracker); + } + + Marshal.Release(newInstance); + } + } + + public unsafe static void Init(IObjectReference objRef, bool addRefFromTrackerSource = true) + { + if (objRef.ReferenceTrackerPtr == IntPtr.Zero) + { + Guid iid = typeof(IReferenceTrackerVftbl).GUID; + int hr = Marshal.QueryInterface(objRef.ThisPtr, ref iid, out var referenceTracker); + if (hr == 0) + { + // WinUI scenario + // This instance should be used to tell the + // Reference Tracker runtime whenever an AddRef()/Release() + // is performed on newInstance. + objRef.ReferenceTrackerPtr = referenceTracker; + + if (addRefFromTrackerSource) + { + objRef.AddRefFromTrackerSource(); // ObjRef instance + } + else + { + objRef.PreventReleaseFromTrackerSourceOnDispose = true; + } + + Marshal.Release(referenceTracker); + } + } + } } public class DefaultComWrappers : ComWrappers @@ -267,36 +420,53 @@ private static unsafe bool IsRuntimeImplementedRCW(Type objType) } } return isRcw; - } - - protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags) - { - IObjectReference objRef = ComWrappersSupport.GetObjectReferenceForInterface(externalComObject); - + } + + private static object CreateObject(IObjectReference objRef) + { if (objRef.TryAs(out var inspectableRef) == 0) { IInspectable inspectable = new IInspectable(inspectableRef); - string runtimeClassName = ComWrappersSupport.GetRuntimeClassForTypeCreation(inspectable, ComWrappersSupport.CreateRCWType.Value); - if (string.IsNullOrEmpty(runtimeClassName)) - { - // If the external IInspectable has not implemented GetRuntimeClassName, - // we use the Inspectable wrapper directly. - return inspectable; - } + string runtimeClassName = ComWrappersSupport.GetRuntimeClassForTypeCreation(inspectable, ComWrappersSupport.CreateRCWType.Value); + if (string.IsNullOrEmpty(runtimeClassName)) + { + // If the external IInspectable has not implemented GetRuntimeClassName, + // we use the Inspectable wrapper directly. + return inspectable; + } return ComWrappersSupport.GetTypedRcwFactory(runtimeClassName)(inspectable); } else if (objRef.TryAs(out var weakRef) == 0) - { - // IWeakReference is IUnknown-based, so implementations of it may not (and likely won't) implement - // IInspectable. As a result, we need to check for them explicitly. - + { + // IWeakReference is IUnknown-based, so implementations of it may not (and likely won't) implement + // IInspectable. As a result, we need to check for them explicitly. + return new SingleInterfaceOptimizedObject(typeof(IWeakReference), weakRef); } + // If the external COM object isn't IInspectable or IWeakReference, we can't handle it. // If we're registered globally, we want to let the runtime fall back for IUnknown and IDispatch support. // Return null so the runtime can fall back gracefully in IUnknown and IDispatch scenarios. - return null; + return null; + } + + protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags) + { + IObjectReference objRef = ComWrappersSupport.GetObjectReferenceForInterface(externalComObject); + ComWrappersHelper.Init(objRef); + + var obj = CreateObject(objRef); + if (obj is IWinRTObject winrtObj && winrtObj.HasUnwrappableNativeObject && winrtObj.NativeObject != null) + { + // Handle the scenario where the CLR has already done an AddRefFromTrackerSource on the instance + // stored by the RCW type. We handle it by releasing the AddRef we did and not doing an release + // on destruction as the CLR would do it. + winrtObj.NativeObject.ReleaseFromTrackerSource(); + winrtObj.NativeObject.PreventReleaseFromTrackerSourceOnDispose = true; + } + + return obj; } protected override void ReleaseObjects(IEnumerable objects) diff --git a/src/WinRT.Runtime/Interop/IReferenceTracker.cs b/src/WinRT.Runtime/Interop/IReferenceTracker.cs new file mode 100644 index 0000000000..4eb61e8bfe --- /dev/null +++ b/src/WinRT.Runtime/Interop/IReferenceTracker.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.InteropServices; + +namespace WinRT.Interop +{ + [Guid("11D3B13A-180E-4789-A8BE-7712882893E6")] + internal unsafe struct IReferenceTrackerVftbl + { + public global::WinRT.Interop.IUnknownVftbl IUnknownVftbl; + private void* _ConnectFromTrackerSource_0; + private void* _DisconnectFromTrackerSource_1; + private void* _FindTrackerTargets_2; + private void* _GetReferenceTrackerManager_3; + private void* _AddRefFromTrackerSource_4; + public delegate* unmanaged[Stdcall] AddRefFromTrackerSource { get => (delegate* unmanaged[Stdcall])_AddRefFromTrackerSource_4; set => _AddRefFromTrackerSource_4 = (void*)value; } + private void* _ReleaseFromTrackerSource_5; + public delegate* unmanaged[Stdcall] ReleaseFromTrackerSource { get => (delegate* unmanaged[Stdcall])_ReleaseFromTrackerSource_5; set => _ReleaseFromTrackerSource_5 = (void*)value; } + private void* _PegFromTrackerSource_6; + } +} \ No newline at end of file diff --git a/src/WinRT.Runtime/Marshalers.cs b/src/WinRT.Runtime/Marshalers.cs index a9ac3bca4f..d4ea4bcb02 100644 --- a/src/WinRT.Runtime/Marshalers.cs +++ b/src/WinRT.Runtime/Marshalers.cs @@ -1061,13 +1061,13 @@ public static T FromAbi(IntPtr ptr) public static void DisposeAbi(IntPtr ptr) => MarshalInterfaceHelper.DisposeAbi(ptr); public static IntPtr FromManaged(T o, bool unwrapObject = true) { - var objRef = CreateMarshaler(o, unwrapObject); + using var objRef = CreateMarshaler(o, unwrapObject); return objRef?.GetRef() ?? IntPtr.Zero; } public static unsafe void CopyManaged(T o, IntPtr dest, bool unwrapObject = true) { - var objRef = CreateMarshaler(o, unwrapObject); + using var objRef = CreateMarshaler(o, unwrapObject); *(IntPtr*)dest.ToPointer() = objRef?.GetRef() ?? IntPtr.Zero; } diff --git a/src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt b/src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt index 9604eab403..a05c43ee51 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 'protected void WinRT.IObjectReference.AddRef(System.Boolean)' 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..7e5a999b72 100644 --- a/src/WinRT.Runtime/ObjectReference.cs +++ b/src/WinRT.Runtime/ObjectReference.cs @@ -16,7 +16,8 @@ public abstract class IObjectReference : IDisposable { protected bool disposed; private readonly IntPtr _thisPtr; - private object _disposedLock = new object(); + private object _disposedLock = new object(); + private IntPtr _referenceTrackerPtr; public IntPtr ThisPtr { @@ -25,6 +26,52 @@ public IntPtr ThisPtr ThrowIfDisposed(); return _thisPtr; } + } + +#if DEBUG + private unsafe uint RefCount + { + get + { + VftblIUnknown.AddRef(ThisPtr); + return VftblIUnknown.Release(ThisPtr); + } + } + + private bool BreakOnDispose { get; set; } +#endif + + internal bool IsAggregated { get; set; } + + internal bool PreventReleaseOnDispose { get; set; } + + internal bool PreventReleaseFromTrackerSourceOnDispose { get; set; } + + internal unsafe IntPtr ReferenceTrackerPtr + { + get + { + return _referenceTrackerPtr; + } + + set + { + _referenceTrackerPtr = value; + if (_referenceTrackerPtr != IntPtr.Zero) + { + ReferenceTracker.IUnknownVftbl.AddRef(_referenceTrackerPtr); + AddRefFromTrackerSource(); + } + } + } + + internal unsafe IReferenceTrackerVftbl ReferenceTracker + { + get + { + ThrowIfDisposed(); + return **(IReferenceTrackerVftbl**)ReferenceTrackerPtr; + } } protected unsafe IUnknownVftbl VftblIUnknown @@ -52,10 +99,20 @@ protected IObjectReference(IntPtr thisPtr) 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); + { + ThrowIfDisposed(); + Marshal.ThrowExceptionForHR(VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr)); + if (IsAggregated) + { + Marshal.Release(thatPtr); + } + AddRefFromTrackerSource(); + + var objRef = ObjectReference.Attach(ref thatPtr); + objRef.IsAggregated = IsAggregated; + objRef.PreventReleaseOnDispose = IsAggregated; + objRef.ReferenceTrackerPtr = ReferenceTrackerPtr; + return objRef; } public unsafe TInterface AsInterface() @@ -86,14 +143,23 @@ public unsafe TInterface AsInterface() 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); - } + { + objRef = null; + ThrowIfDisposed(); + int hr = VftblIUnknown.QueryInterface(ThisPtr, ref iid, out IntPtr thatPtr); + if (hr >= 0) + { + if (IsAggregated) + { + Marshal.Release(thatPtr); + } + AddRefFromTrackerSource(); + + objRef = ObjectReference.Attach(ref thatPtr); + objRef.IsAggregated = IsAggregated; + objRef.PreventReleaseOnDispose = IsAggregated; + objRef.ReferenceTrackerPtr = ReferenceTrackerPtr; + } return hr; } @@ -113,7 +179,7 @@ public T AsType() public IntPtr GetRef() { ThrowIfDisposed(); - AddRef(); + AddRef(false); return ThisPtr; } @@ -141,8 +207,20 @@ protected virtual void Dispose(bool disposing) if (disposed) { return; - } - Release(); + } +#if DEBUG + if (BreakOnDispose && System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Break(); + } +#endif + + if (!PreventReleaseOnDispose) + { + Release(); + } + + DisposeTrackerSource(); disposed = true; } } @@ -156,20 +234,31 @@ internal bool Resurrect() return false; } disposed = false; + ResurrectTrackerSource(); AddRef(); GC.ReRegisterForFinalize(this); return true; } - } + } + + protected virtual unsafe void AddRef(bool refFromTrackerSource) + { + VftblIUnknown.AddRef(ThisPtr); + if(refFromTrackerSource) + { + AddRefFromTrackerSource(); + } + } protected virtual unsafe void AddRef() { - VftblIUnknown.AddRef(ThisPtr); + AddRef(true); } protected virtual unsafe void Release() { - VftblIUnknown.Release(ThisPtr); + ReleaseFromTrackerSource(); + VftblIUnknown.Release(ThisPtr); } internal unsafe bool IsReferenceToManagedObject @@ -178,6 +267,46 @@ internal unsafe bool IsReferenceToManagedObject { return VftblIUnknown.Equals(IUnknownVftbl.AbiToProjectionVftbl); } + } + + internal unsafe void AddRefFromTrackerSource() + { + if (ReferenceTrackerPtr != IntPtr.Zero) + { + ReferenceTracker.AddRefFromTrackerSource(ReferenceTrackerPtr); + } + } + + internal unsafe void ReleaseFromTrackerSource() + { + if (ReferenceTrackerPtr != IntPtr.Zero) + { + ReferenceTracker.ReleaseFromTrackerSource(ReferenceTrackerPtr); + } + } + + private unsafe void ResurrectTrackerSource() + { + if (ReferenceTrackerPtr != IntPtr.Zero) + { + ReferenceTracker.IUnknownVftbl.AddRef(ReferenceTrackerPtr); + if (!PreventReleaseFromTrackerSourceOnDispose) + { + ReferenceTracker.AddRefFromTrackerSource(ReferenceTrackerPtr); + } + } + } + + private unsafe void DisposeTrackerSource() + { + if (ReferenceTrackerPtr != IntPtr.Zero) + { + if (!PreventReleaseFromTrackerSourceOnDispose) + { + ReferenceTracker.ReleaseFromTrackerSource(ReferenceTrackerPtr); + } + ReferenceTracker.IUnknownVftbl.Release(ReferenceTrackerPtr); + } } } @@ -293,10 +422,21 @@ 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()); + { + if (IsAggregated) + { + Marshal.Release(thatPtr); + } + AddRefFromTrackerSource(); + + using (var contextCallbackReference = ObjectReference.FromAbi(_contextCallbackPtr)) + { + objRef = new ObjectReferenceWithContext(thatPtr, contextCallbackReference.GetRef()) + { + IsAggregated = IsAggregated, + PreventReleaseOnDispose = IsAggregated, + ReferenceTrackerPtr = ReferenceTrackerPtr + }; } } return hr; diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 09cd6f1aa8..a0461f2587 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -1660,7 +1660,8 @@ MarshalInspectable.DisposeAbi(ptr); } }))()) { - ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr); +ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr); +% } )", platform_attribute, @@ -1670,7 +1671,8 @@ MarshalInspectable.DisposeAbi(ptr); cache_object, method.Name(), bind_list(", ", signature.params()), - settings.netstandard_compat ? "new " + default_interface_name : ""); + settings.netstandard_compat ? "new " + default_interface_name : "", + settings.netstandard_compat ? "" : "ComWrappersHelper.Init(_inner, false);"); } } else @@ -1679,11 +1681,13 @@ MarshalInspectable.DisposeAbi(ptr); public %() : this(%(ActivationFactory<%>.ActivateInstance())) { ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr); +% } )", class_type.TypeName(), settings.netstandard_compat ? "new " + default_interface_name : "", - class_type.TypeName()); + class_type.TypeName(), + settings.netstandard_compat ? "" : "ComWrappersHelper.Init(_inner, false);"); } } @@ -1746,23 +1750,20 @@ MarshalInspectable.DisposeAbi(ptr); 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); -if(baseInspectable == null) _inner = _inner.As(GuidGenerator.GetIID(typeof(%).GetHelperType())); +ComWrappersHelper.Init(isAggregation, this, composed, inner, out _inner); _defaultLazy = new Lazy<%>(() => (%)new SingleInterfaceOptimizedObject(typeof(%), _inner)); _lazyInterfaces = new Dictionary() {% }; - -ComWrappersSupport.RegisterObjectForInterface(this, ThisPtr); } finally { -MarshalInspectable.DisposeAbi(ptr); +Marshal.Release(inner); } } )", @@ -1779,7 +1780,6 @@ MarshalInspectable.DisposeAbi(ptr); default_interface_name, default_interface_name, default_interface_name, - default_interface_name, bind(class_type)); } } @@ -4734,7 +4734,7 @@ IInspectableVftbl = global::WinRT.IInspectable.Vftbl.AbiToProjectionVftable, if (!std::holds_alternative(type)) { w.write(R"( - : base(objRef) + : base(global::WinRT.DerivedComposed.Instance) )"); } }