diff --git a/src/coreclr/nativeaot/Directory.Build.props b/src/coreclr/nativeaot/Directory.Build.props index dce8e37c32fe38..ebfa725e4efd2c 100644 --- a/src/coreclr/nativeaot/Directory.Build.props +++ b/src/coreclr/nativeaot/Directory.Build.props @@ -56,6 +56,7 @@ true + FEATURE_COMWRAPPERS;$(DefineConstants) false diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/InternalCalls.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/InternalCalls.cs index 1ae7d4e7753de6..3c9d6c86ffc323 100644 --- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/InternalCalls.cs +++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/InternalCalls.cs @@ -60,14 +60,14 @@ internal static class InternalCalls // Force a garbage collection. [RuntimeExport("RhCollect")] - internal static void RhCollect(int generation, InternalGCCollectionMode mode) + internal static void RhCollect(int generation, InternalGCCollectionMode mode, bool lowMemoryP = false) { - RhpCollect(generation, mode); + RhpCollect(generation, mode, lowMemoryP); } [DllImport(Redhawk.BaseName)] [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] - private static extern void RhpCollect(int generation, InternalGCCollectionMode mode); + private static extern void RhpCollect(int generation, InternalGCCollectionMode mode, bool lowMemoryP); [RuntimeExport("RhGetGcTotalMemory")] internal static long RhGetGcTotalMemory() diff --git a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp index 681a63e8fb9ab1..8ba49e1f2dad7d 100644 --- a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp @@ -25,7 +25,7 @@ #include "threadstore.inl" #include "thread.inl" -EXTERN_C NATIVEAOT_API void __cdecl RhpCollect(uint32_t uGeneration, uint32_t uMode) +EXTERN_C NATIVEAOT_API void __cdecl RhpCollect(uint32_t uGeneration, uint32_t uMode, UInt32_BOOL lowMemoryP) { // This must be called via p/invoke rather than RuntimeImport to make the stack crawlable. @@ -35,7 +35,7 @@ EXTERN_C NATIVEAOT_API void __cdecl RhpCollect(uint32_t uGeneration, uint32_t uM pCurThread->DisablePreemptiveMode(); ASSERT(!pCurThread->IsDoNotTriggerGcSet()); - GCHeapUtilities::GetGCHeap()->GarbageCollect(uGeneration, FALSE, uMode); + GCHeapUtilities::GetGCHeap()->GarbageCollect(uGeneration, lowMemoryP, uMode); pCurThread->EnablePreemptiveMode(); } @@ -128,6 +128,11 @@ COOP_PINVOKE_HELPER(int32_t, RhSetGcLatencyMode, (int32_t newLatencyMode)) return GCHeapUtilities::GetGCHeap()->SetGcLatencyMode(newLatencyMode); } +COOP_PINVOKE_HELPER(FC_BOOL_RET, RhIsPromoted, (OBJECTREF obj)) +{ + FC_RETURN_BOOL(GCHeapUtilities::GetGCHeap()->IsPromoted(obj)); +} + COOP_PINVOKE_HELPER(FC_BOOL_RET, RhIsServerGc, ()) { FC_RETURN_BOOL(GCHeapUtilities::IsServerHeap()); diff --git a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp index 5c5e20e3c7d195..3d0990962b7c99 100644 --- a/src/coreclr/nativeaot/Runtime/gcrhenv.cpp +++ b/src/coreclr/nativeaot/Runtime/gcrhenv.cpp @@ -989,15 +989,19 @@ bool GCToEEInterface::EagerFinalized(Object* obj) // Managed code should not be running. ASSERT(GCHeapUtilities::GetGCHeap()->IsGCInProgressHelper()); - // the lowermost 1 bit is reserved for storing additional info about the handle - const uintptr_t HandleTagBits = 1; + // the lowermost 2 bits are reserved for storing additional info about the handle + // we can use these bits because handle is at least 4 byte aligned + const uintptr_t HandleTagBits = 3; WeakReference* weakRefObj = (WeakReference*)obj; OBJECTHANDLE handle = (OBJECTHANDLE)(weakRefObj->m_taggedHandle & ~HandleTagBits); - _ASSERTE((weakRefObj->m_taggedHandle & 2) == 0); - HandleType handleType = (weakRefObj->m_taggedHandle & 1) ? HandleType::HNDTYPE_WEAK_LONG : HandleType::HNDTYPE_WEAK_SHORT; + HandleType handleType = (weakRefObj->m_taggedHandle & 2) ? + HandleType::HNDTYPE_STRONG : + (weakRefObj->m_taggedHandle & 1) ? + HandleType::HNDTYPE_WEAK_LONG : + HandleType::HNDTYPE_WEAK_SHORT; // keep the bit that indicates whether this reference was tracking resurrection, clear the rest. - weakRefObj->m_taggedHandle &= HandleTagBits; + weakRefObj->m_taggedHandle &= (uintptr_t)1; GCHandleUtilities::GetGCHandleManager()->DestroyHandleOfType(handle, handleType); return true; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 13cd0a531e3a2a..9652509dc831bd 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -210,6 +210,7 @@ + @@ -273,6 +274,9 @@ Interop\Windows\Ole32\Interop.CoGetApartmentType.cs + + Interop\Windows\Ole32\Interop.CoGetContextToken.cs + Interop\Windows\OleAut32\Interop.VariantClear.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs index 42916dad1ed5b1..d95eab971603a2 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs @@ -2,32 +2,47 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime.CompilerServices; #if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS -#pragma warning disable IDE0060 - namespace System { internal sealed partial class ComAwareWeakReference { - internal static object? ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId) + // We don't want to consult ComWrappers if no RCW objects have been created. + // In addition we don't want a direct reference to ComWrappers to allow for better + // trimming. So we instead make use of delegates that ComWrappers registers when + // it is used. + private static unsafe delegate* s_comWeakRefToObjectCallback; + private static unsafe delegate* s_possiblyComObjectCallback; + private static unsafe delegate* s_objectToComWeakRefCallback; + + internal static unsafe void InitializeCallbacks( + delegate* comWeakRefToObject, + delegate* possiblyComObject, + delegate* objectToComWeakRef) + { + s_comWeakRefToObjectCallback = comWeakRefToObject; + s_objectToComWeakRefCallback = objectToComWeakRef; + s_possiblyComObjectCallback = possiblyComObject; + } + + internal static unsafe object? ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId) { - // NativeAOT support for COM WeakReference is NYI - throw new NotImplementedException(); + return s_comWeakRefToObjectCallback != null ? s_comWeakRefToObjectCallback(pComWeakRef, wrapperId) : null; } - internal static bool PossiblyComObject(object target) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe bool PossiblyComObject(object target) { - // NativeAOT support for COM WeakReference is NYI - return false; + return s_possiblyComObjectCallback != null ? s_possiblyComObjectCallback(target) : false; } - internal static IntPtr ObjectToComWeakRef(object target, out long wrapperId) + internal static unsafe IntPtr ObjectToComWeakRef(object target, out long wrapperId) { - // NativeAOT support for COM WeakReference is NYI wrapperId = 0; - return 0; + return s_objectToComWeakRefCallback != null ? s_objectToComWeakRefCallback(target, out wrapperId) : IntPtr.Zero; } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs index 39a431a3bab090..71946fb85dc411 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs @@ -30,13 +30,35 @@ public abstract partial class ComWrappers internal static readonly Guid IID_IUnknown = new Guid(0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); internal static readonly Guid IID_IReferenceTrackerTarget = new Guid(0x64bd43f8, 0xbfee, 0x4ec4, 0xb7, 0xeb, 0x29, 0x35, 0x15, 0x8d, 0xae, 0x21); internal static readonly Guid IID_TaggedImpl = new Guid(0x5c13e51c, 0x4f32, 0x4726, 0xa3, 0xfd, 0xf3, 0xed, 0xd6, 0x3d, 0xa3, 0xa0); + internal static readonly Guid IID_IReferenceTracker = new Guid(0x11D3B13A, 0x180E, 0x4789, 0xA8, 0xBE, 0x77, 0x12, 0x88, 0x28, 0x93, 0xE6); + internal static readonly Guid IID_IReferenceTrackerHost = new Guid(0x29a71c6a, 0x3c42, 0x4416, 0xa3, 0x9d, 0xe2, 0x82, 0x5a, 0x7, 0xa7, 0x73); + internal static readonly Guid IID_IReferenceTrackerManager = new Guid(0x3cf184b4, 0x7ccb, 0x4dda, 0x84, 0x55, 0x7e, 0x6c, 0xe9, 0x9a, 0x32, 0x98); + internal static readonly Guid IID_IFindReferenceTargetsCallback = new Guid(0x04b3486c, 0x4687, 0x4229, 0x8d, 0x14, 0x50, 0x5a, 0xb5, 0x84, 0xdd, 0x88); + + private static readonly Guid IID_IInspectable = new Guid(0xAF86E2E0, 0xB12D, 0x4c6a, 0x9C, 0x5A, 0xD7, 0xAA, 0x65, 0x10, 0x1E, 0x90); + private static readonly Guid IID_IWeakReferenceSource = new Guid(0x00000038, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46); private static readonly ConditionalWeakTable s_rcwTable = new ConditionalWeakTable(); + private static readonly List s_referenceTrackerNativeObjectWrapperCache = new List(); private readonly ConditionalWeakTable _ccwTable = new ConditionalWeakTable(); private readonly Lock _lock = new Lock(); private readonly Dictionary _rcwCache = new Dictionary(); + internal static bool TryGetComInstanceForIID(object obj, Guid iid, out IntPtr unknown, out long wrapperId) + { + if (obj == null + || !s_rcwTable.TryGetValue(obj, out NativeObjectWrapper? wrapper)) + { + unknown = IntPtr.Zero; + wrapperId = 0; + return false; + } + + wrapperId = wrapper._comWrappers.id; + return Marshal.QueryInterface(wrapper._externalComObject, in iid, out unknown) == HResults.S_OK; + } + public static unsafe bool TryGetComInstance(object obj, out IntPtr unknown) { unknown = IntPtr.Zero; @@ -46,7 +68,7 @@ public static unsafe bool TryGetComInstance(object obj, out IntPtr unknown) return false; } - return Marshal.QueryInterface(wrapper._externalComObject, in IID_IUnknown, out unknown) == 0; + return Marshal.QueryInterface(wrapper._externalComObject, in IID_IUnknown, out unknown) == HResults.S_OK; } public static unsafe bool TryGetObject(IntPtr unknown, [NotNullWhen(true)] out object? obj) @@ -58,7 +80,8 @@ public static unsafe bool TryGetObject(IntPtr unknown, [NotNullWhen(true)] out o } ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(unknown); - if (comInterfaceDispatch == null) + if (comInterfaceDispatch == null || + ComInterfaceDispatch.ToManagedObjectWrapper(comInterfaceDispatch)->MarkedToDestroy) { return false; } @@ -144,9 +167,8 @@ public bool IsRooted bool rooted = GetComCount(refCount) > 0; if (!rooted) { - // TODO: global pegging state - // https://github.com/dotnet/runtime/issues/85137 - rooted = GetTrackerCount(refCount) > 0 && (Flags & CreateComInterfaceFlagsEx.IsPegged) != 0; + rooted = GetTrackerCount(refCount) > 0 && + ((Flags & CreateComInterfaceFlagsEx.IsPegged) != 0 || TrackerObjectManager.s_isGlobalPeggingOn); } return rooted; } @@ -164,6 +186,8 @@ public ManagedObjectWrapperHolder? Holder } } + public readonly bool MarkedToDestroy => IsMarkedToDestroy(RefCount); + public uint AddRef() { return GetComCount(Interlocked.Increment(ref RefCount)); @@ -388,7 +412,7 @@ private static bool IsMarkedToDestroy(ulong c) } } - internal unsafe class ManagedObjectWrapperHolder + internal sealed unsafe class ManagedObjectWrapperHolder { static ManagedObjectWrapperHolder() { @@ -428,7 +452,7 @@ public ManagedObjectWrapperHolder(ManagedObjectWrapper* wrapper, object wrappedO public uint AddRef() => _wrapper->AddRef(); } - internal unsafe class ManagedObjectWrapperReleaser + internal sealed unsafe class ManagedObjectWrapperReleaser { private ManagedObjectWrapper* _wrapper; @@ -471,18 +495,60 @@ public ManagedObjectWrapperReleaser(ManagedObjectWrapper* wrapper) internal unsafe class NativeObjectWrapper { internal IntPtr _externalComObject; - private ComWrappers _comWrappers; - internal GCHandle _proxyHandle; + private IntPtr _inner; + internal ComWrappers _comWrappers; + internal readonly GCHandle _proxyHandle; + internal readonly GCHandle _proxyHandleTrackingResurrection; + internal readonly bool _aggregatedManagedObjectWrapper; + + static NativeObjectWrapper() + { + // Registering the weak reference support callbacks to enable + // consulting ComWrappers when weak references are created + // for RCWs. + ComAwareWeakReference.InitializeCallbacks(&ComWeakRefToObject, &PossiblyComObject, &ObjectToComWeakRef); + } + + public static NativeObjectWrapper Create(IntPtr externalComObject, IntPtr inner, ComWrappers comWrappers, object comProxy, CreateObjectFlags flags) + { + if (flags.HasFlag(CreateObjectFlags.TrackerObject) && + Marshal.QueryInterface(externalComObject, in IID_IReferenceTracker, out IntPtr trackerObject) == HResults.S_OK) + { + return new ReferenceTrackerNativeObjectWrapper(externalComObject, inner, comWrappers, comProxy, flags, trackerObject); + } + else + { + return new NativeObjectWrapper(externalComObject, inner, comWrappers, comProxy, flags); + } + } - public NativeObjectWrapper(IntPtr externalComObject, ComWrappers comWrappers, object comProxy) + public NativeObjectWrapper(IntPtr externalComObject, IntPtr inner, ComWrappers comWrappers, object comProxy, CreateObjectFlags flags) { _externalComObject = externalComObject; + _inner = inner; _comWrappers = comWrappers; - Marshal.AddRef(externalComObject); _proxyHandle = GCHandle.Alloc(comProxy, GCHandleType.Weak); + + // We have a separate handle tracking resurrection as we want to make sure + // we clean up the NativeObjectWrapper only after the RCW has been finalized + // due to it can access the native object in the finalizer. At the same time, + // we want other callers which are using _proxyHandle such as the RCW cache to + // see the object as not alive once it is eligible for finalization. + _proxyHandleTrackingResurrection = GCHandle.Alloc(comProxy, GCHandleType.WeakTrackResurrection); + + // If this is an aggregation scenario and the identity object + // is a managed object wrapper, we need to call Release() to + // indicate this external object isn't rooted. In the event the + // object is passed out to native code an AddRef() must be called + // based on COM convention and will "fix" the count. + _aggregatedManagedObjectWrapper = flags.HasFlag(CreateObjectFlags.Aggregation) && TryGetComInterfaceDispatch(_externalComObject) != null; + if (_aggregatedManagedObjectWrapper) + { + Marshal.Release(externalComObject); + } } - public void Release() + public virtual void Release() { if (_comWrappers != null) { @@ -495,19 +561,113 @@ public void Release() _proxyHandle.Free(); } - if (_externalComObject != IntPtr.Zero) + if (_proxyHandleTrackingResurrection.IsAllocated) + { + _proxyHandleTrackingResurrection.Free(); + } + + // If the inner was supplied, we need to release our reference. + if (_inner != IntPtr.Zero) { - Marshal.Release(_externalComObject); - _externalComObject = IntPtr.Zero; + Marshal.Release(_inner); + _inner = IntPtr.Zero; } + + _externalComObject = IntPtr.Zero; } ~NativeObjectWrapper() { + if (_proxyHandleTrackingResurrection.IsAllocated && _proxyHandleTrackingResurrection.Target != null) + { + // The RCW object has not been fully collected, so it still + // can make calls on the native object in its finalizer. + // Keep ourselves alive until it is finalized. + GC.ReRegisterForFinalize(this); + return; + } + Release(); } } + internal sealed class ReferenceTrackerNativeObjectWrapper : NativeObjectWrapper + { + private IntPtr _trackerObject; + private readonly bool _releaseTrackerObject; + private int _trackerObjectDisconnected; // Atomic boolean, so using int. + internal readonly IntPtr _contextToken; + internal readonly GCHandle _nativeObjectWrapperWeakHandle; + + public IntPtr TrackerObject => (_trackerObject == IntPtr.Zero || _trackerObjectDisconnected == 1) ? IntPtr.Zero : _trackerObject; + + public ReferenceTrackerNativeObjectWrapper( + nint externalComObject, + nint inner, + ComWrappers comWrappers, + object comProxy, + CreateObjectFlags flags, + IntPtr trackerObject) + : base(externalComObject, inner, comWrappers, comProxy, flags) + { + Debug.Assert(flags.HasFlag(CreateObjectFlags.TrackerObject)); + Debug.Assert(trackerObject != IntPtr.Zero); + + _trackerObject = trackerObject; + _releaseTrackerObject = true; + + TrackerObjectManager.OnIReferenceTrackerFound(_trackerObject); + TrackerObjectManager.AfterWrapperCreated(_trackerObject); + + if (flags.HasFlag(CreateObjectFlags.Aggregation)) + { + // Aggregation with an IReferenceTracker instance creates an extra AddRef() + // on the outer (e.g. MOW) so we clean up that issue here. + _releaseTrackerObject = false; + IReferenceTracker.ReleaseFromTrackerSource(_trackerObject); // IReferenceTracker + Marshal.Release(_trackerObject); + } + + _contextToken = GetContextToken(); + _nativeObjectWrapperWeakHandle = GCHandle.Alloc(this, GCHandleType.Weak); + } + + public override void Release() + { + // Remove the entry from the cache that keeps track of the active NativeObjectWrappers. + if (_nativeObjectWrapperWeakHandle.IsAllocated) + { + s_referenceTrackerNativeObjectWrapperCache.Remove(_nativeObjectWrapperWeakHandle); + _nativeObjectWrapperWeakHandle.Free(); + } + + DisconnectTracker(); + + base.Release(); + } + + public void DisconnectTracker() + { + // Return if already disconnected or the tracker isn't set. + if (_trackerObject == IntPtr.Zero || Interlocked.CompareExchange(ref _trackerObjectDisconnected, 1, 0) != 0) + { + return; + } + + // Always release the tracker source during a disconnect. + // This to account for the implied IUnknown ownership by the runtime. + IReferenceTracker.ReleaseFromTrackerSource(_trackerObject); // IUnknown + + // Disconnect from the tracker. + if (_releaseTrackerObject) + { + IReferenceTracker.ReleaseFromTrackerSource(_trackerObject); // IReferenceTracker + Marshal.Release(_trackerObject); + _trackerObject = IntPtr.Zero; + } + } + } + /// /// Globally registered instance of the ComWrappers class for reference tracker support. /// @@ -518,6 +678,25 @@ public void Release() /// private static ComWrappers? s_globalInstanceForMarshalling; + private static long s_instanceCounter; + private readonly long id = Interlocked.Increment(ref s_instanceCounter); + + internal static object? GetOrCreateObjectFromWrapper(long wrapperId, IntPtr externalComObject) + { + if (s_globalInstanceForTrackerSupport != null && s_globalInstanceForTrackerSupport.id == wrapperId) + { + return s_globalInstanceForTrackerSupport.GetOrCreateObjectForComInstance(externalComObject, CreateObjectFlags.TrackerObject); + } + else if (s_globalInstanceForMarshalling != null && s_globalInstanceForMarshalling.id == wrapperId) + { + return ComObjectForInterface(externalComObject); + } + else + { + return null; + } + } + /// /// Create a COM representation of the supplied object that can be passed to a non-managed environment. /// @@ -692,6 +871,52 @@ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, Create return (ComInterfaceDispatch*)comObject; } + private static void DetermineIdentityAndInner( + IntPtr externalComObject, + IntPtr innerMaybe, + CreateObjectFlags flags, + out IntPtr identity, + out IntPtr inner) + { + inner = innerMaybe; + + IntPtr checkForIdentity = externalComObject; + + // Check if the flags indicate we are creating + // an object for an external IReferenceTracker instance + // that we are aggregating with. + bool refTrackerInnerScenario = flags.HasFlag(CreateObjectFlags.TrackerObject) + && flags.HasFlag(CreateObjectFlags.Aggregation); + if (refTrackerInnerScenario && + Marshal.QueryInterface(externalComObject, in IID_IReferenceTracker, out IntPtr referenceTrackerPtr) == HResults.S_OK) + { + // We are checking the supplied external value + // for IReferenceTracker since in .NET 5 API usage scenarios + // this could actually be the inner and we want the true identity + // not the inner . This is a trick since the only way + // to get identity from an inner is through a non-IUnknown + // interface QI. Once we have the IReferenceTracker + // instance we can be sure the QI for IUnknown will really + // be the true identity. + using ComHolder referenceTracker = new ComHolder(referenceTrackerPtr); + checkForIdentity = referenceTrackerPtr; + Marshal.ThrowExceptionForHR(Marshal.QueryInterface(checkForIdentity, in IID_IUnknown, out identity)); + } + else + { + Marshal.ThrowExceptionForHR(Marshal.QueryInterface(externalComObject, in IID_IUnknown, out identity)); + } + + // Set the inner if scenario dictates an update. + if (innerMaybe == IntPtr.Zero && // User didn't supply inner - .NET 5 API scenario sanity check. + checkForIdentity != externalComObject && // Target of check was changed - .NET 5 API scenario sanity check. + externalComObject != identity && // The supplied object doesn't match the computed identity. + refTrackerInnerScenario) // The appropriate flags were set. + { + inner = externalComObject; + } + } + #pragma warning disable IDE0060 /// /// Get the currently registered managed object or creates a new managed object and registers it. @@ -715,12 +940,17 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal( if (innerMaybe != IntPtr.Zero && !flags.HasFlag(CreateObjectFlags.Aggregation)) throw new InvalidOperationException(SR.InvalidOperation_SuppliedInnerMustBeMarkedAggregation); - if (flags.HasFlag(CreateObjectFlags.Aggregation)) - throw new NotImplementedException(); + DetermineIdentityAndInner( + externalComObject, + innerMaybe, + flags, + out IntPtr identity, + out IntPtr inner); + using ComHolder releaseIdentity = new ComHolder(identity); if (flags.HasFlag(CreateObjectFlags.Unwrap)) { - ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(externalComObject); + ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(identity); if (comInterfaceDispatch != null) { // If we found a managed object wrapper in this ComWrappers instance @@ -735,18 +965,13 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal( object unwrapped = ComInterfaceDispatch.GetInstance(comInterfaceDispatch); if (_ccwTable.TryGetValue(unwrapped, out ManagedObjectWrapperHolder? unwrappedWrapperInThisContext)) { - // The unwrapped object has a CCW in this context. Get the IUnknown for the externalComObject + // The unwrapped object has a CCW in this context. Compare with identity // so we can see if it's the CCW for the unwrapped object in this context. - int hr = Marshal.QueryInterface(externalComObject, in IID_IUnknown, out IntPtr externalIUnknown); - Debug.Assert(hr == 0); // An external COM object that came from a ComWrappers instance - // will always be well-formed. - if (unwrappedWrapperInThisContext.ComIp == externalIUnknown) + if (unwrappedWrapperInThisContext.ComIp == identity) { - Marshal.Release(externalIUnknown); retValue = unwrapped; return true; } - Marshal.Release(externalIUnknown); } } } @@ -755,7 +980,7 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal( { using (LockHolder.Hold(_lock)) { - if (_rcwCache.TryGetValue(externalComObject, out GCHandle handle)) + if (_rcwCache.TryGetValue(identity, out GCHandle handle)) { object? cachedWrapper = handle.Target; if (cachedWrapper is not null) @@ -767,29 +992,35 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal( { // The GCHandle has been clear out but the NativeObjectWrapper // finalizer has not yet run to remove the entry from _rcwCache - _rcwCache.Remove(externalComObject); + _rcwCache.Remove(identity); } } if (wrapperMaybe is not null) { retValue = wrapperMaybe; - NativeObjectWrapper wrapper = new NativeObjectWrapper( - externalComObject, + NativeObjectWrapper wrapper = NativeObjectWrapper.Create( + identity, + inner, this, - retValue); + retValue, + flags); if (!s_rcwTable.TryAdd(retValue, wrapper)) { wrapper.Release(); throw new NotSupportedException(); } - _rcwCache.Add(externalComObject, wrapper._proxyHandle); + _rcwCache.Add(identity, wrapper._proxyHandle); + if (wrapper is ReferenceTrackerNativeObjectWrapper referenceTrackerNativeObjectWrapper) + { + s_referenceTrackerNativeObjectWrapperCache.Add(referenceTrackerNativeObjectWrapper._nativeObjectWrapperWeakHandle); + } return true; } } } - retValue = CreateObject(externalComObject, flags); + retValue = CreateObject(identity, flags); if (retValue == null) { // If ComWrappers instance cannot create wrapper, we can do nothing here. @@ -798,29 +1029,35 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal( if (flags.HasFlag(CreateObjectFlags.UniqueInstance)) { - NativeObjectWrapper wrapper = new NativeObjectWrapper( - externalComObject, + NativeObjectWrapper wrapper = NativeObjectWrapper.Create( + identity, + inner, null, // No need to cache NativeObjectWrapper for unique instances. They are not cached. - retValue); + retValue, + flags); if (!s_rcwTable.TryAdd(retValue, wrapper)) { wrapper.Release(); throw new NotSupportedException(); } + if (wrapper is ReferenceTrackerNativeObjectWrapper referenceTrackerNativeObjectWrapper) + { + s_referenceTrackerNativeObjectWrapperCache.Add(referenceTrackerNativeObjectWrapper._nativeObjectWrapperWeakHandle); + } return true; } using (LockHolder.Hold(_lock)) { object? cachedWrapper = null; - if (_rcwCache.TryGetValue(externalComObject, out var existingHandle)) + if (_rcwCache.TryGetValue(identity, out var existingHandle)) { cachedWrapper = existingHandle.Target; if (cachedWrapper is null) { // The GCHandle has been clear out but the NativeObjectWrapper // finalizer has not yet run to remove the entry from _rcwCache - _rcwCache.Remove(externalComObject); + _rcwCache.Remove(identity); } } @@ -830,16 +1067,22 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal( } else { - NativeObjectWrapper wrapper = new NativeObjectWrapper( - externalComObject, + NativeObjectWrapper wrapper = NativeObjectWrapper.Create( + identity, + inner, this, - retValue); + retValue, + flags); if (!s_rcwTable.TryAdd(retValue, wrapper)) { wrapper.Release(); throw new NotSupportedException(); } - _rcwCache.Add(externalComObject, wrapper._proxyHandle); + _rcwCache.Add(identity, wrapper._proxyHandle); + if (wrapper is ReferenceTrackerNativeObjectWrapper referenceTrackerNativeObjectWrapper) + { + s_referenceTrackerNativeObjectWrapperCache.Add(referenceTrackerNativeObjectWrapper._nativeObjectWrapperWeakHandle); + } } } @@ -853,7 +1096,7 @@ private void RemoveRCWFromCache(IntPtr comPointer, GCHandle expectedValue) { // TryGetOrCreateObjectForComInstanceInternal may have put a new entry into the cache // in the time between the GC cleared the contents of the GC handle but before the - // NativeObjectWrapper finializer ran. + // NativeObjectWrapper finalizer ran. if (_rcwCache.TryGetValue(comPointer, out GCHandle cachedValue) && expectedValue.Equals(cachedValue)) { _rcwCache.Remove(comPointer); @@ -952,7 +1195,92 @@ internal static object ComObjectForInterface(IntPtr externalComObject) throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperInstance); } - return s_globalInstanceForMarshalling.GetOrCreateObjectForComInstance(externalComObject, CreateObjectFlags.Unwrap); + // TrackerObject support and unwrapping matches the built-in semantics that the global marshalling scenario mimics. + return s_globalInstanceForMarshalling.GetOrCreateObjectForComInstance(externalComObject, CreateObjectFlags.TrackerObject | CreateObjectFlags.Unwrap); + } + + internal static IntPtr GetOrCreateTrackerTarget(IntPtr externalComObject) + { + if (s_globalInstanceForTrackerSupport == null) + { + throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperTrackerInstance); + } + + object obj = s_globalInstanceForTrackerSupport.GetOrCreateObjectForComInstance(externalComObject, CreateObjectFlags.TrackerObject); + return s_globalInstanceForTrackerSupport.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.TrackerSupport); + } + + internal static void ReleaseExternalObjectsFromCurrentThread() + { + if (s_globalInstanceForTrackerSupport == null) + { + throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperTrackerInstance); + } + + IntPtr contextToken = GetContextToken(); + + List objects = new List(); + foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) + { + ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); + if (nativeObjectWrapper != null && + nativeObjectWrapper._contextToken == contextToken) + { + objects.Add(nativeObjectWrapper._proxyHandle.Target); + + // Separate the wrapper from the tracker runtime prior to + // passing them. + nativeObjectWrapper.DisconnectTracker(); + } + } + + s_globalInstanceForTrackerSupport.ReleaseObjects(objects); + } + + // Used during GC callback + internal static unsafe void WalkExternalTrackerObjects() + { + bool walkFailed = false; + + foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) + { + ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); + if (nativeObjectWrapper != null && + nativeObjectWrapper.TrackerObject != IntPtr.Zero) + { + FindReferenceTargetsCallback.s_currentRootObjectHandle = nativeObjectWrapper._proxyHandle; + if (IReferenceTracker.FindTrackerTargets(nativeObjectWrapper.TrackerObject, TrackerObjectManager.s_findReferencesTargetCallback) != HResults.S_OK) + { + walkFailed = true; + FindReferenceTargetsCallback.s_currentRootObjectHandle = default; + break; + } + FindReferenceTargetsCallback.s_currentRootObjectHandle = default; + } + } + + // Report whether walking failed or not. + if (walkFailed) + { + TrackerObjectManager.s_isGlobalPeggingOn = true; + } + IReferenceTrackerManager.FindTrackerTargetsCompleted(TrackerObjectManager.s_trackerManager, walkFailed); + } + + // Used during GC callback + internal static void DetachNonPromotedObjects() + { + foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) + { + ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); + if (nativeObjectWrapper != null && + nativeObjectWrapper.TrackerObject != IntPtr.Zero && + !RuntimeImports.RhIsPromoted(nativeObjectWrapper._proxyHandle.Target)) + { + // Notify the wrapper it was not promoted and is being collected. + TrackerObjectManager.BeforeWrapperFinalized(nativeObjectWrapper.TrackerObject); + } + } } [UnmanagedCallersOnly] @@ -1049,5 +1377,257 @@ private static unsafe IntPtr CreateDefaultIReferenceTrackerTargetVftbl() vftbl[6] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerTarget_Unpeg; return (IntPtr)vftbl; } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_DisconnectUnusedReferenceSources(IntPtr pThis, uint flags) + { + try + { + // Defined in windows.ui.xaml.hosting.referencetracker.h. + const uint XAML_REFERENCETRACKER_DISCONNECT_SUSPEND = 0x00000001; + + if ((flags & XAML_REFERENCETRACKER_DISCONNECT_SUSPEND) != 0) + { + RuntimeImports.RhCollect(2, InternalGCCollectionMode.Blocking | InternalGCCollectionMode.Optimized, true); + } + else + { + GC.Collect(); + } + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + + } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_ReleaseDisconnectedReferenceSources(IntPtr pThis) + { + try + { + GC.WaitForPendingFinalizers(); + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnThread(IntPtr pThis) + { + try + { + ReleaseExternalObjectsFromCurrentThread(); + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + + } + + // Creates a proxy object (managed object wrapper) that points to the given IUnknown. + // The proxy represents the following: + // 1. Has a managed reference pointing to the external object + // and therefore forms a cycle that can be resolved by GC. + // 2. Forwards data binding requests. + // + // For example: + // NoCW = Native Object Com Wrapper also known as RCW + // + // Grid <---- NoCW Grid <-------- NoCW + // | ^ | ^ + // | | Becomes | | + // v | v | + // Rectangle Rectangle ----->Proxy + // + // Arguments + // obj - An IUnknown* where a NoCW points to (Grid, in this case) + // Notes: + // 1. We can either create a new NoCW or get back an old one from the cache. + // 2. This obj could be a regular tracker runtime object for data binding. + // ppNewReference - The IReferenceTrackerTarget* for the proxy created + // The tracker runtime will call IReferenceTrackerTarget to establish a reference. + // + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_GetTrackerTarget(IntPtr pThis, IntPtr punk, IntPtr* ppNewReference) + { + if (punk == IntPtr.Zero) + { + return HResults.E_INVALIDARG; + } + + if (Marshal.QueryInterface(punk, in IID_IUnknown, out IntPtr ppv) != HResults.S_OK) + { + return HResults.COR_E_INVALIDCAST; + } + + try + { + using ComHolder identity = new ComHolder(ppv); + using ComHolder trackerTarget = new ComHolder(GetOrCreateTrackerTarget(identity.Ptr)); + return Marshal.QueryInterface(trackerTarget.Ptr, in IID_IReferenceTrackerTarget, out *ppNewReference); + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_AddMemoryPressure(IntPtr pThis, long bytesAllocated) + { + try + { + GC.AddMemoryPressure(bytesAllocated); + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_RemoveMemoryPressure(IntPtr pThis, long bytesAllocated) + { + try + { + GC.RemoveMemoryPressure(bytesAllocated); + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + + // Lifetime maintained by stack - we don't care about ref counts + [UnmanagedCallersOnly] + internal static unsafe uint Untracked_AddRef(IntPtr pThis) + { + return 1; + } + + [UnmanagedCallersOnly] + internal static unsafe uint Untracked_Release(IntPtr pThis) + { + return 1; + } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) + { + if (*guid == IID_IReferenceTrackerHost || *guid == IID_IUnknown) + { + *ppObject = pThis; + return 0; + } + else + { + return HResults.COR_E_INVALIDCAST; + } + } + + internal static unsafe IntPtr CreateDefaultIReferenceTrackerHostVftbl() + { + IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ComWrappers), 9 * sizeof(IntPtr)); + vftbl[0] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerHost_QueryInterface; + vftbl[1] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_AddRef; + vftbl[2] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_Release; + vftbl[3] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerHost_DisconnectUnusedReferenceSources; + vftbl[4] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerHost_ReleaseDisconnectedReferenceSources; + vftbl[5] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnThread; + vftbl[6] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerHost_GetTrackerTarget; + vftbl[7] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerHost_AddMemoryPressure; + vftbl[8] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerHost_RemoveMemoryPressure; + return (IntPtr)vftbl; + } + + private static IntPtr GetContextToken() + { +#if TARGET_WINDOWS + Interop.Ole32.CoGetContextToken(out IntPtr contextToken); + return contextToken; +#else + return IntPtr.Zero; +#endif + } + + // Wrapper for IWeakReference + private static unsafe class IWeakReference + { + public static int Resolve(IntPtr pThis, Guid guid, out IntPtr inspectable) + { + fixed (IntPtr* inspectablePtr = &inspectable) + return (*(delegate* unmanaged**)pThis)[3](pThis, &guid, inspectablePtr); + } + } + + // Wrapper for IWeakReferenceSource + private static unsafe class IWeakReferenceSource + { + public static int GetWeakReference(IntPtr pThis, out IntPtr weakReference) + { + fixed (IntPtr* weakReferencePtr = &weakReference) + return (*(delegate* unmanaged**)pThis)[3](pThis, weakReferencePtr); + } + } + + private static object? ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId) + { + if (wrapperId == 0) + { + return null; + } + + // Using the IWeakReference*, get ahold of the target native COM object's IInspectable*. If this resolve fails or + // returns null, then we assume that the underlying native COM object is no longer alive, and thus we cannot create a + // new RCW for it. + if (IWeakReference.Resolve(pComWeakRef, IID_IInspectable, out IntPtr targetPtr) == HResults.S_OK && + targetPtr != IntPtr.Zero) + { + using ComHolder target = new ComHolder(targetPtr); + if (Marshal.QueryInterface(target.Ptr, in IID_IUnknown, out IntPtr targetIdentityPtr) == HResults.S_OK) + { + using ComHolder targetIdentity = new ComHolder(targetIdentityPtr); + return GetOrCreateObjectFromWrapper(wrapperId, targetIdentity.Ptr); + } + } + + return null; + } + + private static unsafe bool PossiblyComObject(object target) + { + // If the RCW is an aggregated RCW, then the managed object cannot be recreated from the IUnknown + // as the outer IUnknown wraps the managed object. In this case, don't create a weak reference backed + // by a COM weak reference. + return s_rcwTable.TryGetValue(target, out NativeObjectWrapper? wrapper) && !wrapper._aggregatedManagedObjectWrapper; + } + + private static unsafe IntPtr ObjectToComWeakRef(object target, out long wrapperId) + { + if (TryGetComInstanceForIID( + target, + IID_IWeakReferenceSource, + out IntPtr weakReferenceSourcePtr, + out wrapperId)) + { + using ComHolder weakReferenceSource = new ComHolder(weakReferenceSourcePtr); + if (IWeakReferenceSource.GetWeakReference(weakReferenceSource.Ptr, out IntPtr weakReference) == HResults.S_OK) + { + return weakReference; + } + } + + return IntPtr.Zero; + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.NativeAot.cs new file mode 100644 index 00000000000000..658e894870a6bb --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.NativeAot.cs @@ -0,0 +1,474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using static System.Runtime.InteropServices.ComWrappers; + +namespace System.Runtime.InteropServices +{ + internal static class TrackerObjectManager + { + internal static readonly IntPtr s_findReferencesTargetCallback = FindReferenceTargetsCallback.CreateFindReferenceTargetsCallback(); + internal static readonly IntPtr s_globalHostServices = CreateHostServices(); + + internal static volatile IntPtr s_trackerManager; + internal static volatile bool s_hasTrackingStarted; + internal static volatile bool s_isGlobalPeggingOn = true; + + internal static DependentHandleList s_referenceCache; + + // Used during GC callback + // Indicates if walking the external objects is needed. + // (i.e. Have any IReferenceTracker instances been found?) + public static bool ShouldWalkExternalObjects() + { + return s_trackerManager != IntPtr.Zero; + } + + // Called when an IReferenceTracker instance is found. + public static void OnIReferenceTrackerFound(IntPtr referenceTracker) + { + Debug.Assert(referenceTracker != IntPtr.Zero); + if (s_trackerManager != IntPtr.Zero) + { + return; + } + + IReferenceTracker.GetReferenceTrackerManager(referenceTracker, out IntPtr referenceTrackerManager); + + // Attempt to set the tracker instance. + // If set, the ownership of referenceTrackerManager has been transferred + if (Interlocked.CompareExchange(ref s_trackerManager, referenceTrackerManager, IntPtr.Zero) == IntPtr.Zero) + { + IReferenceTrackerManager.SetReferenceTrackerHost(s_trackerManager, s_globalHostServices); + + // Our GC callbacks are used only for reference walk of tracker objects, so register it here + // when we find our first tracker object. + RegisterGCCallbacks(); + } + else + { + Marshal.Release(referenceTrackerManager); + } + } + + // Called after wrapper has been created. + public static void AfterWrapperCreated(IntPtr referenceTracker) + { + Debug.Assert(referenceTracker != IntPtr.Zero); + + // Notify tracker runtime that we've created a new wrapper for this object. + // To avoid surprises, we should notify them before we fire the first AddRefFromTrackerSource. + IReferenceTracker.ConnectFromTrackerSource(referenceTracker); + + // Send out AddRefFromTrackerSource callbacks to notify tracker runtime we've done AddRef() + // for certain interfaces. We should do this *after* we made a AddRef() because we should never + // be in a state where report refs > actual refs + IReferenceTracker.AddRefFromTrackerSource(referenceTracker); // IUnknown + IReferenceTracker.AddRefFromTrackerSource(referenceTracker); // IReferenceTracker + } + + // Used during GC callback + // Called before wrapper is about to be finalized (the same lifetime as short weak handle). + public static void BeforeWrapperFinalized(IntPtr referenceTracker) + { + Debug.Assert(referenceTracker != IntPtr.Zero); + + // Notify tracker runtime that we are about to finalize a wrapper + // (same timing as short weak handle) for this object. + // They need this information to disconnect weak refs and stop firing events, + // so that they can avoid resurrecting the object. + IReferenceTracker.DisconnectFromTrackerSource(referenceTracker); + } + + // Used during GC callback + // Begin the reference tracking process for external objects. + public static void BeginReferenceTracking() + { + if (!ShouldWalkExternalObjects()) + { + return; + } + + Debug.Assert(!s_hasTrackingStarted); + Debug.Assert(s_isGlobalPeggingOn); + + s_hasTrackingStarted = true; + + // Let the tracker runtime know we are about to walk external objects so that + // they can lock their reference cache. Note that the tracker runtime doesn't need to + // unpeg all external objects at this point and they can do the pegging/unpegging. + // in FindTrackerTargetsCompleted. + Debug.Assert(s_trackerManager != IntPtr.Zero); + IReferenceTrackerManager.ReferenceTrackingStarted(s_trackerManager); + + // From this point, the tracker runtime decides whether a target + // should be pegged or not as the global pegging flag is now off. + s_isGlobalPeggingOn = false; + + // Time to walk the external objects + WalkExternalTrackerObjects(); + } + + // Used during GC callback + // End the reference tracking process for external object. + public static void EndReferenceTracking() + { + if (!s_hasTrackingStarted || !ShouldWalkExternalObjects()) + { + return; + } + + // Let the tracker runtime know the external object walk is done and they need to: + // 1. Unpeg all managed object wrappers (mow) if the (mow) needs to be unpegged + // (i.e. when the (mow) is only reachable by other external tracker objects). + // 2. Peg all mows if the mow needs to be pegged (i.e. when the above condition is not true) + // 3. Unlock reference cache when they are done. + Debug.Assert(s_trackerManager != IntPtr.Zero); + IReferenceTrackerManager.ReferenceTrackingCompleted(s_trackerManager); + + s_isGlobalPeggingOn = true; + s_hasTrackingStarted = false; + } + + public static unsafe void RegisterGCCallbacks() + { + delegate* unmanaged gcStartCallback = &GCStartCollection; + delegate* unmanaged gcStopCallback = &GCStopCollection; + delegate* unmanaged gcAfterMarkCallback = &GCAfterMarkPhase; + + if (!RuntimeImports.RhRegisterGcCallout(RuntimeImports.GcRestrictedCalloutKind.StartCollection, (IntPtr)gcStartCallback) || + !RuntimeImports.RhRegisterGcCallout(RuntimeImports.GcRestrictedCalloutKind.EndCollection, (IntPtr)gcStopCallback) || + !RuntimeImports.RhRegisterGcCallout(RuntimeImports.GcRestrictedCalloutKind.AfterMarkPhase, (IntPtr)gcAfterMarkCallback)) + { + throw new OutOfMemoryException(); + } + } + + public static bool AddReferencePath(object target, object foundReference) + { + return s_referenceCache.AddDependentHandle(target, foundReference); + } + + // Used during GC callback + [UnmanagedCallersOnly] + private static void GCStartCollection(int condemnedGeneration) + { + if (condemnedGeneration >= 2) + { + s_referenceCache.Reset(); + + BeginReferenceTracking(); + } + } + + // Used during GC callback + [UnmanagedCallersOnly] + private static void GCStopCollection(int condemnedGeneration) + { + if (condemnedGeneration >= 2) + { + EndReferenceTracking(); + } + } + + // Used during GC callback + [UnmanagedCallersOnly] + private static void GCAfterMarkPhase(int condemnedGeneration) + { + DetachNonPromotedObjects(); + } + + private static unsafe IntPtr CreateHostServices() + { + IntPtr* wrapperMem = (IntPtr*)NativeMemory.Alloc((nuint)sizeof(IntPtr)); + wrapperMem[0] = CreateDefaultIReferenceTrackerHostVftbl(); + return (IntPtr)wrapperMem; + } + } + + // Wrapper for IReferenceTrackerManager + internal static unsafe class IReferenceTrackerManager + { + // Used during GC callback + public static int ReferenceTrackingStarted(IntPtr pThis) + { + return (*(delegate* unmanaged**)pThis)[3](pThis); + } + + // Used during GC callback + public static int FindTrackerTargetsCompleted(IntPtr pThis, bool walkFailed) + { + return (*(delegate* unmanaged**)pThis)[4](pThis, walkFailed); + } + + // Used during GC callback + public static int ReferenceTrackingCompleted(IntPtr pThis) + { + return (*(delegate* unmanaged**)pThis)[5](pThis); + } + + public static void SetReferenceTrackerHost(IntPtr pThis, IntPtr referenceTrackerHost) + { + Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[6](pThis, referenceTrackerHost)); + } + } + + // Wrapper for IReferenceTracker + internal static unsafe class IReferenceTracker + { + public static void ConnectFromTrackerSource(IntPtr pThis) + { + Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[3](pThis)); + } + + // Used during GC callback + public static int DisconnectFromTrackerSource(IntPtr pThis) + { + return (*(delegate* unmanaged**)pThis)[4](pThis); + } + + // Used during GC callback + public static int FindTrackerTargets(IntPtr pThis, IntPtr findReferenceTargetsCallback) + { + return (*(delegate* unmanaged**)pThis)[5](pThis, findReferenceTargetsCallback); + } + + public static void GetReferenceTrackerManager(IntPtr pThis, out IntPtr referenceTrackerManager) + { + fixed (IntPtr* ptr = &referenceTrackerManager) + Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[6](pThis, ptr)); + } + + public static void AddRefFromTrackerSource(IntPtr pThis) + { + Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[7](pThis)); + } + + public static void ReleaseFromTrackerSource(IntPtr pThis) + { + Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[8](pThis)); + } + + public static void PegFromTrackerSource(IntPtr pThis) + { + Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[9](pThis)); + } + } + + // Callback implementation of IFindReferenceTargetsCallback + internal static unsafe class FindReferenceTargetsCallback + { + internal static GCHandle s_currentRootObjectHandle; + + [UnmanagedCallersOnly] + private static unsafe int IFindReferenceTargetsCallback_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) + { + if (*guid == IID_IFindReferenceTargetsCallback || *guid == IID_IUnknown) + { + *ppObject = pThis; + return HResults.S_OK; + } + else + { + return HResults.COR_E_INVALIDCAST; + } + } + + [UnmanagedCallersOnly] + private static unsafe int IFindReferenceTargetsCallback_FoundTrackerTarget(IntPtr pThis, IntPtr referenceTrackerTarget) + { + if (referenceTrackerTarget == IntPtr.Zero) + { + return HResults.E_INVALIDARG; + } + + if (TryGetObject(referenceTrackerTarget, out object? foundObject)) + { + // Notify the runtime a reference path was found. + return TrackerObjectManager.AddReferencePath(s_currentRootObjectHandle.Target, foundObject) ? HResults.S_OK : HResults.S_FALSE; + } + + return HResults.S_OK; + } + + private static unsafe IntPtr CreateDefaultIFindReferenceTargetsCallbackVftbl() + { + IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(FindReferenceTargetsCallback), 4 * sizeof(IntPtr)); + vftbl[0] = (IntPtr)(delegate* unmanaged)&IFindReferenceTargetsCallback_QueryInterface; + vftbl[1] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_AddRef; + vftbl[2] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_Release; + vftbl[3] = (IntPtr)(delegate* unmanaged)&IFindReferenceTargetsCallback_FoundTrackerTarget; + return (IntPtr)vftbl; + } + + internal static unsafe IntPtr CreateFindReferenceTargetsCallback() + { + IntPtr* wrapperMem = (IntPtr*)NativeMemory.Alloc((nuint)sizeof(IntPtr)); + wrapperMem[0] = CreateDefaultIFindReferenceTargetsCallbackVftbl(); + return (IntPtr)wrapperMem; + } + } + + internal readonly struct ComHolder : IDisposable + { + private readonly IntPtr _ptr; + + internal readonly IntPtr Ptr => _ptr; + + public ComHolder(IntPtr ptr) + { + _ptr = ptr; + } + + public readonly void Dispose() + { + Marshal.Release(_ptr); + } + } + + // This is used during a GC callback so it needs to be free of any managed allocations. + internal unsafe struct DependentHandleList + { + private int _freeIndex; // The next available slot + private int _capacity; // Total numbers of slots available in the list + private IntPtr* _pHandles; // All handles + private int _shrinkHint; // How many times we've consistently seen "hints" that a + // shrink is needed + + private const int DefaultCapacity = 100; // Default initial capacity of this list + private const int ShrinkHintThreshold = 10; // The number of hints we've seen before we really + // shrink the list + + public bool AddDependentHandle(object target, object dependent) + { + if (_freeIndex >= _capacity) + { + // We need a bigger dependent handle array + if (!Grow()) + return false; + } + + IntPtr handle = _pHandles[_freeIndex]; + if (handle != default) + { + RuntimeImports.RhHandleSet(handle, target); + RuntimeImports.RhHandleSetDependentSecondary(handle, dependent); + } + else + { + _pHandles[_freeIndex] = RuntimeImports.RhpHandleAllocDependent(target, dependent); + if (_pHandles[_freeIndex] == default) + { + return false; + } + } + + _freeIndex++; + return true; + } + + public bool Reset() + { + // Allocation for the first time + if (_pHandles == null) + { + _capacity = DefaultCapacity; +#if TARGET_WINDOWS + _pHandles = (IntPtr*)Interop.Ucrtbase.calloc((nuint)_capacity, (nuint)sizeof(IntPtr)); +#else + _pHandles = (IntPtr*)Interop.Sys.Calloc((nuint)_capacity, (nuint)sizeof(IntPtr)); +#endif + + return _pHandles != null; + } + + // If we are not using half of the handles last time, it is a hint that probably we need to shrink + if (_freeIndex < _capacity / 2 && _capacity > DefaultCapacity) + { + _shrinkHint++; + + // Only shrink if we consistently seen such hint more than ShrinkHintThreshold times + if (_shrinkHint > ShrinkHintThreshold) + { + Shrink(); + _shrinkHint = 0; + } + } + else + { + // Reset shrink hint and start over the counting + _shrinkHint = 0; + } + + // Clear all the handles that were used + for (int index = 0; index < _freeIndex; index++) + { + IntPtr handle = _pHandles[index]; + if (handle != default) + { + RuntimeImports.RhHandleSet(handle, null); + RuntimeImports.RhHandleSetDependentSecondary(handle, null); + } + } + + _freeIndex = 0; + return true; + } + + private bool Shrink() + { + int newCapacity = _capacity / 2; + + // Free all handles that will go away + for (int index = newCapacity; index < _capacity; index++) + { + if (_pHandles[index] != default) + { + RuntimeImports.RhHandleFree(_pHandles[index]); + // Assign them back to null in case the reallocation fails + _pHandles[index] = default; + } + } + + // Shrink the size of the memory +#if TARGET_WINDOWS + IntPtr* pNewHandles = (IntPtr*)Interop.Ucrtbase.realloc(_pHandles, (nuint)(newCapacity * sizeof(IntPtr))); +#else + IntPtr* pNewHandles = (IntPtr*)Interop.Sys.Realloc(_pHandles, (nuint)(newCapacity * sizeof(IntPtr))); +#endif + if (pNewHandles == null) + return false; + + _pHandles = pNewHandles; + _capacity = newCapacity; + + return true; + } + + private bool Grow() + { + int newCapacity = _capacity * 2; +#if TARGET_WINDOWS + IntPtr* pNewHandles = (IntPtr*)Interop.Ucrtbase.realloc(_pHandles, (nuint)(newCapacity * sizeof(IntPtr))); +#else + IntPtr* pNewHandles = (IntPtr*)Interop.Sys.Realloc(_pHandles, (nuint)(newCapacity * sizeof(IntPtr))); +#endif + if (pNewHandles == null) + return false; + + for (int index = _capacity; index < newCapacity; index++) + { + pNewHandles[index] = default; + } + + _pHandles = pNewHandles; + _capacity = newCapacity; + + return true; + } + } +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 9c652dd69bb9aa..9f486a4901fb6e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -65,7 +65,7 @@ public static partial class RuntimeImports // Force a garbage collection. [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhCollect")] - internal static extern void RhCollect(int generation, InternalGCCollectionMode mode); + internal static extern void RhCollect(int generation, InternalGCCollectionMode mode, bool lowMemoryP = false); // Mark an object instance as already finalized. [MethodImpl(MethodImplOptions.InternalCall)] @@ -129,6 +129,10 @@ internal static void RhWaitForPendingFinalizers(bool allowReentrantWait) [RuntimeImport(RuntimeLibrary, "RhSetGcLatencyMode")] internal static extern int RhSetGcLatencyMode(GCLatencyMode newLatencyMode); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhIsPromoted")] + internal static extern bool RhIsPromoted(object obj); + [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhIsServerGc")] internal static extern bool RhIsServerGc(); @@ -293,7 +297,7 @@ internal static IntPtr RhHandleAllocRefCounted(object value) // Allocate handle for dependent handle case where a secondary can be set at the same time. [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhpHandleAllocDependent")] - private static extern IntPtr RhpHandleAllocDependent(object primary, object secondary); + internal static extern IntPtr RhpHandleAllocDependent(object primary, object secondary); internal static IntPtr RhHandleAllocDependent(object primary, object secondary) { diff --git a/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetContextToken.cs b/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetContextToken.cs new file mode 100644 index 00000000000000..64ffb469e766fe --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Ole32/Interop.CoGetContextToken.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Ole32 + { + [LibraryImport(Libraries.Ole32)] + internal static unsafe partial int CoGetContextToken(out IntPtr pToken); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index bd016b716a1617..5f3d727748835e 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4181,6 +4181,9 @@ COM Interop requires ComWrapper instance registered for marshalling. + + COM Interop requires ComWrapper instance registered for reference tracker support. + Must not be greater than the length of the buffer. diff --git a/src/tests/Interop/COM/ComWrappers/API/Program.cs b/src/tests/Interop/COM/ComWrappers/API/Program.cs index c7c4cba921fe74..5f987f567fe3cd 100644 --- a/src/tests/Interop/COM/ComWrappers/API/Program.cs +++ b/src/tests/Interop/COM/ComWrappers/API/Program.cs @@ -927,7 +927,6 @@ public static WeakReference AllocateAndUseBaseType(ComWrappers cw, bool } } - [ActiveIssue("https://github.com/dotnet/runtime/issues/85137", typeof(Utilities), nameof(Utilities.IsNativeAot))] [Fact] public void ValidateAggregationWithComObject() { @@ -944,7 +943,6 @@ public void ValidateAggregationWithComObject() Assert.Equal(0, allocTracker.GetCount()); } - [ActiveIssue("https://github.com/dotnet/runtime/issues/85137", typeof(Utilities), nameof(Utilities.IsNativeAot))] [Fact] public void ValidateAggregationWithReferenceTrackerObject() {