diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index a4ddab439f6c07..854e13fedb8a97 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -254,7 +254,8 @@ - + + diff --git a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs index de9aeddf403054..de9908401cba8b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -10,46 +10,56 @@ namespace System internal sealed partial class ComAwareWeakReference { [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWeakRefToObject")] - private static partial void ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId, ObjectHandleOnStack retRcw); + private static partial void ComWeakRefToObject(IntPtr pComWeakRef, ObjectHandleOnStack retRcw); - internal static object? ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId) + internal static object? ComWeakRefToObject(IntPtr pComWeakRef, object? context) { - object? retRcw = null; - ComWeakRefToObject(pComWeakRef, wrapperId, ObjectHandleOnStack.Create(ref retRcw)); - return retRcw; +#if FEATURE_COMINTEROP + if (context is null) + { + // This wrapper was not created by ComWrappers, so we try to rehydrate using built-in COM. + object? retRcw = null; + ComWeakRefToObject(pComWeakRef, ObjectHandleOnStack.Create(ref retRcw)); + return retRcw; + } +#endif // FEATURE_COMINTEROP + + return ComWeakRefToComWrappersObject(pComWeakRef, context); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe bool PossiblyComObject(object target) { - // see: syncblk.h - const int IS_HASHCODE_BIT_NUMBER = 26; - const int BIT_SBLK_IS_HASHCODE = 1 << IS_HASHCODE_BIT_NUMBER; - const int BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX = 0x08000000; - - fixed (byte* pRawData = &target.GetRawData()) - { - // The header is 4 bytes before MT field on all architectures - int header = *(int*)(pRawData - sizeof(IntPtr) - sizeof(int)); - // common case: target does not have a syncblock, so there is no interop info - return (header & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) == BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX; - } +#if FEATURE_COMINTEROP + return target is __ComObject || PossiblyComWrappersObject(target); +#else // !FEATURE_COMINTEROP + // If we are not using built-in COM, then we can only be a ComWrappers object. + return PossiblyComWrappersObject(target); +#endif // FEATURE_COMINTEROP } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool HasInteropInfo(object target); - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjectToComWeakRef")] - private static partial IntPtr ObjectToComWeakRef(ObjectHandleOnStack retRcw, out long wrapperId); + private static partial IntPtr ObjectToComWeakRef(ObjectHandleOnStack retRcw); - internal static nint ObjectToComWeakRef(object target, out long wrapperId) + internal static nint ObjectToComWeakRef(object target, out object? context) { - if (HasInteropInfo(target)) +#if FEATURE_COMINTEROP + if (target is __ComObject) + { + // This object is using built-in COM, so use built-in COM to create the weak reference. + context = null; + return ObjectToComWeakRef(ObjectHandleOnStack.Create(ref target)); + } +#endif // FEATURE_COMINTEROP + + if (PossiblyComWrappersObject(target)) { - return ObjectToComWeakRef(ObjectHandleOnStack.Create(ref target), out wrapperId); + return ComWrappersObjectToComWeakRef(target, out context); } - wrapperId = 0; + // This object is not produced using built-in COM or ComWrappers + // or is an aggregated object, so we cannot create a weak reference. + context = null; return IntPtr.Zero; } } diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 184ce80f44b899..101fbfa7c6bc45 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -106,7 +106,7 @@ internal enum GC_ALLOC_FLAGS private static partial long GetTotalMemory(); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_Collect")] - private static partial void _Collect(int generation, int mode); + private static partial void _Collect(int generation, int mode, [MarshalAs(UnmanagedType.U1)] bool lowMemoryPressure); [MethodImpl(MethodImplOptions.InternalCall)] private static extern int GetMaxGeneration(); @@ -174,7 +174,7 @@ public static void Collect(int generation) public static void Collect() { // -1 says to GC all generations. - _Collect(-1, (int)InternalGCCollectionMode.Blocking); + _Collect(-1, (int)InternalGCCollectionMode.Blocking, lowMemoryPressure: false); } public static void Collect(int generation, GCCollectionMode mode) @@ -189,6 +189,11 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking) } public static void Collect(int generation, GCCollectionMode mode, bool blocking, bool compacting) + { + Collect(generation, mode, blocking, compacting, lowMemoryPressure: false); + } + + internal static void Collect(int generation, GCCollectionMode mode, bool blocking, bool compacting, bool lowMemoryPressure) { ArgumentOutOfRangeException.ThrowIfNegative(generation); @@ -197,7 +202,6 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, throw new ArgumentOutOfRangeException(nameof(mode), SR.ArgumentOutOfRange_Enum); } - int iInternalModes = 0; if (mode == GCCollectionMode.Optimized) @@ -222,7 +226,9 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, } if (compacting) + { iInternalModes |= (int)InternalGCCollectionMode.Compacting; + } if (blocking) { @@ -233,7 +239,7 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, iInternalModes |= (int)InternalGCCollectionMode.NonBlocking; } - _Collect(generation, iInternalModes); + _Collect(generation, (int)iInternalModes, lowMemoryPressure); } public static int CollectionCount(int generation) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreCLR.cs new file mode 100644 index 00000000000000..5f89431729f0aa --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreCLR.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Runtime.CompilerServices; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace System.Runtime.InteropServices +{ + /// + /// Class for managing wrappers of COM IUnknown types. + /// + public abstract partial class ComWrappers + { + /// + /// Get the runtime provided IUnknown implementation. + /// + /// Function pointer to QueryInterface. + /// Function pointer to AddRef. + /// Function pointer to Release. + public static unsafe void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) + => GetIUnknownImplInternal(out fpQueryInterface, out fpAddRef, out fpRelease); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIUnknownImpl")] + [SuppressGCTransition] + private static partial void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); + + internal static IntPtr DefaultIUnknownVftblPtr { get; } = CreateDefaultIUnknownVftbl(); + internal static IntPtr TaggedImplVftblPtr { get; } = CreateTaggedImplVftbl(); + internal static IntPtr DefaultIReferenceTrackerTargetVftblPtr { get; } = CreateDefaultIReferenceTrackerTargetVftbl(); + + private static unsafe IntPtr CreateDefaultIUnknownVftbl() + { + IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ComWrappers), 3 * sizeof(IntPtr)); + GetIUnknownImpl(out vftbl[0], out vftbl[1], out vftbl[2]); + return (IntPtr)vftbl; + } + + private static unsafe IntPtr CreateTaggedImplVftbl() + { + IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ComWrappers), 4 * sizeof(IntPtr)); + GetIUnknownImpl(out vftbl[0], out vftbl[1], out vftbl[2]); + vftbl[3] = GetTaggedImplCurrentVersion(); + return (IntPtr)vftbl; + } + + internal static int CallICustomQueryInterface(ManagedObjectWrapperHolder holder, ref Guid iid, out IntPtr ppObject) + { + if (holder.WrappedObject is ICustomQueryInterface customQueryInterface) + { + return (int)customQueryInterface.GetInterface(ref iid, out ppObject); + } + + ppObject = IntPtr.Zero; + return -1; // See TryInvokeICustomQueryInterfaceResult + } + + internal static IntPtr GetOrCreateComInterfaceForObjectWithGlobalMarshallingInstance(object obj) + { + try + { + return s_globalInstanceForMarshalling is null + ? IntPtr.Zero + : s_globalInstanceForMarshalling.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.TrackerSupport); + } + catch (ArgumentException) + { + // We've failed to create a COM interface for the object. + // Fallback to built-in COM. + return IntPtr.Zero; + } + } + + internal static object? GetOrCreateObjectForComInstanceWithGlobalMarshallingInstance(IntPtr comObject, CreateObjectFlags flags) + { + try + { + return s_globalInstanceForMarshalling?.GetOrCreateObjectForComInstance(comObject, flags); + } + catch (ArgumentNullException) + { + // We've failed to create a managed object for the COM instance. + // Fallback to built-in COM. + return null; + } + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIReferenceTrackerTargetVftbl")] + [SuppressGCTransition] + private static partial IntPtr GetDefaultIReferenceTrackerTargetVftbl(); + + private static IntPtr CreateDefaultIReferenceTrackerTargetVftbl() + => GetDefaultIReferenceTrackerTargetVftbl(); + + private static IntPtr GetTaggedImplCurrentVersion() + { + return GetTaggedImpl(); + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetTaggedImpl")] + [SuppressGCTransition] + private static partial IntPtr GetTaggedImpl(); + + internal sealed partial class ManagedObjectWrapperHolder + { + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterIsRootedCallback")] + private static partial void RegisterIsRootedCallback(); + + private static IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) + { + return AllocateRefCountedHandle(ObjectHandleOnStack.Create(ref holder)); + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_AllocateRefCountedHandle")] + private static partial IntPtr AllocateRefCountedHandle(ObjectHandleOnStack obj); + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs deleted file mode 100644 index c7c78a7325f998..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ /dev/null @@ -1,396 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.Versioning; -using System.Threading; - -namespace System.Runtime.InteropServices -{ - /// - /// Internal enumeration used by the runtime to indicate the scenario for which ComWrappers is being used. - /// - internal enum ComWrappersScenario - { - Instance = 0, - TrackerSupportGlobalInstance = 1, - MarshallingGlobalInstance = 2, - } - - /// - /// Class for managing wrappers of COM IUnknown types. - /// - public abstract partial class ComWrappers - { - /// - /// Given a managed object, determine if it is a -created - /// managed wrapper and if so, return the wrapped unmanaged pointer. - /// - /// A managed wrapper - /// An unmanaged COM object - /// True if the wrapper was resolved to an external COM object, otherwise false. - /// - /// If a COM object is returned, the caller is expected to call Release() on the object. - /// This can be done through an API like . - /// Since this API is required to interact directly with the external COM object, QueryInterface(), - /// it is important for the caller to understand the COM object may have apartment affinity and therefore - /// if the current thread is not in the correct apartment or the COM object is not a proxy this call may fail. - /// - public static bool TryGetComInstance(object obj, out IntPtr unknown) - { - if (obj == null) - { - unknown = IntPtr.Zero; - return false; - } - - return TryGetComInstanceInternal(ObjectHandleOnStack.Create(ref obj), out unknown); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_TryGetComInstance")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool TryGetComInstanceInternal(ObjectHandleOnStack wrapperMaybe, out IntPtr externalComObject); - - /// - /// Given a COM object, determine if it is a -created - /// unmanaged wrapper and if so, return the wrapped managed object. - /// - /// An unmanaged wrapper - /// A managed object - /// True if the wrapper was resolved to a managed object, otherwise false. - public static bool TryGetObject(IntPtr unknown, [NotNullWhen(true)] out object? obj) - { - obj = null; - if (unknown == IntPtr.Zero) - { - return false; - } - - return TryGetObjectInternal(unknown, ObjectHandleOnStack.Create(ref obj)); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_TryGetObject")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool TryGetObjectInternal(IntPtr wrapperMaybe, ObjectHandleOnStack instance); - - /// - /// ABI for function dispatch of a COM interface. - /// - public partial struct ComInterfaceDispatch - { - /// - /// Given a from a generated Vtable, convert to the target type. - /// - /// Desired type. - /// Pointer supplied to Vtable function entry. - /// Instance of type associated with dispatched function call. - public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class - { - // See the dispatch section in the runtime for details on the masking below. - long DispatchAlignmentThisPtr = sizeof(void*) == 8 ? 64 : 16; // Should be a power of 2. - long DispatchThisPtrMask = ~(DispatchAlignmentThisPtr - 1); - var comInstance = *(ComInterfaceInstance**)(((long)dispatchPtr) & DispatchThisPtrMask); - - return Unsafe.As(GCHandle.InternalGet(comInstance->GcHandle)); - } - - private struct ComInterfaceInstance - { - public IntPtr GcHandle; - } - } - - /// - /// Globally registered instance of the ComWrappers class for reference tracker support. - /// - private static ComWrappers? s_globalInstanceForTrackerSupport; - - /// - /// Globally registered instance of the ComWrappers class for marshalling. - /// - private static ComWrappers? s_globalInstanceForMarshalling; - - private static long s_instanceCounter; - private readonly long id = Interlocked.Increment(ref s_instanceCounter); - - /// - /// Create a COM representation of the supplied object that can be passed to a non-managed environment. - /// - /// The managed object to expose outside the .NET runtime. - /// Flags used to configure the generated interface. - /// The generated COM interface that can be passed outside the .NET runtime. - /// - /// If a COM representation was previously created for the specified using - /// this instance, the previously created COM interface will be returned. - /// If not, a new one will be created. - /// - public IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags) - { - IntPtr ptr; - if (!TryGetOrCreateComInterfaceForObjectInternal(this, instance, flags, out ptr)) - throw new ArgumentException(null, nameof(instance)); - - return ptr; - } - - /// - /// Create a COM representation of the supplied object that can be passed to a non-managed environment. - /// - /// The implementation to use when creating the COM representation. - /// The managed object to expose outside the .NET runtime. - /// Flags used to configure the generated interface. - /// The generated COM interface that can be passed outside the .NET runtime or IntPtr.Zero if it could not be created. - /// Returns true if a COM representation could be created, false otherwise - /// - /// If is null, the global instance (if registered) will be used. - /// - private static bool TryGetOrCreateComInterfaceForObjectInternal(ComWrappers impl, object instance, CreateComInterfaceFlags flags, out IntPtr retValue) - { - ArgumentNullException.ThrowIfNull(instance); - - return TryGetOrCreateComInterfaceForObjectInternal(ObjectHandleOnStack.Create(ref impl), impl.id, ObjectHandleOnStack.Create(ref instance), flags, out retValue); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_TryGetOrCreateComInterfaceForObject")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool TryGetOrCreateComInterfaceForObjectInternal(ObjectHandleOnStack comWrappersImpl, long wrapperId, ObjectHandleOnStack instance, CreateComInterfaceFlags flags, out IntPtr retValue); - - // Called by the runtime to execute the abstract instance function - internal static unsafe void* CallComputeVtables(ComWrappersScenario scenario, ComWrappers? comWrappersImpl, object obj, CreateComInterfaceFlags flags, out int count) - { - ComWrappers? impl = null; - switch (scenario) - { - case ComWrappersScenario.Instance: - impl = comWrappersImpl; - break; - case ComWrappersScenario.TrackerSupportGlobalInstance: - impl = s_globalInstanceForTrackerSupport; - break; - case ComWrappersScenario.MarshallingGlobalInstance: - impl = s_globalInstanceForMarshalling; - break; - } - - if (impl is null) - { - count = -1; - return null; - } - - return impl.ComputeVtables(obj, flags, out count); - } - - /// - /// Get the currently registered managed object or creates a new managed object and registers it. - /// - /// Object to import for usage into the .NET runtime. - /// Flags used to describe the external object. - /// Returns a managed object associated with the supplied external COM object. - /// - /// If a managed object was previously created for the specified - /// using this instance, the previously created object will be returned. - /// If not, a new one will be created. - /// - public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags) - { - object? obj; - if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, IntPtr.Zero, flags, null, out obj)) - throw new ArgumentNullException(nameof(externalComObject)); - - return obj!; - } - - // Called by the runtime to execute the abstract instance function. - internal static object? CallCreateObject(ComWrappersScenario scenario, ComWrappers? comWrappersImpl, IntPtr externalComObject, CreateObjectFlags flags) - { - ComWrappers? impl = null; - switch (scenario) - { - case ComWrappersScenario.Instance: - impl = comWrappersImpl; - break; - case ComWrappersScenario.TrackerSupportGlobalInstance: - impl = s_globalInstanceForTrackerSupport; - break; - case ComWrappersScenario.MarshallingGlobalInstance: - impl = s_globalInstanceForMarshalling; - break; - } - - if (impl == null) - return null; - - return impl.CreateObject(externalComObject, flags); - } - - /// - /// Get the currently registered managed object or uses the supplied managed object and registers it. - /// - /// Object to import for usage into the .NET runtime. - /// Flags used to describe the external object. - /// The to be used as the wrapper for the external object - /// Returns a managed object associated with the supplied external COM object. - /// - /// If the instance already has an associated external object a will be thrown. - /// - public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper) - { - return GetOrRegisterObjectForComInstance(externalComObject, flags, wrapper, IntPtr.Zero); - } - - /// - /// Get the currently registered managed object or uses the supplied managed object and registers it. - /// - /// Object to import for usage into the .NET runtime. - /// Flags used to describe the external object. - /// The to be used as the wrapper for the external object - /// Inner for COM aggregation scenarios - /// Returns a managed object associated with the supplied external COM object. - /// - /// This method override is for registering an aggregated COM instance with its associated inner. The inner - /// will be released when the associated wrapper is eventually freed. Note that it will be released on a thread - /// in an unknown apartment state. If the supplied inner is not known to be a free-threaded instance then - /// it is advised to not supply the inner. - /// - /// If the instance already has an associated external object a will be thrown. - /// - public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper, IntPtr inner) - { - ArgumentNullException.ThrowIfNull(wrapper); - - object? obj; - if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, inner, flags, wrapper, out obj)) - throw new ArgumentNullException(nameof(externalComObject)); - - return obj!; - } - - /// - /// Get the currently registered managed object or creates a new managed object and registers it. - /// - /// The implementation to use when creating the managed object. - /// Object to import for usage into the .NET runtime. - /// The inner instance if aggregation is involved - /// Flags used to describe the external object. - /// The to be used as the wrapper for the external object. - /// The managed object associated with the supplied external COM object or null if it could not be created. - /// Returns true if a managed object could be retrieved/created, false otherwise - /// - /// If is null, the global instance (if registered) will be used. - /// - private static bool TryGetOrCreateObjectForComInstanceInternal( - ComWrappers impl, - IntPtr externalComObject, - IntPtr innerMaybe, - CreateObjectFlags flags, - object? wrapperMaybe, - out object? retValue) - { - ArgumentNullException.ThrowIfNull(externalComObject); - - // If the inner is supplied the Aggregation flag should be set. - if (innerMaybe != IntPtr.Zero && !flags.HasFlag(CreateObjectFlags.Aggregation)) - throw new InvalidOperationException(SR.InvalidOperation_SuppliedInnerMustBeMarkedAggregation); - - object? wrapperMaybeLocal = wrapperMaybe; - retValue = null; - return TryGetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack.Create(ref impl), impl.id, externalComObject, innerMaybe, flags, ObjectHandleOnStack.Create(ref wrapperMaybeLocal), ObjectHandleOnStack.Create(ref retValue)); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_TryGetOrCreateObjectForComInstance")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool TryGetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack comWrappersImpl, long wrapperId, IntPtr externalComObject, IntPtr innerMaybe, CreateObjectFlags flags, ObjectHandleOnStack wrapper, ObjectHandleOnStack retValue); - - // Call to execute the virtual instance function - internal static void CallReleaseObjects(ComWrappers? comWrappersImpl, IEnumerable objects) - => (comWrappersImpl ?? s_globalInstanceForTrackerSupport!).ReleaseObjects(objects); - - /// - /// Register a instance to be used as the global instance for reference tracker support. - /// - /// Instance to register - /// - /// This function can only be called a single time. Subsequent calls to this function will result - /// in a being thrown. - /// - /// Scenarios where this global instance may be used are: - /// * Object tracking via the and flags. - /// - public static void RegisterForTrackerSupport(ComWrappers instance) - { - ArgumentNullException.ThrowIfNull(instance); - - if (null != Interlocked.CompareExchange(ref s_globalInstanceForTrackerSupport, instance, null)) - { - throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); - } - - SetGlobalInstanceRegisteredForTrackerSupport(instance.id); - } - - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_SetGlobalInstanceRegisteredForTrackerSupport")] - [SuppressGCTransition] - private static partial void SetGlobalInstanceRegisteredForTrackerSupport(long id); - - /// - /// Register a instance to be used as the global instance for marshalling in the runtime. - /// - /// Instance to register - /// - /// This function can only be called a single time. Subsequent calls to this function will result - /// in a being thrown. - /// - /// Scenarios where this global instance may be used are: - /// * Usage of COM-related Marshal APIs - /// * P/Invokes with COM-related types - /// * COM activation - /// - [SupportedOSPlatform("windows")] - public static void RegisterForMarshalling(ComWrappers instance) - { - ArgumentNullException.ThrowIfNull(instance); - - if (null != Interlocked.CompareExchange(ref s_globalInstanceForMarshalling, instance, null)) - { - throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); - } - - // Indicate to the runtime that a global instance has been registered for marshalling. - // This allows the native runtime know to call into the managed ComWrappers only if a - // global instance is registered for marshalling. - SetGlobalInstanceRegisteredForMarshalling(instance.id); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_SetGlobalInstanceRegisteredForMarshalling")] - [SuppressGCTransition] - private static partial void SetGlobalInstanceRegisteredForMarshalling(long id); - - /// - /// Get the runtime provided IUnknown implementation. - /// - /// Function pointer to QueryInterface. - /// Function pointer to AddRef. - /// Function pointer to Release. - public static void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) - => GetIUnknownImplInternal(out fpQueryInterface, out fpAddRef, out fpRelease); - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIUnknownImpl")] - private static partial void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); - - internal static int CallICustomQueryInterface(object customQueryInterfaceMaybe, ref Guid iid, out IntPtr ppObject) - { - if (customQueryInterfaceMaybe is ICustomQueryInterface customQueryInterface) - { - return (int)customQueryInterface.GetInterface(ref iid, out ppObject); - } - - ppObject = IntPtr.Zero; - return -1; // See TryInvokeICustomQueryInterfaceResult - } - } -} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.CoreCLR.cs new file mode 100644 index 00000000000000..ac60cd56719b64 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.CoreCLR.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace System.Runtime.InteropServices +{ + internal static partial class TrackerObjectManager + { + private static bool HasReferenceTrackerManager + => HasReferenceTrackerManagerInternal(); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_HasReferenceTrackerManager")] + [SuppressGCTransition] + [return: MarshalAs(UnmanagedType.U1)] + private static partial bool HasReferenceTrackerManagerInternal(); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_TryRegisterReferenceTrackerManager")] + [SuppressGCTransition] + [return: MarshalAs(UnmanagedType.U1)] + private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager); + + internal static bool IsGlobalPeggingEnabled + => IsGlobalPeggingEnabledInternal(); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_IsGlobalPeggingEnabled")] + [SuppressGCTransition] + [return: MarshalAs(UnmanagedType.U1)] + private static partial bool IsGlobalPeggingEnabledInternal(); + + private static void RegisterGCCallbacks() + { + // CoreCLR doesn't have GC callbacks, but we do need to register the GC handle set with the runtime for enumeration + // during GC. + GCHandleSet handleSet = s_referenceTrackerNativeObjectWrapperCache; + RegisterNativeObjectWrapperCache(ObjectHandleOnStack.Create(ref handleSet)); + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_RegisterNativeObjectWrapperCache")] + private static partial void RegisterNativeObjectWrapperCache(ObjectHandleOnStack nativeObjectWrapperCache); + } +} diff --git a/src/coreclr/classlibnative/bcltype/objectnative.cpp b/src/coreclr/classlibnative/bcltype/objectnative.cpp index 2a06cf22e737be..c9087be0db975f 100644 --- a/src/coreclr/classlibnative/bcltype/objectnative.cpp +++ b/src/coreclr/classlibnative/bcltype/objectnative.cpp @@ -45,24 +45,7 @@ FCIMPL1(INT32, ObjectNative::TryGetHashCode, Object* obj) return 0; OBJECTREF objRef = ObjectToOBJECTREF(obj); - DWORD bits = objRef->GetHeader()->GetBits(); - if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) - { - if (bits & BIT_SBLK_IS_HASHCODE) - { - // Common case: the object already has a hash code - return bits & MASK_HASHCODE; - } - else - { - // We have a sync block index. This means if we already have a hash code, - // it is in the sync block, otherwise we will return 0, which means "not set". - SyncBlock *psb = objRef->PassiveGetSyncBlock(); - if (psb != NULL) - return psb->GetHashCode(); - } - } - return 0; + return objRef->TryGetHashCode(); } FCIMPLEND diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index f516c0eda6f200..ef4a8246374e56 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1504,6 +1504,7 @@ class ClrDataAccess BOOL DACIsComWrappersCCW(CLRDATA_ADDRESS ccwPtr); TADDR DACGetManagedObjectWrapperFromCCW(CLRDATA_ADDRESS ccwPtr); HRESULT DACTryGetComWrappersObjectFromCCW(CLRDATA_ADDRESS ccwPtr, OBJECTREF* objRef); + TADDR GetIdentityForManagedObjectWrapper(TADDR mow); #endif protected: diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 96e38cc41b525f..99c157007ff20f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -14,6 +14,7 @@ #include "typestring.h" #include #include +#include #ifdef FEATURE_COMINTEROP #include @@ -22,9 +23,6 @@ #ifdef FEATURE_COMWRAPPERS #include #include - -typedef DPTR(InteropLibInterface::ExternalObjectContextBase) PTR_ExternalObjectContext; -typedef DPTR(InteropLib::ABI::ManagedObjectWrapperLayout) PTR_ManagedObjectWrapper; #endif // FEATURE_COMWRAPPERS #ifndef TARGET_UNIX @@ -4433,6 +4431,7 @@ ErrExit: return hr; HRESULT ClrDataAccess::DACTryGetComWrappersObjectFromCCW(CLRDATA_ADDRESS ccwPtr, OBJECTREF* objRef) { HRESULT hr = E_FAIL; + MOWHOLDERREF holder = NULL; if (ccwPtr == 0 || objRef == NULL) { @@ -4447,7 +4446,9 @@ HRESULT ClrDataAccess::DACTryGetComWrappersObjectFromCCW(CLRDATA_ADDRESS ccwPtr, goto ErrExit; } - *objRef = ObjectFromHandle(handle); + holder = (MOWHOLDERREF)ObjectFromHandle(handle); + + *objRef = holder->_wrappedObject; return S_OK; @@ -5048,6 +5049,66 @@ HRESULT ClrDataAccess::GetBreakingChangeVersion(int* pVersion) return S_OK; } +#ifdef FEATURE_COMWRAPPERS +namespace +{ + typedef DPTR(InteropLib::ABI::ComInterfaceEntry) PTR_ComInterfaceEntry; + + struct TargetManagedObjectWrapper : public InteropLib::ABI::ManagedObjectWrapperLayout + { + public: + InteropLib::Com::CreateComInterfaceFlagsEx GetFlags() + { + return _flags; + } + + PTR_ComInterfaceEntry GetUserDefined(int32_t* pNumEntries) + { + return dac_cast((TADDR)_userDefined); + } + + TADDR IndexIntoDispatchSection(int32_t index) + { + return (TADDR)InteropLib::ABI::IndexIntoDispatchSection(index, _dispatches); + } + + TADDR GetRuntimeDefinedIUnknown() + { + return (TADDR)InteropLib::ABI::IndexIntoDispatchSection(_userDefinedCount, _dispatches); + } + }; + + typedef DPTR(TargetManagedObjectWrapper) PTR_ManagedObjectWrapper; +} + +TADDR ClrDataAccess::GetIdentityForManagedObjectWrapper(TADDR mow) +{ + PTR_ManagedObjectWrapper pMOW = dac_cast(mow); + // Replicate the logic for ManagedObjectWrapper.As(IID_IUnknown) + if ((pMOW->GetFlags() & InteropLib::Com::CreateComInterfaceFlagsEx::CallerDefinedIUnknown) == InteropLib::Com::CreateComInterfaceFlagsEx::None) + { + // We have the standard IUnknown implementation, so grab it from its known location. + // The index returned from IndexIntoDispatchSection is in the target address space. + return pMOW->GetRuntimeDefinedIUnknown(); + } + + // We need to find the IUnknown interface pointer in the MOW. + int32_t userDefinedCount; + PTR_ComInterfaceEntry pUserDefined = pMOW->GetUserDefined(&userDefinedCount); + for (int32_t i = 0; i < userDefinedCount; i++) + { + if (pUserDefined[i].IID == IID_IUnknown) + { + // We found the IUnknown interface pointer. + // The index returned from IndexIntoDispatchSection is in the target address space. + return pMOW->IndexIntoDispatchSection(i); + } + } + + return (TADDR)NULL; +} +#endif // FEATURE_COMWRAPPERS + HRESULT ClrDataAccess::GetObjectComWrappersData(CLRDATA_ADDRESS objAddr, CLRDATA_ADDRESS *rcw, unsigned int count, CLRDATA_ADDRESS *mowList, unsigned int *pNeeded) { #ifdef FEATURE_COMWRAPPERS @@ -5062,6 +5123,10 @@ HRESULT ClrDataAccess::GetObjectComWrappersData(CLRDATA_ADDRESS objAddr, CLRDATA } SOSDacEnter(); + + // Default to having found no information. + HRESULT hr = S_FALSE; + if (pNeeded != NULL) { *pNeeded = 0; @@ -5072,59 +5137,63 @@ HRESULT ClrDataAccess::GetObjectComWrappersData(CLRDATA_ADDRESS objAddr, CLRDATA *rcw = 0; } - PTR_SyncBlock pSyncBlk = PTR_Object(TO_TADDR(objAddr))->PassiveGetSyncBlock(); - if (pSyncBlk != NULL) + FieldDesc* pRcwTableField = (&g_CoreLib)->GetField(FIELD__COMWRAPPERS__NAITVE_OBJECT_WRAPPER_TABLE); + CONDITIONAL_WEAK_TABLE_REF rcwTable = *(DPTR(CONDITIONAL_WEAK_TABLE_REF))PTR_TO_TADDR(pRcwTableField->GetStaticAddressHandle(pRcwTableField->GetBase())); + if (rcwTable != nullptr) { - PTR_InteropSyncBlockInfo pInfo = pSyncBlk->GetInteropInfoNoCreate(); - if (pInfo != NULL) + NATIVEOBJECTWRAPPERREF pNativeObjectWrapperRef = nullptr; + if (rcwTable->TryGetValue(OBJECTREF(TO_TADDR(objAddr)), &pNativeObjectWrapperRef)) { - if (rcw != NULL) - { - *rcw = TO_TADDR(pInfo->m_externalComObjectContext); - } + // Tag this RCW as a ComWrappers RCW. + *rcw = TO_CDADDR(dac_cast(pNativeObjectWrapperRef)) | 0x1; + hr = S_OK; + } + } - DPTR(NewHolder) mapHolder(PTR_TO_MEMBER_TADDR(InteropSyncBlockInfo, pInfo, m_managedObjectComWrapperMap)); - DPTR(ManagedObjectComWrapperByIdMap *)ppMap(PTR_TO_MEMBER_TADDR(NewHolder, mapHolder, m_value)); - DPTR(ManagedObjectComWrapperByIdMap) pMap(TO_TADDR(*ppMap)); + FieldDesc* pMowTableField = (&g_CoreLib)->GetField(FIELD__COMWRAPPERS__ALL_MANAGED_OBJECT_WRAPPER_TABLE); + CONDITIONAL_WEAK_TABLE_REF mowTable = *(DPTR(CONDITIONAL_WEAK_TABLE_REF))PTR_TO_TADDR(pMowTableField->GetStaticAddressHandle(pRcwTableField->GetBase())); + if (mowTable != nullptr) + { + OBJECTREF pAllManagedObjectWrapperRef = nullptr; + if (mowTable->TryGetValue(OBJECTREF(TO_TADDR(objAddr)), &pAllManagedObjectWrapperRef)) + { + hr = S_OK; - CQuickArrayList comWrappers; - if (pMap != NULL) + // Read the list of MOWs into the provided buffer. + FieldDesc* pListItemsField = (&g_CoreLib)->GetField(FIELD__LISTGENERIC__ITEMS); + PTRARRAYREF pListItems = (PTRARRAYREF)pListItemsField->GetRefValue(pAllManagedObjectWrapperRef); + FieldDesc* pListSizeField = (&g_CoreLib)->GetField(FIELD__LISTGENERIC__SIZE); + int32_t listCount = pListSizeField->GetValue32(pAllManagedObjectWrapperRef); + if (listCount > 0 && pListItems != nullptr) { - ManagedObjectComWrapperByIdMap::Iterator iter = pMap->Begin(); - while (iter != pMap->End()) + // The list is not empty, so we can return the MOWs. + if (pNeeded != NULL) { - comWrappers.Push(TO_CDADDR(iter->Value())); - ++iter; - + *pNeeded = (unsigned int)listCount; } - } - - if (pNeeded != NULL) - { - *pNeeded = (unsigned int)comWrappers.Size(); - } - for (SIZE_T pos = 0; pos < comWrappers.Size(); ++pos) - { - if (pos >= count) + if (count < (unsigned int)listCount) { + // Return S_FALSE if the buffer is too small. hr = S_FALSE; - break; } - mowList[pos] = comWrappers[pos]; + for (unsigned int i = 0; i < count; i++) + { + MOWHOLDERREF pMOWRef = (MOWHOLDERREF)pListItems->GetAt(i); + PTR_ManagedObjectWrapper pMOW = PTR_ManagedObjectWrapper(dac_cast(pMOWRef->ManagedObjectWrapper)); + + // Now that we have the managed object wrapper, we need to figure out the COM identity of it. + TADDR pComIdentity = GetIdentityForManagedObjectWrapper(dac_cast(pMOW)); + + mowList[i] = TO_CDADDR(pComIdentity); + } } } - else - { - hr = S_FALSE; - } - } - else - { - hr = S_FALSE; } + hr = S_FALSE; + SOSDacLeave(); return hr; #else // FEATURE_COMWRAPPERS @@ -5186,7 +5255,7 @@ HRESULT ClrDataAccess::GetComWrappersCCWData(CLRDATA_ADDRESS ccw, CLRDATA_ADDRES if (refCount != NULL) { - *refCount = (int)pMOW->RefCount; + *refCount = (int)pMOW->GetRawRefCount(); } } else @@ -5202,52 +5271,58 @@ HRESULT ClrDataAccess::GetComWrappersCCWData(CLRDATA_ADDRESS ccw, CLRDATA_ADDRES #endif // FEATURE_COMWRAPPERS } -HRESULT ClrDataAccess::IsComWrappersRCW(CLRDATA_ADDRESS rcw, BOOL *isComWrappersRCW) -{ #ifdef FEATURE_COMWRAPPERS - if (rcw == 0) - { - return E_INVALIDARG; - } - - SOSDacEnter(); - - if (isComWrappersRCW != NULL) +namespace +{ + BOOL IsComWrappersRCW(CLRDATA_ADDRESS rcw) { - PTR_ExternalObjectContext pRCW(TO_TADDR(rcw)); - BOOL stillValid = TRUE; - if(pRCW->SyncBlockIndex >= SyncBlockCache::s_pSyncBlockCache->m_SyncTableSize) + if ((rcw & 1) == 0) { - stillValid = FALSE; + // We use the low bit of the RCW address to indicate that it is a ComWrappers RCW. + return FALSE; } - PTR_SyncBlock pSyncBlk = NULL; - if (stillValid) + OBJECTREF nativeObjectWrapper = OBJECTREF(TO_TADDR(rcw & ~1)); + if (nativeObjectWrapper == NULL) { - PTR_SyncTableEntry ste = PTR_SyncTableEntry(dac_cast(g_pSyncTable) + (sizeof(SyncTableEntry) * pRCW->SyncBlockIndex)); - pSyncBlk = ste->m_SyncBlock; - if(pSyncBlk == NULL) - { - stillValid = FALSE; - } + return FALSE; } - PTR_InteropSyncBlockInfo pInfo = NULL; - if (stillValid) + if (nativeObjectWrapper->GetMethodTable() != (&g_CoreLib)->GetClass(CLASS__NATIVE_OBJECT_WRAPPER)) { - pInfo = pSyncBlk->GetInteropInfoNoCreate(); - if(pInfo == NULL) - { - stillValid = FALSE; - } + return FALSE; } - if (stillValid) + return TRUE; + } + + TADDR GetComWrappersRCWIdentity(CLRDATA_ADDRESS rcw) + { + if ((rcw & 1) == 0) { - stillValid = TO_TADDR(pInfo->m_externalComObjectContext) == PTR_HOST_TO_TADDR(pRCW); + // We use the low bit of the RCW address to indicate that it is a ComWrappers RCW. + return (TADDR)NULL; } - *isComWrappersRCW = stillValid; + NATIVEOBJECTWRAPPERREF pNativeObjectWrapper = NATIVEOBJECTWRAPPERREF(TO_TADDR(rcw & ~1)); + return pNativeObjectWrapper->GetExternalComObject(); + } +} +#endif + +HRESULT ClrDataAccess::IsComWrappersRCW(CLRDATA_ADDRESS rcw, BOOL *isComWrappersRCW) +{ +#ifdef FEATURE_COMWRAPPERS + if (rcw == 0) + { + return E_INVALIDARG; + } + + SOSDacEnter(); + + if (isComWrappersRCW != NULL) + { + *isComWrappersRCW = ::IsComWrappersRCW(rcw); hr = *isComWrappersRCW ? S_OK : S_FALSE; } @@ -5268,10 +5343,9 @@ HRESULT ClrDataAccess::GetComWrappersRCWData(CLRDATA_ADDRESS rcw, CLRDATA_ADDRES SOSDacEnter(); - PTR_ExternalObjectContext pEOC(TO_TADDR(rcw)); if (identity != NULL) { - *identity = PTR_CDADDR(pEOC->Identity); + *identity = TO_CDADDR(GetComWrappersRCWIdentity(rcw)); } SOSDacLeave(); diff --git a/src/coreclr/debug/ee/dactable.cpp b/src/coreclr/debug/ee/dactable.cpp index 6dac100ffb39c4..80d1995acf8928 100644 --- a/src/coreclr/debug/ee/dactable.cpp +++ b/src/coreclr/debug/ee/dactable.cpp @@ -31,16 +31,19 @@ extern "C" void STDCALL ThePreStubPatchLabel(void); #ifdef FEATURE_COMWRAPPERS // Keep these forward declarations in sync with the method definitions in interop/comwrappers.cpp -namespace ABI +namespace InteropLib { - struct ComInterfaceDispatch; + namespace ABI + { + struct ComInterfaceDispatch; + } } HRESULT STDMETHODCALLTYPE ManagedObjectWrapper_QueryInterface( - _In_ ABI::ComInterfaceDispatch* disp, + _In_ InteropLib::ABI::ComInterfaceDispatch* disp, /* [in] */ REFIID riid, /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject); HRESULT STDMETHODCALLTYPE TrackerTarget_QueryInterface( - _In_ ABI::ComInterfaceDispatch* disp, + _In_ InteropLib::ABI::ComInterfaceDispatch* disp, /* [in] */ REFIID riid, /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject); diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index 58b83368f7bf4b..05fb629cf2282f 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -11,7 +11,6 @@ #endif // _WIN32 using OBJECTHANDLE = InteropLib::OBJECTHANDLE; -using AllocScenario = InteropLibImports::AllocScenario; using TryInvokeICustomQueryInterfaceResult = InteropLibImports::TryInvokeICustomQueryInterfaceResult; namespace ABI @@ -44,133 +43,11 @@ namespace ABI // See the dispatch section building API below for an example of how indexing works. //-------------------------------------------------------------------------------- - struct ComInterfaceDispatch - { - const void* vtable; - }; - ABI_ASSERT(sizeof(ComInterfaceDispatch) == sizeof(void*)); - + using InteropLib::ABI::ComInterfaceDispatch; + using InteropLib::ABI::ComInterfaceEntry; using InteropLib::ABI::DispatchAlignmentThisPtr; using InteropLib::ABI::DispatchThisPtrMask; - ABI_ASSERT(sizeof(void*) < DispatchAlignmentThisPtr); - - const intptr_t AlignmentThisPtrMaxPadding = DispatchAlignmentThisPtr - sizeof(void*); - const size_t EntriesPerThisPtr = (DispatchAlignmentThisPtr / sizeof(void*)) - 1; - - // Check if the instance can dispatch according to the ABI. - bool IsAbleToDispatch(_In_ ComInterfaceDispatch* disp) - { - return (reinterpret_cast(disp) & DispatchThisPtrMask) != 0; - } - - // Given the number of dispatch entries, compute the needed number of 'this' pointer entries. - constexpr size_t ComputeThisPtrForDispatchSection(_In_ size_t dispatchCount) - { - return (dispatchCount / ABI::EntriesPerThisPtr) + ((dispatchCount % ABI::EntriesPerThisPtr) == 0 ? 0 : 1); - } - - // Given a pointer and a padding allowance, attempt to find an offset into - // the memory that is properly aligned for the dispatch section. - char* AlignDispatchSection(_In_ char* section, _In_ intptr_t extraPadding) - { - _ASSERTE(section != nullptr); - - // If the dispatch section is not properly aligned by default, we - // utilize the padding to make sure the dispatch section is aligned. - while ((reinterpret_cast(section) % ABI::DispatchAlignmentThisPtr) != 0) - { - // Check if there is padding to attempt an alignment. - if (extraPadding <= 0) - return nullptr; - - extraPadding -= sizeof(void*); - -#ifdef _DEBUG - // Poison unused portions of the section. - ::memset(section, 0xff, sizeof(void*)); -#endif - - section += sizeof(void*); - } - - return section; - } - - struct ComInterfaceEntry - { - GUID IID; - const void* Vtable; - }; - - struct EntrySet - { - const ComInterfaceEntry* start; - int32_t count; - }; - - // Populate the dispatch section with the entry sets - ComInterfaceDispatch* PopulateDispatchSection( - _In_ void* thisPtr, - _In_ void* dispatchSection, - _In_ size_t entrySetCount, - _In_ const EntrySet* entrySets) - { - // Define dispatch section iterator. - const void** currDisp = reinterpret_cast(dispatchSection); - - // Keep rolling count of dispatch entries. - int32_t dispCount = 0; - - // Iterate over all interface entry sets. - const EntrySet* curr = entrySets; - const EntrySet* end = entrySets + entrySetCount; - for (; curr != end; ++curr) - { - const ComInterfaceEntry* currEntry = curr->start; - int32_t entryCount = curr->count; - - // Update dispatch section with 'this' pointer and vtables. - for (int32_t i = 0; i < entryCount; ++i, ++dispCount, ++currEntry) - { - // Insert the 'this' pointer at the appropriate locations - // e.g.: - // 32-bit | 64-bit - // (0 * 4) % 16 = 0 | (0 * 8) % 64 = 0 - // (1 * 4) % 16 = 4 | (1 * 8) % 64 = 8 - // (2 * 4) % 16 = 8 | (2 * 8) % 64 = 16 - // (3 * 4) % 16 = 12 | ... - // (4 * 4) % 16 = 0 | (7 * 8) % 64 = 56 - // (5 * 4) % 16 = 4 | (8 * 8) % 64 = 0 - // - if (((dispCount * sizeof(void*)) % ABI::DispatchAlignmentThisPtr) == 0) - { - *currDisp++ = thisPtr; - ++dispCount; - } - - // Fill in the dispatch entry - *currDisp++ = currEntry->Vtable; - } - } - - return reinterpret_cast(dispatchSection); - } - - // Given the entry index, compute the dispatch index. - ComInterfaceDispatch* IndexIntoDispatchSection(_In_ int32_t i, _In_ ComInterfaceDispatch* dispatches) - { - // Convert the supplied zero based index into what it represents as a count. - const size_t count = static_cast(i) + 1; - - // Based on the supplied count, compute how many previous 'this' pointers would be - // required in the dispatch section and add that to the supplied index to get the - // index into the dispatch section. - const size_t idx = ComputeThisPtrForDispatchSection(count) + i; - - ComInterfaceDispatch* disp = dispatches + idx; - _ASSERTE(IsAbleToDispatch(disp)); - return disp; - } + using InteropLib::ABI::IndexIntoDispatchSection; // Given a dispatcher instance, return the associated ManagedObjectWrapper. ManagedObjectWrapper* ToManagedObjectWrapper(_In_ ComInterfaceDispatch* disp) @@ -242,7 +119,7 @@ HRESULT STDMETHODCALLTYPE TrackerTarget_QueryInterface( // 1. Marked to Destroy - in this case it is unsafe to touch wrapper. // 2. Object Handle target has been NULLed out by GC. if (wrapper->IsMarkedToDestroy() - || !InteropLibImports::HasValidTarget(wrapper->Target)) + || !InteropLibImports::HasValidTarget(wrapper->GetTarget())) { // It is unsafe to proceed with a QueryInterface call. The MOW has been // marked destroyed or the associated managed object has been collected. @@ -338,6 +215,11 @@ namespace static_assert(sizeof(ManagedObjectWrapper_IReferenceTrackerTargetImpl) == (7 * sizeof(void*)), "Unexpected vtable size"); } +void const* ManagedObjectWrapper::GetIReferenceTrackerTargetImpl() noexcept +{ + return &ManagedObjectWrapper_IReferenceTrackerTargetImpl; +} + namespace { // This IID represents an internal interface we define to tag any ManagedObjectWrappers we create. @@ -355,22 +237,6 @@ namespace { return (version == (void*)&ITaggedImpl_IsCurrentVersion) ? S_OK : E_FAIL; } - - // Hard-coded ManagedObjectWrapper tagged vtable. - const struct - { - decltype(&ManagedObjectWrapper_QueryInterface) QueryInterface; - decltype(&ManagedObjectWrapper_AddRef) AddRef; - decltype(&ManagedObjectWrapper_Release) Release; - decltype(&ITaggedImpl_IsCurrentVersion) IsCurrentVersion; - } ManagedObjectWrapper_TaggedImpl { - &ManagedObjectWrapper_QueryInterface, - &ManagedObjectWrapper_AddRef, - &ManagedObjectWrapper_Release, - &ITaggedImpl_IsCurrentVersion, - }; - - static_assert(sizeof(ManagedObjectWrapper_TaggedImpl) == (4 * sizeof(void*)), "Unexpected vtable size"); } void ManagedObjectWrapper::GetIUnknownImpl( @@ -387,6 +253,11 @@ void ManagedObjectWrapper::GetIUnknownImpl( *fpRelease = (void*)ManagedObjectWrapper_IUnknownImpl.Release; } +void const* ManagedObjectWrapper::GetTaggedCurrentVersionImpl() noexcept +{ + return reinterpret_cast(&ITaggedImpl_IsCurrentVersion); +} + // The logic here should match code:ClrDataAccess::DACTryGetComWrappersObjectFromCCW in daccess/request.cpp ManagedObjectWrapper* ManagedObjectWrapper::MapFromIUnknown(_In_ IUnknown* pUnk) { @@ -428,166 +299,35 @@ ManagedObjectWrapper* ManagedObjectWrapper::MapFromIUnknownWithQueryInterface(_I return ABI::ToManagedObjectWrapper(disp); } -HRESULT ManagedObjectWrapper::Create( - _In_ InteropLib::Com::CreateComInterfaceFlags flagsRaw, - _In_ OBJECTHANDLE objectHandle, - _In_ int32_t userDefinedCount, - _In_ ABI::ComInterfaceEntry* userDefined, - _Outptr_ ManagedObjectWrapper** mow) +void* ManagedObjectWrapper::AsRuntimeDefined(_In_ REFIID riid) { - _ASSERTE(objectHandle != nullptr && mow != nullptr); - - auto flags = static_cast(flagsRaw); - _ASSERTE((flags & CreateComInterfaceFlagsEx::InternalMask) == CreateComInterfaceFlagsEx::None); - - // Maximum number of runtime supplied vtables. - ABI::ComInterfaceEntry runtimeDefinedLocal[3]; - int32_t runtimeDefinedCount = 0; + // The order of interface lookup here is important. + // See ComWrappers.CreateManagedObjectWrapper() for the expected order. + int i = _userDefinedCount; - // Check if the caller will provide the IUnknown table. - if ((flags & CreateComInterfaceFlagsEx::CallerDefinedIUnknown) == CreateComInterfaceFlagsEx::None) + if ((_flags & CreateComInterfaceFlagsEx::CallerDefinedIUnknown) == CreateComInterfaceFlagsEx::None) { - ABI::ComInterfaceEntry& curr = runtimeDefinedLocal[runtimeDefinedCount++]; - curr.IID = __uuidof(IUnknown); - curr.Vtable = &ManagedObjectWrapper_IUnknownImpl; - } - - // Check if the caller wants tracker support. - if ((flags & CreateComInterfaceFlagsEx::TrackerSupport) == CreateComInterfaceFlagsEx::TrackerSupport) - { - ABI::ComInterfaceEntry& curr = runtimeDefinedLocal[runtimeDefinedCount++]; - curr.IID = IID_IReferenceTrackerTarget; - curr.Vtable = &ManagedObjectWrapper_IReferenceTrackerTargetImpl; - } - - // Always add the tagged interface. This is used to confirm at run-time with certainty - // the wrapper is created by the ComWrappers API. - { - ABI::ComInterfaceEntry& curr = runtimeDefinedLocal[runtimeDefinedCount++]; - curr.IID = IID_TaggedImpl; - curr.Vtable = &ManagedObjectWrapper_TaggedImpl; - } - - _ASSERTE(runtimeDefinedCount <= static_cast(ARRAY_SIZE(runtimeDefinedLocal))); - - // Compute size for ManagedObjectWrapper instance. - const size_t totalRuntimeDefinedSize = runtimeDefinedCount * sizeof(ABI::ComInterfaceEntry); - const size_t totalDefinedCount = static_cast(runtimeDefinedCount) + userDefinedCount; - - // Compute the total entry size of dispatch section. - const size_t totalDispatchSectionCount = ABI::ComputeThisPtrForDispatchSection(totalDefinedCount) + totalDefinedCount; - const size_t totalDispatchSectionSize = totalDispatchSectionCount * sizeof(void*); - - // Allocate memory for the ManagedObjectWrapper. - char* wrapperMem = (char*)InteropLibImports::MemAlloc(sizeof(ManagedObjectWrapper) + totalRuntimeDefinedSize + totalDispatchSectionSize + ABI::AlignmentThisPtrMaxPadding, AllocScenario::ManagedObjectWrapper); - if (wrapperMem == nullptr) - return E_OUTOFMEMORY; - - // Compute Runtime defined offset. - char* runtimeDefinedOffset = wrapperMem + sizeof(ManagedObjectWrapper); + if (riid == IID_IUnknown) + { + return ABI::IndexIntoDispatchSection(i, _dispatches); + } - // Copy in runtime supplied COM interface entries. - ABI::ComInterfaceEntry* runtimeDefined = nullptr; - if (0 < runtimeDefinedCount) - { - ::memcpy(runtimeDefinedOffset, runtimeDefinedLocal, totalRuntimeDefinedSize); - runtimeDefined = reinterpret_cast(runtimeDefinedOffset); + ++i; } - // Compute the dispatch section offset and ensure it is aligned. - char* dispatchSectionOffset = runtimeDefinedOffset + totalRuntimeDefinedSize; - dispatchSectionOffset = ABI::AlignDispatchSection(dispatchSectionOffset, ABI::AlignmentThisPtrMaxPadding); - if (dispatchSectionOffset == nullptr) - return E_UNEXPECTED; - - // Define the sets for the tables to insert - const ABI::EntrySet AllEntries[] = + if ((_flags & CreateComInterfaceFlagsEx::TrackerSupport) == CreateComInterfaceFlagsEx::TrackerSupport) { - { runtimeDefined, runtimeDefinedCount }, - { userDefined, userDefinedCount } - }; - - ABI::ComInterfaceDispatch* dispSection = ABI::PopulateDispatchSection(wrapperMem, dispatchSectionOffset, ARRAY_SIZE(AllEntries), AllEntries); - - ManagedObjectWrapper* wrapper = new (wrapperMem) ManagedObjectWrapper + if (riid == IID_IReferenceTrackerTarget) { - flags, - objectHandle, - runtimeDefinedCount, - runtimeDefined, - userDefinedCount, - userDefined, - dispSection - }; - - *mow = wrapper; - return S_OK; -} - -void ManagedObjectWrapper::Destroy(_In_ ManagedObjectWrapper* wrapper) -{ - _ASSERTE(wrapper != nullptr); - _ASSERTE(GetComCount(wrapper->_refCount) == 0); + return ABI::IndexIntoDispatchSection(i, _dispatches); + } - // Attempt to set the destroyed bit. - LONGLONG refCount; - LONGLONG prev; - do - { - prev = wrapper->_refCount; - refCount = prev | DestroySentinel; - } while (InterlockedCompareExchange64(&wrapper->_refCount, refCount, prev) != prev); - - // The destroy sentinel represents the bit that indicates the wrapper - // should be destroyed. Since the reference count field (64-bit) holds - // two counters we rely on the singular sentinel value - no other bits - // in the 64-bit counter are set. If there are outstanding bits set it - // indicates there are still outstanding references. - if (refCount == DestroySentinel) - { - // Manually trigger the destructor since placement - // new was used to allocate the object. - wrapper->~ManagedObjectWrapper(); - InteropLibImports::MemFree(wrapper, AllocScenario::ManagedObjectWrapper); + ++i; } -} - -ManagedObjectWrapper::ManagedObjectWrapper( - _In_ CreateComInterfaceFlagsEx flags, - _In_ OBJECTHANDLE objectHandle, - _In_ int32_t runtimeDefinedCount, - _In_ const ABI::ComInterfaceEntry* runtimeDefined, - _In_ int32_t userDefinedCount, - _In_ const ABI::ComInterfaceEntry* userDefined, - _In_ ABI::ComInterfaceDispatch* dispatches) - : Target{ nullptr } - , _refCount{ 1 } - , _runtimeDefinedCount{ runtimeDefinedCount } - , _userDefinedCount{ userDefinedCount } - , _runtimeDefined{ runtimeDefined } - , _userDefined{ userDefined } - , _dispatches{ dispatches } - , _flags{ flags } -{ - bool wasSet = TrySetObjectHandle(objectHandle); - _ASSERTE(wasSet); -} - -ManagedObjectWrapper::~ManagedObjectWrapper() -{ - // If the target isn't null, then release it. - if (Target != nullptr) - InteropLibImports::DeleteObjectInstanceHandle(Target); -} -void* ManagedObjectWrapper::AsRuntimeDefined(_In_ REFIID riid) -{ - for (int32_t i = 0; i < _runtimeDefinedCount; ++i) + if (riid == IID_TaggedImpl) { - if (IsEqualGUID(_runtimeDefined[i].IID, riid)) - { - return ABI::IndexIntoDispatchSection(i, _dispatches); - } + return ABI::IndexIntoDispatchSection(i, _dispatches); } return nullptr; @@ -599,7 +339,7 @@ void* ManagedObjectWrapper::AsUserDefined(_In_ REFIID riid) { if (IsEqualGUID(_userDefined[i].IID, riid)) { - return ABI::IndexIntoDispatchSection(i + _runtimeDefinedCount, _dispatches); + return ABI::IndexIntoDispatchSection(i, _dispatches); } } @@ -616,11 +356,6 @@ void* ManagedObjectWrapper::As(_In_ REFIID riid) return typeMaybe; } -bool ManagedObjectWrapper::TrySetObjectHandle(_In_ OBJECTHANDLE objectHandle, _In_ OBJECTHANDLE current) -{ - return (InterlockedCompareExchangePointer(&Target, objectHandle, current) == current); -} - bool ManagedObjectWrapper::IsSet(_In_ CreateComInterfaceFlagsEx flag) const { return (_flags & flag) != CreateComInterfaceFlagsEx::None; @@ -689,7 +424,13 @@ ULONG ManagedObjectWrapper::ReleaseFromReferenceTracker() // If we observe the destroy sentinel, then this release // must destroy the wrapper. if (refCount == DestroySentinel) - Destroy(this); + { + InteropLib::OBJECTHANDLE handle = InterlockedExchangePointer(&_target, nullptr); + if (handle != nullptr) + { + InteropLibImports::DestroyHandle(handle); + } + } return GetTrackerCount(refCount); } @@ -720,7 +461,7 @@ HRESULT ManagedObjectWrapper::QueryInterface( // Check if the managed object has implemented ICustomQueryInterface if (!IsSet(CreateComInterfaceFlagsEx::LacksICustomQueryInterface)) { - TryInvokeICustomQueryInterfaceResult result = InteropLibImports::TryInvokeICustomQueryInterface(Target, riid, ppvObject); + TryInvokeICustomQueryInterfaceResult result = InteropLibImports::TryInvokeICustomQueryInterface(GetTarget(), riid, ppvObject); switch (result) { case TryInvokeICustomQueryInterfaceResult::Handled: @@ -782,166 +523,7 @@ ULONG ManagedObjectWrapper::Release(void) return GetComCount(::InterlockedDecrement64(&_refCount)); } -namespace -{ - const size_t LiveContextSentinel = 0x0a110ced; - const size_t DeadContextSentinel = 0xdeaddead; -} - -NativeObjectWrapperContext* NativeObjectWrapperContext::MapFromRuntimeContext(_In_ void* cxtMaybe) -{ - _ASSERTE(cxtMaybe != nullptr); - - // Convert the supplied context - char* cxtRaw = reinterpret_cast(cxtMaybe); - cxtRaw -= sizeof(NativeObjectWrapperContext); - NativeObjectWrapperContext* cxt = reinterpret_cast(cxtRaw); - -#ifdef _DEBUG - _ASSERTE(cxt->_sentinel == LiveContextSentinel); -#endif - - return cxt; -} - -HRESULT NativeObjectWrapperContext::Create( - _In_ IUnknown* external, - _In_opt_ IUnknown* inner, - _In_ InteropLib::Com::CreateObjectFlags flags, - _In_ size_t runtimeContextSize, - _Outptr_ NativeObjectWrapperContext** context) +InteropLib::OBJECTHANDLE ManagedObjectWrapper::GetTarget() const { - _ASSERTE(external != nullptr && context != nullptr); - - HRESULT hr; - - ComHolder trackerObject; - if (flags & InteropLib::Com::CreateObjectFlags_TrackerObject) - { - hr = external->QueryInterface(IID_IReferenceTracker, (void**)&trackerObject); - if (SUCCEEDED(hr)) - RETURN_IF_FAILED(TrackerObjectManager::OnIReferenceTrackerFound(trackerObject)); - } - - // Allocate memory for the RCW - char* cxtMem = (char*)InteropLibImports::MemAlloc(sizeof(NativeObjectWrapperContext) + runtimeContextSize, AllocScenario::NativeObjectWrapper); - if (cxtMem == nullptr) - return E_OUTOFMEMORY; - - void* runtimeContext = cxtMem + sizeof(NativeObjectWrapperContext); - - // Contract specifically requires zeroing out runtime context. - ::memset(runtimeContext, 0, runtimeContextSize); - - NativeObjectWrapperContext* contextLocal = new (cxtMem) NativeObjectWrapperContext{ runtimeContext, trackerObject, inner }; - - if (trackerObject != nullptr) - { - // Inform the tracker object manager - _ASSERTE(flags & InteropLib::Com::CreateObjectFlags_TrackerObject); - hr = TrackerObjectManager::AfterWrapperCreated(trackerObject); - if (FAILED(hr)) - { - Destroy(contextLocal); - return hr; - } - - // Aggregation with a tracker object must be "cleaned up". - if (flags & InteropLib::Com::CreateObjectFlags_Aggregated) - { - _ASSERTE(inner != nullptr); - contextLocal->HandleReferenceTrackerAggregation(); - } - } - - *context = contextLocal; - return S_OK; -} - -void NativeObjectWrapperContext::Destroy(_In_ NativeObjectWrapperContext* wrapper) -{ - _ASSERTE(wrapper != nullptr); - - // Manually trigger the destructor since placement - // new was used to allocate the object. - wrapper->~NativeObjectWrapperContext(); - InteropLibImports::MemFree(wrapper, AllocScenario::NativeObjectWrapper); -} - -NativeObjectWrapperContext::NativeObjectWrapperContext( - _In_ void* runtimeContext, - _In_opt_ IReferenceTracker* trackerObject, - _In_opt_ IUnknown* nativeObjectAsInner) - : _trackerObject{ trackerObject } - , _runtimeContext{ runtimeContext } - , _trackerObjectDisconnected{ FALSE } - , _trackerObjectState{ (trackerObject == nullptr ? TrackerObjectState::NotSet : TrackerObjectState::SetForRelease) } - , _nativeObjectAsInner{ nativeObjectAsInner } -#ifdef _DEBUG - , _sentinel{ LiveContextSentinel } -#endif -{ - if (_trackerObjectState == TrackerObjectState::SetForRelease) - (void)_trackerObject->AddRef(); -} - -NativeObjectWrapperContext::~NativeObjectWrapperContext() -{ - DisconnectTracker(); - - // If the inner was supplied, we need to release our reference. - if (_nativeObjectAsInner != nullptr) - (void)_nativeObjectAsInner->Release(); - -#ifdef _DEBUG - _sentinel = DeadContextSentinel; -#endif -} - -void* NativeObjectWrapperContext::GetRuntimeContext() const noexcept -{ - return _runtimeContext; -} - -IReferenceTracker* NativeObjectWrapperContext::GetReferenceTracker() const noexcept -{ - return ((_trackerObjectState == TrackerObjectState::NotSet || _trackerObjectDisconnected) ? nullptr : _trackerObject); -} - -// See TrackerObjectManager::AfterWrapperCreated() for AddRefFromTrackerSource() usage. -// See NativeObjectWrapperContext::HandleReferenceTrackerAggregation() for additional -// cleanup logistics. -void NativeObjectWrapperContext::DisconnectTracker() noexcept -{ - // Return if already disconnected or the tracker isn't set. - if (FALSE != ::InterlockedCompareExchange((LONG*)&_trackerObjectDisconnected, TRUE, FALSE) - || _trackerObjectState == TrackerObjectState::NotSet) - { - return; - } - - _ASSERTE(_trackerObject != nullptr); - - // Always release the tracker source during a disconnect. - // This to account for the implied IUnknown ownership by the runtime. - (void)_trackerObject->ReleaseFromTrackerSource(); // IUnknown - - // Disconnect from the tracker. - if (_trackerObjectState == TrackerObjectState::SetForRelease) - { - (void)_trackerObject->ReleaseFromTrackerSource(); // IReferenceTracker - (void)_trackerObject->Release(); - } -} - -void NativeObjectWrapperContext::HandleReferenceTrackerAggregation() noexcept -{ - _ASSERTE(_trackerObjectState == TrackerObjectState::SetForRelease && _trackerObject != nullptr); - - // Aggregation with an IReferenceTracker instance creates an extra AddRef() - // on the outer (e.g. MOW) so we clean up that issue here. - _trackerObjectState = TrackerObjectState::SetNoRelease; - - (void)_trackerObject->ReleaseFromTrackerSource(); // IReferenceTracker - (void)_trackerObject->Release(); + return _target; } diff --git a/src/coreclr/interop/comwrappers.hpp b/src/coreclr/interop/comwrappers.hpp index 47bf008ac50126..00ebfc39194b96 100644 --- a/src/coreclr/interop/comwrappers.hpp +++ b/src/coreclr/interop/comwrappers.hpp @@ -9,62 +9,14 @@ #include #include "referencetrackertypes.hpp" -#ifndef DEFINE_ENUM_FLAG_OPERATORS -#define DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \ -extern "C++" { \ - inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((int)a)|((int)b)); } \ - inline ENUMTYPE operator |= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((int &)a) |= ((int)b)); } \ - inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((int)a)&((int)b)); } \ - inline ENUMTYPE operator &= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((int &)a) &= ((int)b)); } \ - inline ENUMTYPE operator ~ (ENUMTYPE a) { return (ENUMTYPE)(~((int)a)); } \ - inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((int)a)^((int)b)); } \ - inline ENUMTYPE operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((int &)a) ^= ((int)b)); } \ -} -#endif - -enum class CreateComInterfaceFlagsEx : int32_t -{ - None = InteropLib::Com::CreateComInterfaceFlags_None, - CallerDefinedIUnknown = InteropLib::Com::CreateComInterfaceFlags_CallerDefinedIUnknown, - TrackerSupport = InteropLib::Com::CreateComInterfaceFlags_TrackerSupport, - - // Highest bits are reserved for internal usage - LacksICustomQueryInterface = 1 << 29, - IsComActivated = 1 << 30, - IsPegged = 1 << 31, - - InternalMask = IsPegged | IsComActivated | LacksICustomQueryInterface, -}; - -DEFINE_ENUM_FLAG_OPERATORS(CreateComInterfaceFlagsEx); - -// Forward declarations -namespace ABI -{ - struct ComInterfaceDispatch; - struct ComInterfaceEntry; -} +using InteropLib::Com::CreateComInterfaceFlagsEx; static constexpr size_t ManagedObjectWrapperRefCountOffset(); +static constexpr size_t ManagedObjectWrapperFlagsOffset(); // Class for wrapping a managed object and projecting it in a non-managed environment -class ManagedObjectWrapper +class ManagedObjectWrapper final : public InteropLib::ABI::ManagedObjectWrapperLayout { - friend constexpr size_t ManagedObjectWrapperRefCountOffset(); -public: - Volatile Target; - -private: - LONGLONG _refCount; - - const int32_t _runtimeDefinedCount; - const int32_t _userDefinedCount; - const ABI::ComInterfaceEntry* _runtimeDefined; - const ABI::ComInterfaceEntry* _userDefined; - ABI::ComInterfaceDispatch* _dispatches; - - Volatile _flags; - public: // static // Get the implementation for IUnknown. static void GetIUnknownImpl( @@ -72,6 +24,10 @@ class ManagedObjectWrapper _Out_ void** fpAddRef, _Out_ void** fpRelease); + static void const* GetIReferenceTrackerTargetImpl() noexcept; + + static void const* GetTaggedCurrentVersionImpl() noexcept; + // Convert the IUnknown if the instance is a ManagedObjectWrapper // into a ManagedObjectWrapper, otherwise null. static ManagedObjectWrapper* MapFromIUnknown(_In_ IUnknown* pUnk); @@ -82,42 +38,17 @@ class ManagedObjectWrapper // performing a QueryInterface() which may not always be possible. // See implementation for more details. static ManagedObjectWrapper* MapFromIUnknownWithQueryInterface(_In_ IUnknown* pUnk); - - // Create a ManagedObjectWrapper instance - static HRESULT Create( - _In_ InteropLib::Com::CreateComInterfaceFlags flags, - _In_ InteropLib::OBJECTHANDLE objectHandle, - _In_ int32_t userDefinedCount, - _In_ ABI::ComInterfaceEntry* userDefined, - _Outptr_ ManagedObjectWrapper** mow); - - // Destroy the instance - static void Destroy(_In_ ManagedObjectWrapper* wrapper); - private: - ManagedObjectWrapper( - _In_ CreateComInterfaceFlagsEx flags, - _In_ InteropLib::OBJECTHANDLE objectHandle, - _In_ int32_t runtimeDefinedCount, - _In_ const ABI::ComInterfaceEntry* runtimeDefined, - _In_ int32_t userDefinedCount, - _In_ const ABI::ComInterfaceEntry* userDefined, - _In_ ABI::ComInterfaceDispatch* dispatches); - - ~ManagedObjectWrapper(); - // Query the runtime defined tables. void* AsRuntimeDefined(_In_ REFIID riid); // Query the user defined tables. void* AsUserDefined(_In_ REFIID riid); - public: // N.B. Does not impact the reference count of the object. void* As(_In_ REFIID riid); // Attempt to set the target object handle based on an assumed current value. - bool TrySetObjectHandle(_In_ InteropLib::OBJECTHANDLE objectHandle, _In_ InteropLib::OBJECTHANDLE current = nullptr); bool IsSet(_In_ CreateComInterfaceFlagsEx flag) const; void SetFlag(_In_ CreateComInterfaceFlagsEx flag); void ResetFlag(_In_ CreateComInterfaceFlagsEx flag); @@ -128,6 +59,8 @@ class ManagedObjectWrapper // Check if the wrapper has been marked to be destroyed. bool IsMarkedToDestroy() const; + InteropLib::OBJECTHANDLE GetTarget() const; + public: // IReferenceTrackerTarget ULONG AddRefFromReferenceTracker(); ULONG ReleaseFromReferenceTracker(); @@ -142,82 +75,13 @@ class ManagedObjectWrapper ULONG Release(void); }; -// ABI contract. This below offset is assumed in managed code and the DAC. -ABI_ASSERT(offsetof(ManagedObjectWrapper, Target) == 0); - -static constexpr size_t ManagedObjectWrapperRefCountOffset() -{ - // _refCount is a private field and offsetof won't let you look at private fields. - // To overcome, this function is a friend function of ManagedObjectWrapper. - return offsetof(ManagedObjectWrapper, _refCount); -} - -// ABI contract used by the DAC. -ABI_ASSERT(offsetof(ManagedObjectWrapper, Target) == offsetof(InteropLib::ABI::ManagedObjectWrapperLayout, ManagedObject)); -ABI_ASSERT(ManagedObjectWrapperRefCountOffset() == offsetof(InteropLib::ABI::ManagedObjectWrapperLayout, RefCount)); - -// State ownership mechanism. -enum class TrackerObjectState -{ - NotSet, - SetNoRelease, - SetForRelease, -}; - -// Class for connecting a native COM object to a managed object instance -class NativeObjectWrapperContext -{ - IReferenceTracker* _trackerObject; - void* _runtimeContext; - Volatile _trackerObjectDisconnected; - TrackerObjectState _trackerObjectState; - IUnknown* _nativeObjectAsInner; - -#ifdef _DEBUG - size_t _sentinel; -#endif -public: // static - // Convert a context pointer into a NativeObjectWrapperContext. - static NativeObjectWrapperContext* MapFromRuntimeContext(_In_ void* cxt); - - // Create a NativeObjectWrapperContext instance - static HRESULT Create( - _In_ IUnknown* external, - _In_opt_ IUnknown* nativeObjectAsInner, - _In_ InteropLib::Com::CreateObjectFlags flags, - _In_ size_t runtimeContextSize, - _Outptr_ NativeObjectWrapperContext** context); - - // Destroy the instance - static void Destroy(_In_ NativeObjectWrapperContext* wrapper); - -private: - NativeObjectWrapperContext(_In_ void* runtimeContext, _In_opt_ IReferenceTracker* trackerObject, _In_opt_ IUnknown* nativeObjectAsInner); - ~NativeObjectWrapperContext(); - -public: - // Get the associated runtime context for this context. - void* GetRuntimeContext() const noexcept; - - // Get the IReferenceTracker instance. - IReferenceTracker* GetReferenceTracker() const noexcept; - - // Disconnect reference tracker instance. - void DisconnectTracker() noexcept; - -private: - void HandleReferenceTrackerAggregation() noexcept; -}; - // Manage native object wrappers that support IReferenceTracker. class TrackerObjectManager { public: - // Called when an IReferenceTracker instance is found. - static HRESULT OnIReferenceTrackerFound(_In_ IReferenceTracker* obj); + static bool HasReferenceTrackerManager(); - // Called after wrapper has been created. - static HRESULT AfterWrapperCreated(_In_ IReferenceTracker* obj); + static bool TryRegisterReferenceTrackerManager(_In_ IReferenceTrackerManager* manager); // Called before wrapper is about to be finalized (the same lifetime as short weak handle). static HRESULT BeforeWrapperFinalized(_In_ IReferenceTracker* obj); @@ -228,6 +92,8 @@ class TrackerObjectManager // End the reference tracking process for external object. static HRESULT EndReferenceTracking(); + + static HRESULT DetachNonPromotedObjects(_In_ InteropLibImports::RuntimeCallContext* cxt); }; // Class used to hold COM objects (i.e. IUnknown base class) diff --git a/src/coreclr/interop/inc/interoplib.h b/src/coreclr/interop/inc/interoplib.h index 684283b7133bd7..e4a09fb84c64ee 100644 --- a/src/coreclr/interop/inc/interoplib.h +++ b/src/coreclr/interop/inc/interoplib.h @@ -17,48 +17,16 @@ namespace InteropLib { using OBJECTHANDLE = void*; - namespace Com + namespace ABI { - // See CreateComInterfaceFlags in ComWrappers.cs - enum CreateComInterfaceFlags - { - CreateComInterfaceFlags_None = 0, - CreateComInterfaceFlags_CallerDefinedIUnknown = 1, - CreateComInterfaceFlags_TrackerSupport = 2, - }; - - // Create an IUnknown instance that represents the supplied managed object instance. - HRESULT CreateWrapperForObject( - _In_ OBJECTHANDLE instance, - _In_ INT32 vtableCount, - _In_ void* vtables, - _In_ enum CreateComInterfaceFlags flags, - _Outptr_ IUnknown** wrapper) noexcept; - - // Destroy the supplied wrapper - void DestroyWrapperForObject(_In_ void* wrapper) noexcept; - - // Check if a wrapper is considered a GC root. - HRESULT IsWrapperRooted(_In_ IUnknown* wrapper) noexcept; + struct ManagedObjectWrapperLayout; + } - // Get the object for the supplied wrapper - HRESULT GetObjectForWrapper(_In_ IUnknown* wrapper, _Outptr_result_maybenull_ OBJECTHANDLE* object) noexcept; + namespace Com + { + bool IsRooted(_In_ ABI::ManagedObjectWrapperLayout* wrapper) noexcept; HRESULT MarkComActivated(_In_ IUnknown* wrapper) noexcept; - HRESULT IsComActivated(_In_ IUnknown* wrapper) noexcept; - - struct ExternalWrapperResult - { - // The returned context memory is guaranteed to be initialized to zero. - void* Context; - - // See https://learn.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/ - // for details. - bool FromTrackerRuntime; - - // The supplied external object is wrapping a managed object. - bool ManagedObjectWrapper; - }; // See CreateObjectFlags in ComWrappers.cs enum CreateObjectFlags @@ -70,32 +38,21 @@ namespace InteropLib CreateObjectFlags_Unwrap = 8, }; - // Get the true identity and inner for the supplied IUnknown. - HRESULT DetermineIdentityAndInnerForExternal( - _In_ IUnknown* external, - _In_ enum CreateObjectFlags flags, - _Outptr_ IUnknown** identity, - _Inout_ IUnknown** innerMaybe) noexcept; - - // Allocate a wrapper context for an external object. - // The runtime supplies the external object, flags, and a memory - // request in order to bring the object into the runtime. - HRESULT CreateWrapperForExternal( - _In_ IUnknown* external, - _In_opt_ IUnknown* inner, - _In_ enum CreateObjectFlags flags, - _In_ size_t contextSize, - _Out_ ExternalWrapperResult* result) noexcept; - - // Inform the wrapper it is being collected. - void NotifyWrapperForExternalIsBeingCollected(_In_ void* context) noexcept; - - // Destroy the supplied wrapper. - // Optionally notify the wrapper of collection at the same time. - void DestroyWrapperForExternal(_In_ void* context, _In_ bool notifyIsBeingCollected = false) noexcept; - - // Separate the supplied wrapper from the tracker runtime. - void SeparateWrapperFromTrackerRuntime(_In_ void* context) noexcept; + enum class CreateComInterfaceFlagsEx : int32_t + { + // Matches the managed definition of System.Runtime.InteropServices.CreateComInterfaceFlags + None = 0, + CallerDefinedIUnknown = 1, + TrackerSupport = 2, + + // Highest bits are reserved for internal usage + LacksICustomQueryInterface = 1 << 29, + IsComActivated = 1 << 30, + IsPegged = 1 << 31, + + InternalMask = IsPegged | IsComActivated | LacksICustomQueryInterface, + }; + // Get internal interop IUnknown dispatch pointers. void GetIUnknownImpl( @@ -103,6 +60,8 @@ namespace InteropLib _Out_ void** fpAddRef, _Out_ void** fpRelease) noexcept; + void const* GetTaggedCurrentVersionImpl() noexcept; + // Begin the reference tracking process on external COM objects. // This should only be called during a runtime's GC phase. HRESULT BeginExternalObjectReferenceTracking(_In_ InteropLibImports::RuntimeCallContext* cxt) noexcept; @@ -110,8 +69,35 @@ namespace InteropLib // End the reference tracking process. // This should only be called during a runtime's GC phase. HRESULT EndExternalObjectReferenceTracking() noexcept; + + // Detach non-promoted objects from the reference tracker. + // This should only be called during a runtime's GC phase. + HRESULT DetachNonPromotedObjects(_In_ InteropLibImports::RuntimeCallContext* cxt) noexcept; + + // Get the vtable for IReferenceTrackerTarget + void const* GetIReferenceTrackerTargetVftbl() noexcept; + + // Check if a ReferenceTrackerManager has been registered. + bool HasReferenceTrackerManager() noexcept; + + // Register a ReferenceTrackerManager if one has not already been registered. + bool TryRegisterReferenceTrackerManager(void* manager) noexcept; } } -#endif // _INTEROP_INC_INTEROPLIB_H_ +#ifndef DEFINE_ENUM_FLAG_OPERATORS +#define DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \ +extern "C++" { \ + inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((int)a)|((int)b)); } \ + inline ENUMTYPE operator |= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((int &)a) |= ((int)b)); } \ + inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((int)a)&((int)b)); } \ + inline ENUMTYPE operator &= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((int &)a) &= ((int)b)); } \ + inline ENUMTYPE operator ~ (ENUMTYPE a) { return (ENUMTYPE)(~((int)a)); } \ + inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return ENUMTYPE(((int)a)^((int)b)); } \ + inline ENUMTYPE operator ^= (ENUMTYPE &a, ENUMTYPE b) { return (ENUMTYPE &)(((int &)a) ^= ((int)b)); } \ +} +#endif + +DEFINE_ENUM_FLAG_OPERATORS(InteropLib::Com::CreateComInterfaceFlagsEx); +#endif // _INTEROP_INC_INTEROPLIB_H_ diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index 7789a68217b2be..217ecda8b73e28 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -5,27 +5,79 @@ #define _INTEROP_INC_INTEROPLIBABI_H_ #include +#include namespace InteropLib { namespace ABI { - // Updating this also requires updating ComInterfaceDispatch::GetInstance. + // The definitions in this file are constants and data structures that are shared between interoplib, + // the managed ComWrappers code, and the DAC's ComWrappers support. + // All constants, type layouts, and algorithms that calculate pointer offsets should be in this file + // and should have identical implementations with the managed ComWrappers code. + #ifdef HOST_64BIT - const size_t DispatchAlignmentThisPtr = 64; // Should be a power of 2. + constexpr size_t DispatchAlignmentThisPtr = 64; // Should be a power of 2. #else - const size_t DispatchAlignmentThisPtr = 16; // Should be a power of 2. + constexpr size_t DispatchAlignmentThisPtr = 16; // Should be a power of 2. #endif - const intptr_t DispatchThisPtrMask = ~(DispatchAlignmentThisPtr - 1); + constexpr intptr_t DispatchThisPtrMask = ~(DispatchAlignmentThisPtr - 1); + + static_assert(sizeof(void*) < DispatchAlignmentThisPtr, "DispatchAlignmentThisPtr must be larger than sizeof(void*)."); + + constexpr size_t EntriesPerThisPtr = (DispatchAlignmentThisPtr / sizeof(void*)) - 1; + + struct ComInterfaceDispatch + { + const void* vtable; + }; + + static_assert(sizeof(ComInterfaceDispatch) == sizeof(void*), "ComInterfaceDispatch must be pointer-sized."); + + struct ManagedObjectWrapperLayout; + + struct InternalComInterfaceDispatch + { + private: + ManagedObjectWrapperLayout* _thisPtr; + public: + ComInterfaceDispatch _entries[EntriesPerThisPtr]; + }; + + struct ComInterfaceEntry + { + GUID IID; + const void* Vtable; + }; // Managed object wrapper layout. // This is designed to codify the binary layout. struct ManagedObjectWrapperLayout { - PTR_VOID ManagedObject; - long long RefCount; + public: + LONGLONG GetRawRefCount() const + { + return _refCount; + } + + protected: + Volatile _target; + int64_t _refCount; + + Volatile _flags; + int32_t _userDefinedCount; + ComInterfaceEntry* _userDefined; + InternalComInterfaceDispatch* _dispatches; }; + + // Given the entry index, compute the dispatch index. + inline ComInterfaceDispatch* IndexIntoDispatchSection(int32_t i, InternalComInterfaceDispatch* dispatches) + { + InternalComInterfaceDispatch* dispatch = dispatches + i / EntriesPerThisPtr; + ComInterfaceDispatch* entries = dispatch->_entries; + return entries + (i % EntriesPerThisPtr); + } } } diff --git a/src/coreclr/interop/inc/interoplibimports.h b/src/coreclr/interop/inc/interoplibimports.h index 57824c36d78caa..a75252bf3019d8 100644 --- a/src/coreclr/interop/inc/interoplibimports.h +++ b/src/coreclr/interop/inc/interoplibimports.h @@ -8,45 +8,13 @@ namespace InteropLibImports { - enum class AllocScenario - { - ManagedObjectWrapper, - NativeObjectWrapper, - }; - - // Allocate the given amount of memory. - void* MemAlloc(_In_ size_t sizeInBytes, _In_ AllocScenario scenario) noexcept; - - // Free the previously allocated memory. - void MemFree(_In_ void* mem, _In_ AllocScenario scenario) noexcept; - - // Add memory pressure to the runtime's GC calculations. - HRESULT AddMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept; - - // Remove memory pressure from the runtime's GC calculations. - HRESULT RemoveMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept; - - enum class GcRequest - { - Default, - FullBlocking // This is an expensive GC request, akin to a Gen2/"stop the world" GC. - }; - - // Request a GC from the runtime. - HRESULT RequestGarbageCollectionForExternal(_In_ GcRequest req) noexcept; - - // Wait for the runtime's finalizer to clean up objects. - HRESULT WaitForRuntimeFinalizerForExternal() noexcept; - - // Release objects associated with the current thread. - HRESULT ReleaseExternalObjectsFromCurrentThread() noexcept; - - // Delete Object instance handle. - void DeleteObjectInstanceHandle(_In_ InteropLib::OBJECTHANDLE handle) noexcept; - // Check if Object instance handle still points at an Object. bool HasValidTarget(_In_ InteropLib::OBJECTHANDLE handle) noexcept; + void DestroyHandle(_In_ InteropLib::OBJECTHANDLE handle) noexcept; + + bool IsObjectPromoted(_In_ InteropLib::OBJECTHANDLE handle) noexcept; + // Get the current global pegging state. bool GetGlobalPeggingState() noexcept; @@ -54,26 +22,17 @@ namespace InteropLibImports void SetGlobalPeggingState(_In_ bool state) noexcept; // Get next External Object Context from the Runtime calling context. - // S_OK - Context is valid. - // S_FALSE - Iterator has reached end and context out parameter is set to NULL. - HRESULT IteratorNext( + bool IteratorNext( _In_ RuntimeCallContext* runtimeContext, - _Outptr_result_maybenull_ void** extObjContext) noexcept; + _Outptr_result_maybenull_ void** trackerTarget, + _Outptr_result_maybenull_ InteropLib::OBJECTHANDLE* proxyObject) noexcept; // Tell the runtime a reference path between the External Object Context and // OBJECTHANDLE was found. HRESULT FoundReferencePath( _In_ RuntimeCallContext* runtimeContext, - _In_ void* extObjContext, - _In_ InteropLib::OBJECTHANDLE handle) noexcept; - - // Get or create an IReferenceTrackerTarget instance for the supplied - // external object. - HRESULT GetOrCreateTrackerTargetForExternal( - _In_ IUnknown* externalComObject, - _In_ InteropLib::Com::CreateObjectFlags externalObjectFlags, - _In_ InteropLib::Com::CreateComInterfaceFlags trackerTargetFlags, - _Outptr_ void** trackerTarget) noexcept; + _In_ InteropLib::OBJECTHANDLE sourceHandle, + _In_ InteropLib::OBJECTHANDLE targetHandle) noexcept; // The enum describes the value of System.Runtime.InteropServices.CustomQueryInterfaceResult // and the case where the object doesn't support ICustomQueryInterface. diff --git a/src/coreclr/interop/interoplib.cpp b/src/coreclr/interop/interoplib.cpp index 452df3cce39b79..b86842bcc3f0fe 100644 --- a/src/coreclr/interop/interoplib.cpp +++ b/src/coreclr/interop/interoplib.cpp @@ -3,6 +3,7 @@ #include "platform.h" #include +#include #include #ifdef FEATURE_COMWRAPPERS @@ -18,65 +19,6 @@ namespace InteropLib // Exposed COM related API namespace Com { - HRESULT CreateWrapperForObject( - _In_ OBJECTHANDLE instance, - _In_ INT32 vtableCount, - _In_ void* vtablesRaw, - _In_ enum CreateComInterfaceFlags flags, - _Outptr_ IUnknown** wrapper) noexcept - { - _ASSERTE(instance != nullptr && wrapper != nullptr); - - // Validate the supplied vtable data is valid with a - // reasonable count. - if ((vtablesRaw == nullptr && vtableCount != 0) || vtableCount < 0) - return E_INVALIDARG; - - HRESULT hr; - - // Convert input to appropriate types. - auto vtables = static_cast<::ABI::ComInterfaceEntry*>(vtablesRaw); - - ManagedObjectWrapper* mow; - RETURN_IF_FAILED(ManagedObjectWrapper::Create(flags, instance, vtableCount, vtables, &mow)); - - *wrapper = static_cast(mow->As(IID_IUnknown)); - return S_OK; - } - - void DestroyWrapperForObject(_In_ void* wrapperMaybe) noexcept - { - ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknownWithQueryInterface(static_cast(wrapperMaybe)); - - // A caller should not be destroying a wrapper without knowing if the wrapper is valid. - _ASSERTE(wrapper != nullptr); - - ManagedObjectWrapper::Destroy(wrapper); - } - - HRESULT IsWrapperRooted(_In_ IUnknown* wrapperMaybe) noexcept - { - ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknown(wrapperMaybe); - if (wrapper == nullptr) - return E_INVALIDARG; - - return wrapper->IsRooted() ? S_OK : S_FALSE; - } - - HRESULT GetObjectForWrapper(_In_ IUnknown* wrapper, _Outptr_result_maybenull_ OBJECTHANDLE* object) noexcept - { - _ASSERTE(wrapper != nullptr && object != nullptr); - *object = nullptr; - - // Attempt to get the managed object wrapper. - ManagedObjectWrapper *mow = ManagedObjectWrapper::MapFromIUnknownWithQueryInterface(wrapper); - if (mow == nullptr) - return E_INVALIDARG; - - *object = mow->Target; - return S_OK; - } - HRESULT MarkComActivated(_In_ IUnknown* wrapperMaybe) noexcept { ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknownWithQueryInterface(wrapperMaybe); @@ -87,143 +29,52 @@ namespace InteropLib return S_OK; } - HRESULT IsComActivated(_In_ IUnknown* wrapperMaybe) noexcept + void GetIUnknownImpl( + _Out_ void** fpQueryInterface, + _Out_ void** fpAddRef, + _Out_ void** fpRelease) noexcept { - ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknownWithQueryInterface(wrapperMaybe); - if (wrapper == nullptr) - return E_INVALIDARG; - - return wrapper->IsSet(CreateComInterfaceFlagsEx::IsComActivated) ? S_OK : S_FALSE; + ManagedObjectWrapper::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); } - HRESULT DetermineIdentityAndInnerForExternal( - _In_ IUnknown* external, - _In_ enum CreateObjectFlags flags, - _Outptr_ IUnknown** identity, - _Inout_ IUnknown** innerMaybe) noexcept + void const* GetTaggedCurrentVersionImpl() noexcept { - _ASSERTE(external != nullptr && identity != nullptr && innerMaybe != nullptr); - - IUnknown* checkForIdentity = external; - - // Check if the flags indicate we are creating - // an object for an external IReferenceTracker instance - // that we are aggregating with. - bool refTrackerInnerScenario = (flags & CreateObjectFlags_TrackerObject) - && (flags & CreateObjectFlags_Aggregated); - - ComHolder trackerObject; - if (refTrackerInnerScenario) - { - // We are checking the supplied external value - // for IReferenceTracker since in .NET 5 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. - HRESULT hr = external->QueryInterface(IID_IReferenceTracker, (void**)&trackerObject); - if (SUCCEEDED(hr)) - checkForIdentity = trackerObject.p; - } - - HRESULT hr; - - IUnknown* identityLocal; - RETURN_IF_FAILED(checkForIdentity->QueryInterface(IID_IUnknown, (void **)&identityLocal)); - - // Set the inner if scenario dictates an update. - if (*innerMaybe == nullptr // User didn't supply inner - .NET 5 API scenario sanity check. - && checkForIdentity != external // Target of check was changed - .NET 5 API scenario sanity check. - && external != identityLocal // The supplied object doesn't match the computed identity. - && refTrackerInnerScenario) // The appropriate flags were set. - { - *innerMaybe = external; - } - - *identity = identityLocal; - return S_OK; + return ManagedObjectWrapper::GetTaggedCurrentVersionImpl(); } - HRESULT CreateWrapperForExternal( - _In_ IUnknown* external, - _In_opt_ IUnknown* inner, - _In_ enum CreateObjectFlags flags, - _In_ size_t contextSize, - _Out_ ExternalWrapperResult* result) noexcept + HRESULT BeginExternalObjectReferenceTracking(_In_ RuntimeCallContext* cxt) noexcept { - _ASSERTE(external != nullptr && result != nullptr); - - HRESULT hr; - - NativeObjectWrapperContext* wrapperContext; - RETURN_IF_FAILED(NativeObjectWrapperContext::Create(external, inner, flags, contextSize, &wrapperContext)); - - result->Context = wrapperContext->GetRuntimeContext(); - result->FromTrackerRuntime = (wrapperContext->GetReferenceTracker() != nullptr); - result->ManagedObjectWrapper = (ManagedObjectWrapper::MapFromIUnknownWithQueryInterface(external) != nullptr); - return S_OK; + return TrackerObjectManager::BeginReferenceTracking(cxt); } - void NotifyWrapperForExternalIsBeingCollected(_In_ void* contextMaybe) noexcept - { - NativeObjectWrapperContext* context = NativeObjectWrapperContext::MapFromRuntimeContext(contextMaybe); - - // A caller should not be destroying a context without knowing if the context is valid. - _ASSERTE(context != nullptr); - - // Check if the tracker object manager should be informed of collection. - IReferenceTracker* trackerMaybe = context->GetReferenceTracker(); - if (trackerMaybe != nullptr) - { - // We only call this during a GC so ignore the failure as - // there is no way we can handle it at this point. - HRESULT hr = TrackerObjectManager::BeforeWrapperFinalized(trackerMaybe); - _ASSERTE(SUCCEEDED(hr)); - (void)hr; - } + HRESULT EndExternalObjectReferenceTracking() noexcept + { + return TrackerObjectManager::EndReferenceTracking(); } - void DestroyWrapperForExternal(_In_ void* contextMaybe, _In_ bool notifyIsBeingCollected) noexcept + HRESULT DetachNonPromotedObjects(_In_ RuntimeCallContext* cxt) noexcept { - NativeObjectWrapperContext* context = NativeObjectWrapperContext::MapFromRuntimeContext(contextMaybe); - - // A caller should not be destroying a context without knowing if the context is valid. - _ASSERTE(context != nullptr); - - if (notifyIsBeingCollected) - NotifyWrapperForExternalIsBeingCollected(contextMaybe); - - NativeObjectWrapperContext::Destroy(context); - } + return TrackerObjectManager::DetachNonPromotedObjects(cxt); + } - void SeparateWrapperFromTrackerRuntime(_In_ void* contextMaybe) noexcept + void const* GetIReferenceTrackerTargetVftbl() noexcept { - NativeObjectWrapperContext* context = NativeObjectWrapperContext::MapFromRuntimeContext(contextMaybe); - - // A caller should not be separating a context without knowing if the context is valid. - _ASSERTE(context != nullptr); - - context->DisconnectTracker(); + return ManagedObjectWrapper::GetIReferenceTrackerTargetImpl(); } - void GetIUnknownImpl( - _Out_ void** fpQueryInterface, - _Out_ void** fpAddRef, - _Out_ void** fpRelease) noexcept + bool HasReferenceTrackerManager() noexcept { - ManagedObjectWrapper::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); + return TrackerObjectManager::HasReferenceTrackerManager(); } - HRESULT BeginExternalObjectReferenceTracking(_In_ RuntimeCallContext* cxt) noexcept + bool TryRegisterReferenceTrackerManager(_In_ void* manager) noexcept { - return TrackerObjectManager::BeginReferenceTracking(cxt); + return TrackerObjectManager::TryRegisterReferenceTrackerManager((IReferenceTrackerManager*)manager); } - HRESULT EndExternalObjectReferenceTracking() noexcept + bool IsRooted(InteropLib::ABI::ManagedObjectWrapperLayout* mow) noexcept { - return TrackerObjectManager::EndReferenceTracking(); + return reinterpret_cast(mow)->IsRooted(); } } diff --git a/src/coreclr/interop/trackerobjectmanager.cpp b/src/coreclr/interop/trackerobjectmanager.cpp index 0df78164906dcc..cc178d4187179b 100644 --- a/src/coreclr/interop/trackerobjectmanager.cpp +++ b/src/coreclr/interop/trackerobjectmanager.cpp @@ -9,148 +9,11 @@ using RuntimeCallContext = InteropLibImports::RuntimeCallContext; namespace { - // 29a71c6a-3c42-4416-a39d-e2825a07a773 - const GUID IID_IReferenceTrackerHost = { 0x29a71c6a, 0x3c42, 0x4416, { 0xa3, 0x9d, 0xe2, 0x82, 0x5a, 0x7, 0xa7, 0x73} }; - - // 3cf184b4-7ccb-4dda-8455-7e6ce99a3298 - const GUID IID_IReferenceTrackerManager = { 0x3cf184b4, 0x7ccb, 0x4dda, { 0x84, 0x55, 0x7e, 0x6c, 0xe9, 0x9a, 0x32, 0x98} }; - // 04b3486c-4687-4229-8d14-505ab584dd88 const GUID IID_IFindReferenceTargetsCallback = { 0x04b3486c, 0x4687, 0x4229, { 0x8d, 0x14, 0x50, 0x5a, 0xb5, 0x84, 0xdd, 0x88} }; - // In order to minimize the impact of a constructor running on module load, - // the HostServices class should have no instance fields. - class HostServices : public IReferenceTrackerHost - { - public: // IReferenceTrackerHost - STDMETHOD(DisconnectUnusedReferenceSources)(_In_ DWORD dwFlags); - STDMETHOD(ReleaseDisconnectedReferenceSources)(); - STDMETHOD(NotifyEndOfReferenceTrackingOnThread)(); - STDMETHOD(GetTrackerTarget)(_In_ IUnknown* obj, _Outptr_ IReferenceTrackerTarget** ppNewReference); - STDMETHOD(AddMemoryPressure)(_In_ UINT64 bytesAllocated); - STDMETHOD(RemoveMemoryPressure)(_In_ UINT64 bytesAllocated); - - public: // IUnknown - // 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, IID_IReferenceTrackerHost)) - { - *ppvObject = static_cast(this); - } - else if (IsEqualIID(riid, IID_IUnknown)) - { - *ppvObject = static_cast(this); - } - else - { - *ppvObject = nullptr; - return E_NOINTERFACE; - } - - (void)AddRef(); - return S_OK; - } - }; - - // Global instance of host services. - HostServices g_HostServicesInstance; - - // Defined in windows.ui.xaml.hosting.referencetracker.h. - enum XAML_REFERENCETRACKER_DISCONNECT - { - // Indicates the disconnect is during a suspend and a GC can be trigger. - XAML_REFERENCETRACKER_DISCONNECT_SUSPEND = 0x00000001 - }; - - STDMETHODIMP HostServices::DisconnectUnusedReferenceSources(_In_ DWORD flags) - { - InteropLibImports::GcRequest type = InteropLibImports::GcRequest::Default; - - // Request a "stop the world" GC when a suspend is occurring. - if (flags & XAML_REFERENCETRACKER_DISCONNECT_SUSPEND) - type = InteropLibImports::GcRequest::FullBlocking; - - return InteropLibImports::RequestGarbageCollectionForExternal(type); - } - - STDMETHODIMP HostServices::ReleaseDisconnectedReferenceSources() - { - // We'd like to call InteropLibImports::WaitForRuntimeFinalizerForExternal() here, but this could - // lead to deadlock if the finalizer thread is trying to get back to this thread, because we are - // not pumping anymore. Disable this for now. See: https://github.com/dotnet/runtime/issues/109538. - return S_OK; - } - - STDMETHODIMP HostServices::NotifyEndOfReferenceTrackingOnThread() - { - return InteropLibImports::ReleaseExternalObjectsFromCurrentThread(); - } - - // 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: - // - // 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. - // - STDMETHODIMP HostServices::GetTrackerTarget(_In_ IUnknown* obj, _Outptr_ IReferenceTrackerTarget** ppNewReference) - { - if (obj == nullptr || ppNewReference == nullptr) - return E_INVALIDARG; - - HRESULT hr; - - // QI for IUnknown to get the identity unknown - ComHolder identity; - RETURN_IF_FAILED(obj->QueryInterface(IID_IUnknown, (void**)&identity)); - - // Get or create an existing implementation for this external. - ComHolder target; - RETURN_IF_FAILED(InteropLibImports::GetOrCreateTrackerTargetForExternal( - identity, - InteropLib::Com::CreateObjectFlags_TrackerObject, - InteropLib::Com::CreateComInterfaceFlags_TrackerSupport, - (void**)&target)); - - return target->QueryInterface(IID_IReferenceTrackerTarget, (void**)ppNewReference); - } - - STDMETHODIMP HostServices::AddMemoryPressure(_In_ UINT64 bytesAllocated) - { - return InteropLibImports::AddMemoryPressureForExternal(bytesAllocated); - } - - STDMETHODIMP HostServices::RemoveMemoryPressure(_In_ UINT64 bytesAllocated) - { - return InteropLibImports::RemoveMemoryPressureForExternal(bytesAllocated); - } - VolatilePtr s_TrackerManager; // The one and only Tracker Manager instance - Volatile s_HasTrackingStarted = FALSE; + Volatile s_HasTrackingStarted = false; // Indicates if walking the external objects is needed. // (i.e. Have any IReferenceTracker instances been found?) @@ -160,17 +23,17 @@ namespace } // Callback implementation of IFindReferenceTargetsCallback - class FindDependentWrappersCallback : public IFindReferenceTargetsCallback + class FindDependentWrappersCallback final : public IFindReferenceTargetsCallback { - NativeObjectWrapperContext* _nowCxt; + OBJECTHANDLE _sourceHandle; RuntimeCallContext* _runtimeCallCxt; public: - FindDependentWrappersCallback(_In_ NativeObjectWrapperContext* nowCxt, _In_ RuntimeCallContext* runtimeCallCxt) - : _nowCxt{ nowCxt } + FindDependentWrappersCallback(_In_ OBJECTHANDLE sourceHandle, _In_ RuntimeCallContext* runtimeCallCxt) + : _sourceHandle{ sourceHandle } , _runtimeCallCxt{ runtimeCallCxt } { - _ASSERTE(_nowCxt != nullptr && runtimeCallCxt != nullptr); + _ASSERTE(_sourceHandle != nullptr && runtimeCallCxt != nullptr); } STDMETHOD(FoundTrackerTarget)(_In_ IReferenceTrackerTarget* target) @@ -189,8 +52,8 @@ namespace // Notify the runtime a reference path was found. RETURN_IF_FAILED(InteropLibImports::FoundReferencePath( _runtimeCallCxt, - _nowCxt->GetRuntimeContext(), - mow->Target)); + _sourceHandle, + mow->GetTarget())); return S_OK; } @@ -229,24 +92,19 @@ namespace { _ASSERTE(cxt != nullptr); - BOOL walkFailed = FALSE; - HRESULT hr; + bool walkFailed = false; + HRESULT hr = S_OK; - void* extObjContext = nullptr; - while (S_OK == (hr = InteropLibImports::IteratorNext(cxt, &extObjContext))) + IReferenceTracker* trackerTarget = nullptr; + OBJECTHANDLE proxyObject = nullptr; + while (InteropLibImports::IteratorNext(cxt, (void**)&trackerTarget, &proxyObject)) { - _ASSERTE(extObjContext != nullptr); - - NativeObjectWrapperContext* nowc = NativeObjectWrapperContext::MapFromRuntimeContext(extObjContext); - - // Check if the object is a tracker object. - IReferenceTracker* trackerMaybe = nowc->GetReferenceTracker(); - if (trackerMaybe == nullptr) + if (trackerTarget == nullptr) continue; // Ask the tracker instance to find all reference targets. - FindDependentWrappersCallback cb{ nowc, cxt }; - hr = trackerMaybe->FindTrackerTargets(&cb); + FindDependentWrappersCallback cb{ proxyObject, cxt }; + hr = trackerTarget->FindTrackerTargets(&cb); if (FAILED(hr)) break; } @@ -254,58 +112,26 @@ namespace if (FAILED(hr)) { // Remember the fact that we've failed and stop walking - walkFailed = TRUE; + walkFailed = true; InteropLibImports::SetGlobalPeggingState(true); } _ASSERTE(s_TrackerManager != nullptr); - (void)s_TrackerManager->FindTrackerTargetsCompleted(walkFailed); + (void)s_TrackerManager->FindTrackerTargetsCompleted(walkFailed ? TRUE : FALSE); return hr; } } -HRESULT TrackerObjectManager::OnIReferenceTrackerFound(_In_ IReferenceTracker* obj) +bool TrackerObjectManager::HasReferenceTrackerManager() { - _ASSERTE(obj != nullptr); - if (s_TrackerManager != nullptr) - return S_OK; - - // Retrieve IReferenceTrackerManager - HRESULT hr; - ComHolder trackerManager; - RETURN_IF_FAILED(obj->GetReferenceTrackerManager(&trackerManager)); - - ComHolder hostServices; - RETURN_IF_FAILED(g_HostServicesInstance.QueryInterface(IID_IReferenceTrackerHost, (void**)&hostServices)); - - // Attempt to set the tracker instance. - if (InterlockedCompareExchangePointer((void**)&s_TrackerManager, trackerManager.p, nullptr) == nullptr) - { - (void)trackerManager.Detach(); // Ownership has been transferred - RETURN_IF_FAILED(s_TrackerManager->SetReferenceTrackerHost(hostServices)); - } - - return S_OK; + return s_TrackerManager != nullptr; } -HRESULT TrackerObjectManager::AfterWrapperCreated(_In_ IReferenceTracker* obj) +bool TrackerObjectManager::TryRegisterReferenceTrackerManager(_In_ IReferenceTrackerManager* manager) { - _ASSERTE(obj != nullptr); - - HRESULT hr; - - // 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. - RETURN_IF_FAILED(obj->ConnectFromTrackerSource()); - - // 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 - RETURN_IF_FAILED(obj->AddRefFromTrackerSource()); // IUnknown - RETURN_IF_FAILED(obj->AddRefFromTrackerSource()); // IReferenceTracker - - return S_OK; + _ASSERTE(manager != nullptr); + return InterlockedCompareExchangePointer((void**)&s_TrackerManager, manager, nullptr) == nullptr; } HRESULT TrackerObjectManager::BeforeWrapperFinalized(_In_ IReferenceTracker* obj) @@ -332,10 +158,10 @@ HRESULT TrackerObjectManager::BeginReferenceTracking(_In_ RuntimeCallContext* cx HRESULT hr; - _ASSERTE(s_HasTrackingStarted == FALSE); + _ASSERTE(s_HasTrackingStarted == false); _ASSERTE(InteropLibImports::GetGlobalPeggingState()); - s_HasTrackingStarted = TRUE; + 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 @@ -356,7 +182,7 @@ HRESULT TrackerObjectManager::BeginReferenceTracking(_In_ RuntimeCallContext* cx HRESULT TrackerObjectManager::EndReferenceTracking() { - if (s_HasTrackingStarted != TRUE + if (s_HasTrackingStarted != true || !ShouldWalkExternalObjects()) return S_FALSE; @@ -372,7 +198,31 @@ HRESULT TrackerObjectManager::EndReferenceTracking() _ASSERTE(SUCCEEDED(hr)); InteropLibImports::SetGlobalPeggingState(true); - s_HasTrackingStarted = FALSE; + s_HasTrackingStarted = false; return hr; } + +HRESULT TrackerObjectManager::DetachNonPromotedObjects(_In_ RuntimeCallContext* cxt) +{ + _ASSERTE(cxt != nullptr); + + HRESULT hr; + IReferenceTracker* trackerTarget = nullptr; + OBJECTHANDLE proxyObject = NULL; + while (InteropLibImports::IteratorNext(cxt, (void**)&trackerTarget, &proxyObject)) + { + if (trackerTarget == nullptr) + continue; + + if (proxyObject == nullptr) + continue; + + if (!InteropLibImports::IsObjectPromoted(proxyObject)) + { + RETURN_IF_FAILED(BeforeWrapperFinalized(trackerTarget)); + } + } + + return S_OK; +} 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 d7f5454259ee61..f687c6bb354546 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 @@ -158,6 +158,7 @@ + @@ -168,7 +169,6 @@ - @@ -277,9 +277,6 @@ Interop\Windows\Ole32\Interop.CoGetApartmentType.cs - - Interop\Windows\Ole32\Interop.CoGetContextToken.cs - Interop\Windows\OleAut32\Interop.VariantClear.cs @@ -575,11 +572,7 @@ - + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs index 67d09f8b246a6d..6f6aec5df683aa 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs @@ -141,6 +141,11 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking) } public static void Collect(int generation, GCCollectionMode mode, bool blocking, bool compacting) + { + Collect(generation, mode, blocking, compacting, lowMemoryPressure: false); + } + + internal static void Collect(int generation, GCCollectionMode mode, bool blocking, bool compacting, bool lowMemoryPressure) { ArgumentOutOfRangeException.ThrowIfNegative(generation); @@ -186,7 +191,7 @@ public static void Collect(int generation, GCCollectionMode mode, bool blocking, iInternalModes |= (int)InternalGCCollectionMode.NonBlocking; } - RuntimeImports.RhCollect(generation, (InternalGCCollectionMode)iInternalModes); + RuntimeImports.RhCollect(generation, (InternalGCCollectionMode)iInternalModes, lowMemoryPressure); } /// diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.NativeAot.cs new file mode 100644 index 00000000000000..6e630e096fd762 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.NativeAot.cs @@ -0,0 +1,27 @@ +// 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.CompilerServices; + +namespace System +{ + internal sealed partial class ComAwareWeakReference + { + internal static unsafe object? ComWeakRefToObject(IntPtr pComWeakRef, object? context) + { + return ComWeakRefToComWrappersObject(pComWeakRef, context); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe bool PossiblyComObject(object target) + { + return PossiblyComWrappersObject(target); + } + + internal static unsafe IntPtr ObjectToComWeakRef(object target, out object? context) + { + return ComWrappersObjectToComWeakRef(target, out context); + } + } +} 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 88f392363573e0..0cc40f0f46c894 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 @@ -18,1461 +18,42 @@ namespace System.Runtime.InteropServices /// public abstract partial class ComWrappers { - private const int TrackerRefShift = 32; - private const ulong TrackerRefCounter = 1UL << TrackerRefShift; - private const ulong DestroySentinel = 0x0000000080000000UL; - private const ulong TrackerRefCountMask = 0xffffffff00000000UL; - private const ulong ComRefCountMask = 0x000000007fffffffUL; - private const int COR_E_ACCESSING_CCW = unchecked((int)0x80131544); - - internal static IntPtr DefaultIUnknownVftblPtr { get; } = CreateDefaultIUnknownVftbl(); - internal static IntPtr TaggedImplVftblPtr { get; } = CreateTaggedImplVftbl(); - internal static IntPtr DefaultIReferenceTrackerTargetVftblPtr { get; } = CreateDefaultIReferenceTrackerTargetVftbl(); - - 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_nativeObjectWrapperTable = new ConditionalWeakTable(); - private static readonly GCHandleSet s_referenceTrackerNativeObjectWrapperCache = new GCHandleSet(); - - private readonly ConditionalWeakTable _managedObjectWrapperTable = new ConditionalWeakTable(); - private readonly RcwCache _rcwCache = new(); - - internal static bool TryGetComInstanceForIID(object obj, Guid iid, out IntPtr unknown, out long wrapperId) - { - if (obj == null - || !s_nativeObjectWrapperTable.TryGetValue(obj, out NativeObjectWrapper? wrapper)) - { - unknown = IntPtr.Zero; - wrapperId = 0; - return false; - } - - wrapperId = wrapper.ComWrappers.id; - return Marshal.QueryInterface(wrapper.ExternalComObject, iid, out unknown) == HResults.S_OK; - } - - public static unsafe bool TryGetComInstance(object obj, out IntPtr unknown) - { - unknown = IntPtr.Zero; - if (obj == null - || !s_nativeObjectWrapperTable.TryGetValue(obj, out NativeObjectWrapper? wrapper)) - { - return false; - } - - return Marshal.QueryInterface(wrapper.ExternalComObject, IID_IUnknown, out unknown) == HResults.S_OK; - } - - public static unsafe bool TryGetObject(IntPtr unknown, [NotNullWhen(true)] out object? obj) - { - obj = null; - if (unknown == IntPtr.Zero) - { - return false; - } - - ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(unknown); - if (comInterfaceDispatch == null || - ComInterfaceDispatch.ToManagedObjectWrapper(comInterfaceDispatch)->MarkedToDestroy) - { - return false; - } - - obj = ComInterfaceDispatch.GetInstance(comInterfaceDispatch); - return true; - } - - /// - /// ABI for function dispatch of a COM interface. - /// - public unsafe partial struct ComInterfaceDispatch - { - /// - /// Given a from a generated Vtable, convert to the target type. - /// - /// Desired type. - /// Pointer supplied to Vtable function entry. - /// Instance of type associated with dispatched function call. - public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class - { - ManagedObjectWrapper* comInstance = ToManagedObjectWrapper(dispatchPtr); - return Unsafe.As(comInstance->Holder.WrappedObject); - } - - internal static unsafe ManagedObjectWrapper* ToManagedObjectWrapper(ComInterfaceDispatch* dispatchPtr) - { - InternalComInterfaceDispatch* dispatch = (InternalComInterfaceDispatch*)unchecked((nuint)dispatchPtr & (nuint)InternalComInterfaceDispatch.DispatchAlignmentMask); - return dispatch->_thisPtr; - } - } - - internal unsafe struct InternalComInterfaceDispatch - { -#if TARGET_64BIT - internal const int DispatchAlignment = 64; - internal const int NumEntriesInDispatchTable = DispatchAlignment / 8 /* sizeof(void*) */ - 1; -#else - internal const int DispatchAlignment = 16; - internal const int NumEntriesInDispatchTable = DispatchAlignment / 4 /* sizeof(void*) */ - 1; -#endif - internal const ulong DispatchAlignmentMask = unchecked((ulong)~(InternalComInterfaceDispatch.DispatchAlignment - 1)); - - internal ManagedObjectWrapper* _thisPtr; - - public DispatchTable Vtables; - - [InlineArray(NumEntriesInDispatchTable)] - internal unsafe struct DispatchTable - { - private IntPtr _element; - } - } - - internal enum CreateComInterfaceFlagsEx - { - None = 0, - - /// - /// The caller will provide an IUnknown Vtable. - /// - /// - /// This is useful in scenarios when the caller has no need to rely on an IUnknown instance - /// that is used when running managed code is not possible (i.e. during a GC). In traditional - /// COM scenarios this is common, but scenarios involving Reference Tracker hosting - /// calling of the IUnknown API during a GC is possible. - /// - CallerDefinedIUnknown = 1, - - /// - /// Flag used to indicate the COM interface should implement IReferenceTrackerTarget. - /// When this flag is passed, the resulting COM interface will have an internal implementation of IUnknown - /// and as such none should be supplied by the caller. - /// - TrackerSupport = 2, - - LacksICustomQueryInterface = 1 << 29, - IsComActivated = 1 << 30, - IsPegged = 1 << 31, - - InternalMask = IsPegged | IsComActivated | LacksICustomQueryInterface, - } - - internal unsafe struct ManagedObjectWrapper - { - public volatile IntPtr HolderHandle; // This is GC Handle - public ulong RefCount; - - public int UserDefinedCount; - public ComInterfaceEntry* UserDefined; - internal InternalComInterfaceDispatch* Dispatches; - - internal CreateComInterfaceFlagsEx Flags; - - public bool IsRooted - { - get - { - ulong refCount = Interlocked.Read(ref RefCount); - bool rooted = GetComCount(refCount) > 0; - if (!rooted) - { - rooted = GetTrackerCount(refCount) > 0 && - ((Flags & CreateComInterfaceFlagsEx.IsPegged) != 0 || TrackerObjectManager.s_isGlobalPeggingOn); - } - return rooted; - } - } - - public ManagedObjectWrapperHolder? Holder - { - get - { - IntPtr handle = HolderHandle; - if (handle == IntPtr.Zero) - return null; - else - return Unsafe.As(GCHandle.FromIntPtr(handle).Target); - } - } - - public readonly bool MarkedToDestroy => IsMarkedToDestroy(RefCount); - - public uint AddRef() - { - return GetComCount(Interlocked.Increment(ref RefCount)); - } - - public uint Release() - { - Debug.Assert(GetComCount(RefCount) != 0); - return GetComCount(Interlocked.Decrement(ref RefCount)); - } - - public uint AddRefFromReferenceTracker() - { - ulong prev; - ulong curr; - do - { - prev = RefCount; - curr = prev + TrackerRefCounter; - } while (Interlocked.CompareExchange(ref RefCount, curr, prev) != prev); - - return GetTrackerCount(curr); - } - - public uint ReleaseFromReferenceTracker() - { - Debug.Assert(GetTrackerCount(RefCount) != 0); - ulong prev; - ulong curr; - do - { - prev = RefCount; - curr = prev - TrackerRefCounter; - } - while (Interlocked.CompareExchange(ref RefCount, curr, prev) != prev); - - // If we observe the destroy sentinel, then this release - // must destroy the wrapper. - if (curr == DestroySentinel) - Destroy(); - - return GetTrackerCount(curr); - } - - public uint Peg() - { - SetFlag(CreateComInterfaceFlagsEx.IsPegged); - return HResults.S_OK; - } - - public uint Unpeg() - { - ResetFlag(CreateComInterfaceFlagsEx.IsPegged); - return HResults.S_OK; - } - - - public unsafe int QueryInterfaceForTracker(in Guid riid, out IntPtr ppvObject) - { - if (IsMarkedToDestroy(RefCount) || Holder is null) - { - ppvObject = IntPtr.Zero; - return COR_E_ACCESSING_CCW; - } - - return QueryInterface(in riid, out ppvObject); - } - - public unsafe int QueryInterface(in Guid riid, out IntPtr ppvObject) - { - ppvObject = AsRuntimeDefined(in riid); - if (ppvObject == IntPtr.Zero) - { - if ((Flags & CreateComInterfaceFlagsEx.LacksICustomQueryInterface) == 0) - { - var customQueryInterface = Holder.WrappedObject as ICustomQueryInterface; - if (customQueryInterface is null) - { - SetFlag(CreateComInterfaceFlagsEx.LacksICustomQueryInterface); - } - else - { - Guid riidLocal = riid; - switch (customQueryInterface.GetInterface(ref riidLocal, out ppvObject)) - { - case CustomQueryInterfaceResult.Handled: - return HResults.S_OK; - case CustomQueryInterfaceResult.NotHandled: - break; - case CustomQueryInterfaceResult.Failed: - return HResults.COR_E_INVALIDCAST; - } - } - } - - ppvObject = AsUserDefined(in riid); - if (ppvObject == IntPtr.Zero) - return HResults.COR_E_INVALIDCAST; - } - - AddRef(); - return HResults.S_OK; - } - - public IntPtr As(in Guid riid) - { - // Find target interface and return dispatcher or null if not found. - IntPtr typeMaybe = AsRuntimeDefined(in riid); - if (typeMaybe == IntPtr.Zero) - typeMaybe = AsUserDefined(in riid); - - return typeMaybe; - } - - /// true if actually destroyed - public unsafe bool Destroy() - { - Debug.Assert(GetComCount(RefCount) == 0 || HolderHandle == IntPtr.Zero); - - if (HolderHandle == IntPtr.Zero) - { - // We either were previously destroyed or multiple ManagedObjectWrapperHolder - // were created by the ConditionalWeakTable for the same object and we lost the race. - return true; - } - - ulong prev, refCount; - do - { - prev = RefCount; - refCount = prev | DestroySentinel; - } while (Interlocked.CompareExchange(ref RefCount, refCount, prev) != prev); - - if (refCount == DestroySentinel) - { - IntPtr handle = Interlocked.Exchange(ref HolderHandle, IntPtr.Zero); - if (handle != IntPtr.Zero) - { - RuntimeImports.RhHandleFree(handle); - } - return true; - } - else - { - return false; - } - } - - private unsafe IntPtr GetDispatchPointerAtIndex(int index) - { - InternalComInterfaceDispatch* dispatch = &Dispatches[index / InternalComInterfaceDispatch.NumEntriesInDispatchTable]; - IntPtr* vtables = (IntPtr*)(void*)&dispatch->Vtables; - return (IntPtr)(&vtables[index % InternalComInterfaceDispatch.NumEntriesInDispatchTable]); - } - - private unsafe IntPtr AsRuntimeDefined(in Guid riid) - { - // The order of interface lookup here is important. - // See CreateManagedObjectWrapper() for the expected order. - int i = UserDefinedCount; - if ((Flags & CreateComInterfaceFlagsEx.CallerDefinedIUnknown) == 0) - { - if (riid == IID_IUnknown) - { - return GetDispatchPointerAtIndex(i); - } - - i++; - } - - if ((Flags & CreateComInterfaceFlagsEx.TrackerSupport) != 0) - { - if (riid == IID_IReferenceTrackerTarget) - { - return GetDispatchPointerAtIndex(i); - } - - i++; - } - - { - if (riid == IID_TaggedImpl) - { - return GetDispatchPointerAtIndex(i); - } - } - - return IntPtr.Zero; - } - - private unsafe IntPtr AsUserDefined(in Guid riid) - { - for (int i = 0; i < UserDefinedCount; ++i) - { - if (UserDefined[i].IID == riid) - { - return GetDispatchPointerAtIndex(i); - } - } - - return IntPtr.Zero; - } - - private void SetFlag(CreateComInterfaceFlagsEx flag) - { - int setMask = (int)flag; - Interlocked.Or(ref Unsafe.As(ref Flags), setMask); - } - - private void ResetFlag(CreateComInterfaceFlagsEx flag) - { - int resetMask = ~(int)flag; - Interlocked.And(ref Unsafe.As(ref Flags), resetMask); - } - - private static uint GetTrackerCount(ulong c) - { - return (uint)((c & TrackerRefCountMask) >> TrackerRefShift); - } - - private static uint GetComCount(ulong c) - { - return (uint)(c & ComRefCountMask); - } - - private static bool IsMarkedToDestroy(ulong c) - { - return (c & DestroySentinel) != 0; - } - } - - internal sealed unsafe class ManagedObjectWrapperHolder - { - static ManagedObjectWrapperHolder() - { - delegate* unmanaged callback = &IsRootedCallback; - if (!RuntimeImports.RhRegisterRefCountedHandleCallback((nint)callback, MethodTable.Of())) - { - throw new OutOfMemoryException(); - } - } - - [UnmanagedCallersOnly] - private static bool IsRootedCallback(IntPtr pObj) - { - // We are paused in the GC, so this is safe. - ManagedObjectWrapperHolder* holder = (ManagedObjectWrapperHolder*)&pObj; - return holder->_wrapper->IsRooted; - } - - private readonly ManagedObjectWrapper* _wrapper; - private readonly ManagedObjectWrapperReleaser _releaser; - private readonly object _wrappedObject; - - public ManagedObjectWrapperHolder(ManagedObjectWrapper* wrapper, object wrappedObject) - { - _wrapper = wrapper; - _wrappedObject = wrappedObject; - _releaser = new ManagedObjectWrapperReleaser(wrapper); - _wrapper->HolderHandle = RuntimeImports.RhHandleAllocRefCounted(this); - } - - public unsafe IntPtr ComIp => _wrapper->As(in ComWrappers.IID_IUnknown); - - public object WrappedObject => _wrappedObject; - - public uint AddRef() => _wrapper->AddRef(); - } - - internal sealed unsafe class ManagedObjectWrapperReleaser - { - private ManagedObjectWrapper* _wrapper; - - public ManagedObjectWrapperReleaser(ManagedObjectWrapper* wrapper) - { - _wrapper = wrapper; - } - - ~ManagedObjectWrapperReleaser() - { - IntPtr refCountedHandle = _wrapper->HolderHandle; - if (refCountedHandle != IntPtr.Zero && RuntimeImports.RhHandleGet(refCountedHandle) != null) - { - // The ManagedObjectWrapperHolder has not been fully collected, so it is still - // potentially reachable via the Conditional Weak Table. - // Keep ourselves alive in case the wrapped object is resurrected. - GC.ReRegisterForFinalize(this); - return; - } - - // Release GC handle created when MOW was built. - if (_wrapper->Destroy()) - { - NativeMemory.AlignedFree(_wrapper); - _wrapper = null; - } - else - { - // There are still outstanding references on the COM side. - // This case should only be hit when an outstanding - // tracker refcount exists from AddRefFromReferenceTracker. - GC.ReRegisterForFinalize(this); - } - } - } - - internal unsafe class NativeObjectWrapper - { - private IntPtr _externalComObject; - private IntPtr _inner; - private ComWrappers _comWrappers; - private GCHandle _proxyHandle; - private GCHandle _proxyHandleTrackingResurrection; - private readonly bool _aggregatedManagedObjectWrapper; - private readonly bool _uniqueInstance; - - 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, - ref IntPtr referenceTrackerMaybe) - { - if (flags.HasFlag(CreateObjectFlags.TrackerObject)) - { - IntPtr trackerObject = referenceTrackerMaybe; - - // We're taking ownership of this reference tracker object, so reset the reference - referenceTrackerMaybe = IntPtr.Zero; - - // If we already have a reference tracker (that will be the case in aggregation scenarios), then reuse it. - // Otherwise, do the 'QueryInterface' call for it here. This allows us to only ever query for this IID once. - if (trackerObject != IntPtr.Zero || - Marshal.QueryInterface(externalComObject, IID_IReferenceTracker, out trackerObject) == HResults.S_OK) - { - return new ReferenceTrackerNativeObjectWrapper(externalComObject, inner, comWrappers, comProxy, flags, trackerObject); - } - } - - return new NativeObjectWrapper(externalComObject, inner, comWrappers, comProxy, flags); - } - - protected NativeObjectWrapper(IntPtr externalComObject, IntPtr inner, ComWrappers comWrappers, object comProxy, CreateObjectFlags flags) - { - _externalComObject = externalComObject; - _inner = inner; - _comWrappers = comWrappers; - _uniqueInstance = flags.HasFlag(CreateObjectFlags.UniqueInstance); - _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 reference tracker runtime - // 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); - } - } - - internal IntPtr ExternalComObject => _externalComObject; - internal ComWrappers ComWrappers => _comWrappers; - internal GCHandle ProxyHandle => _proxyHandle; - internal bool IsUniqueInstance => _uniqueInstance; - internal bool IsAggregatedWithManagedObjectWrapper => _aggregatedManagedObjectWrapper; - - public virtual void Release() - { - if (!_uniqueInstance && _comWrappers is not null) - { - _comWrappers._rcwCache.Remove(_externalComObject, this); - _comWrappers = null; - } - - if (_proxyHandle.IsAllocated) - { - _proxyHandle.Free(); - } - - if (_proxyHandleTrackingResurrection.IsAllocated) - { - _proxyHandleTrackingResurrection.Free(); - } - - // If the inner was supplied, we need to release our reference. - if (_inner != 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. - /// - private static ComWrappers? s_globalInstanceForTrackerSupport; - - /// - /// Globally registered instance of the ComWrappers class for marshalling. - /// - 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; - } - } - - // Custom type instead of a value tuple to avoid rooting 'ITuple' and other value tuple stuff - private struct GetOrCreateComInterfaceForObjectParameters - { - public ComWrappers? This; - public CreateComInterfaceFlags Flags; - } - - /// - /// Create a COM representation of the supplied object that can be passed to a non-managed environment. - /// - /// The managed object to expose outside the .NET runtime. - /// Flags used to configure the generated interface. - /// The generated COM interface that can be passed outside the .NET runtime. - /// - /// If a COM representation was previously created for the specified using - /// this instance, the previously created COM interface will be returned. - /// If not, a new one will be created. - /// - public unsafe IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags) - { - ArgumentNullException.ThrowIfNull(instance); - - ManagedObjectWrapperHolder managedObjectWrapper = _managedObjectWrapperTable.GetOrAdd(instance, static (c, items) => - { - ManagedObjectWrapper* value = items.This!.CreateManagedObjectWrapper(c, items.Flags); - return new ManagedObjectWrapperHolder(value, c); - }, new GetOrCreateComInterfaceForObjectParameters { This = this, Flags = flags }); - - managedObjectWrapper.AddRef(); - return managedObjectWrapper.ComIp; - } - - private static nuint AlignUp(nuint value, nuint alignment) - { - nuint alignMask = alignment - 1; - return (nuint)((value + alignMask) & ~alignMask); - } - - private unsafe ManagedObjectWrapper* CreateManagedObjectWrapper(object instance, CreateComInterfaceFlags flags) - { - ComInterfaceEntry* userDefined = ComputeVtables(instance, flags, out int userDefinedCount); - if ((userDefined == null && userDefinedCount != 0) || userDefinedCount < 0) - { - throw new ArgumentException(); - } - - // Maximum number of runtime supplied vtables. - Span runtimeDefinedVtable = stackalloc IntPtr[3]; - int runtimeDefinedCount = 0; - - // Check if the caller will provide the IUnknown table. - if ((flags & CreateComInterfaceFlags.CallerDefinedIUnknown) == CreateComInterfaceFlags.None) - { - runtimeDefinedVtable[runtimeDefinedCount++] = DefaultIUnknownVftblPtr; - } - - if ((flags & CreateComInterfaceFlags.TrackerSupport) != 0) - { - runtimeDefinedVtable[runtimeDefinedCount++] = DefaultIReferenceTrackerTargetVftblPtr; - } - - { - runtimeDefinedVtable[runtimeDefinedCount++] = TaggedImplVftblPtr; - } - - // Compute size for ManagedObjectWrapper instance. - int totalDefinedCount = runtimeDefinedCount + userDefinedCount; - - int numSections = totalDefinedCount / InternalComInterfaceDispatch.NumEntriesInDispatchTable; - if (totalDefinedCount % InternalComInterfaceDispatch.NumEntriesInDispatchTable != 0) - { - // Account for a trailing partial section to fit all of the defined interfaces. - numSections++; - } - - nuint headerSize = AlignUp((nuint)sizeof(ManagedObjectWrapper), InternalComInterfaceDispatch.DispatchAlignment); - - // Instead of allocating a full section even when we have a trailing one, we'll allocate only - // as much space as we need to store all of our dispatch tables. - nuint dispatchSectionSize = (nuint)totalDefinedCount * (nuint)sizeof(void*) + (nuint)numSections * (nuint)sizeof(void*); - - // Allocate memory for the ManagedObjectWrapper with the correct alignment for our dispatch tables. - IntPtr wrapperMem = (IntPtr)NativeMemory.AlignedAlloc( - headerSize + dispatchSectionSize, - InternalComInterfaceDispatch.DispatchAlignment); - - // Dispatches follow the ManagedObjectWrapper. - InternalComInterfaceDispatch* pDispatches = (InternalComInterfaceDispatch*)((nuint)wrapperMem + headerSize); - Span dispatches = new Span(pDispatches, numSections); - for (int i = 0; i < dispatches.Length; i++) - { - dispatches[i]._thisPtr = (ManagedObjectWrapper*)wrapperMem; - Span dispatchVtables = dispatches[i].Vtables; - for (int j = 0; j < dispatchVtables.Length; j++) - { - int index = i * dispatchVtables.Length + j; - if (index >= totalDefinedCount) - { - break; - } - dispatchVtables[j] = (index < userDefinedCount) ? userDefined[index].Vtable : runtimeDefinedVtable[index - userDefinedCount]; - } - } - - ManagedObjectWrapper* mow = (ManagedObjectWrapper*)wrapperMem; - mow->HolderHandle = IntPtr.Zero; - mow->RefCount = 0; - mow->UserDefinedCount = userDefinedCount; - mow->UserDefined = userDefined; - mow->Flags = (CreateComInterfaceFlagsEx)flags; - mow->Dispatches = pDispatches; - return mow; - } - - /// - /// Get the currently registered managed object or creates a new managed object and registers it. - /// - /// Object to import for usage into the .NET runtime. - /// Flags used to describe the external object. - /// Returns a managed object associated with the supplied external COM object. - /// - /// If a managed object was previously created for the specified - /// using this instance, the previously created object will be returned. - /// If not, a new one will be created. - /// - public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags) - { - object? obj; - if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, IntPtr.Zero, flags, null, out obj)) - throw new ArgumentNullException(nameof(externalComObject)); - - return obj; - } - - /// - /// Get the currently registered managed object or uses the supplied managed object and registers it. - /// - /// Object to import for usage into the .NET runtime. - /// Flags used to describe the external object. - /// The to be used as the wrapper for the external object - /// Returns a managed object associated with the supplied external COM object. - /// - /// If the instance already has an associated external object a will be thrown. - /// - public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper) - { - return GetOrRegisterObjectForComInstance(externalComObject, flags, wrapper, IntPtr.Zero); - } - - /// - /// Get the currently registered managed object or uses the supplied managed object and registers it. - /// - /// Object to import for usage into the .NET runtime. - /// Flags used to describe the external object. - /// The to be used as the wrapper for the external object - /// Inner for COM aggregation scenarios - /// Returns a managed object associated with the supplied external COM object. - /// - /// This method override is for registering an aggregated COM instance with its associated inner. The inner - /// will be released when the associated wrapper is eventually freed. Note that it will be released on a thread - /// in an unknown apartment state. If the supplied inner is not known to be a free-threaded instance then - /// it is advised to not supply the inner. - /// - /// If the instance already has an associated external object a will be thrown. - /// - public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper, IntPtr inner) - { - ArgumentNullException.ThrowIfNull(wrapper); - - object? obj; - if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, inner, flags, wrapper, out obj)) - throw new ArgumentNullException(nameof(externalComObject)); - - return obj; - } - - private static unsafe ComInterfaceDispatch* TryGetComInterfaceDispatch(IntPtr comObject) - { - // If the first Vtable entry is part of a ManagedObjectWrapper impl, - // we know how to interpret the IUnknown. - IntPtr knownQI = ((IntPtr*)((IntPtr*)comObject)[0])[0]; - if (knownQI != ((IntPtr*)DefaultIUnknownVftblPtr)[0] - || knownQI != ((IntPtr*)DefaultIReferenceTrackerTargetVftblPtr)[0]) - { - // It is possible the user has defined their own IUnknown impl so - // we fallback to the tagged interface approach to be sure. - if (0 != Marshal.QueryInterface(comObject, IID_TaggedImpl, out nint implMaybe)) - { - return null; - } - - IntPtr currentVersion = (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion; - int hr = ((delegate* unmanaged)(*(*(void***)implMaybe + 3 /* ITaggedImpl.IsCurrentVersion slot */)))(implMaybe, currentVersion); - Marshal.Release(implMaybe); - if (hr != 0) - { - return null; - } - } - - return (ComInterfaceDispatch*)comObject; - } - - private static void DetermineIdentityAndInner( - IntPtr externalComObject, - IntPtr innerMaybe, - CreateObjectFlags flags, - out IntPtr identity, - out IntPtr inner, - out IntPtr referenceTrackerMaybe) - { - 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, 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. This allows us to keep the reference tracker - // reference alive, so we can reuse it later. - checkForIdentity = referenceTrackerPtr; - referenceTrackerMaybe = referenceTrackerPtr; - Marshal.ThrowExceptionForHR(Marshal.QueryInterface(checkForIdentity, IID_IUnknown, out identity)); - } - else - { - referenceTrackerMaybe = IntPtr.Zero; - Marshal.ThrowExceptionForHR(Marshal.QueryInterface(externalComObject, 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; - } - } - - /// - /// Get the currently registered managed object or creates a new managed object and registers it. - /// - /// Object to import for usage into the .NET runtime. - /// The inner instance if aggregation is involved - /// Flags used to describe the external object. - /// The to be used as the wrapper for the external object. - /// The managed object associated with the supplied external COM object or null if it could not be created. - /// Returns true if a managed object could be retrieved/created, false otherwise - private unsafe bool TryGetOrCreateObjectForComInstanceInternal( - IntPtr externalComObject, - IntPtr innerMaybe, - CreateObjectFlags flags, - object? wrapperMaybe, - [NotNullWhen(true)] out object? retValue) - { - if (externalComObject == IntPtr.Zero) - throw new ArgumentNullException(nameof(externalComObject)); - - if (innerMaybe != IntPtr.Zero && !flags.HasFlag(CreateObjectFlags.Aggregation)) - throw new InvalidOperationException(SR.InvalidOperation_SuppliedInnerMustBeMarkedAggregation); - - DetermineIdentityAndInner( - externalComObject, - innerMaybe, - flags, - out IntPtr identity, - out IntPtr inner, - out IntPtr referenceTrackerMaybe); - - try - { - // If the user has requested a unique instance, - // we will immediately create the object, register it, - // and return. - if (flags.HasFlag(CreateObjectFlags.UniqueInstance)) - { - retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, ref referenceTrackerMaybe); - return retValue is not null; - } - - // If we have a live cached wrapper currently, - // return that. - if (_rcwCache.FindProxyForComInstance(identity) is object liveCachedWrapper) - { - retValue = liveCachedWrapper; - return true; - } - - // If the user tried to provide a pre-created managed wrapper, try to register - // that object as the wrapper. - if (wrapperMaybe is not null) - { - retValue = RegisterObjectForComInstance(identity, inner, wrapperMaybe, flags, ref referenceTrackerMaybe); - return retValue is not null; - } - - // Check if the provided COM instance is actually a managed object wrapper from this - // ComWrappers instance, and use it if it is. - if (flags.HasFlag(CreateObjectFlags.Unwrap)) - { - ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(identity); - if (comInterfaceDispatch != null) - { - // If we found a managed object wrapper in this ComWrappers instance - // and it has the same identity pointer as the one we're creating a NativeObjectWrapper for, - // unwrap it. We don't AddRef the wrapper as we don't take a reference to it. - // - // A managed object can have multiple managed object wrappers, with a max of one per context. - // Let's say we have a managed object A and ComWrappers instances C1 and C2. Let B1 and B2 be the - // managed object wrappers for A created with C1 and C2 respectively. - // If we are asked to create an EOC for B1 with the unwrap flag on the C2 ComWrappers instance, - // we will create a new wrapper. In this scenario, we'll only unwrap B2. - object unwrapped = ComInterfaceDispatch.GetInstance(comInterfaceDispatch); - if (_managedObjectWrapperTable.TryGetValue(unwrapped, out ManagedObjectWrapperHolder? unwrappedWrapperInThisContext)) - { - // 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. - if (unwrappedWrapperInThisContext.ComIp == identity) - { - retValue = unwrapped; - return true; - } - } - } - } - - // If the user didn't provide a wrapper and couldn't unwrap a managed object wrapper, - // create a new wrapper. - retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, ref referenceTrackerMaybe); - return retValue is not null; - } - finally - { - // Releasing a native object can never throw (it's a native call, so exceptions can't - // go through the ABI, it'd just crash the whole process). So we can use a single - // 'finally' block to release both native pointers we're holding in this scope. - Marshal.Release(identity); - - if (referenceTrackerMaybe != IntPtr.Zero) - { - Marshal.Release(referenceTrackerMaybe); - } - } - } - - private object? CreateAndRegisterObjectForComInstance( - IntPtr identity, - IntPtr inner, - CreateObjectFlags flags, - ref IntPtr referenceTrackerMaybe) - { - object? retValue = CreateObject(identity, flags); - if (retValue is null) - { - // If ComWrappers instance cannot create wrapper, we can do nothing here. - return null; - } - - return RegisterObjectForComInstance(identity, inner, retValue, flags, ref referenceTrackerMaybe); - } - - private object RegisterObjectForComInstance( - IntPtr identity, - IntPtr inner, - object comProxy, - CreateObjectFlags flags, - ref IntPtr referenceTrackerMaybe) - { - NativeObjectWrapper nativeObjectWrapper = NativeObjectWrapper.Create( - identity, - inner, - this, - comProxy, - flags, - ref referenceTrackerMaybe); - - object actualProxy = comProxy; - NativeObjectWrapper actualWrapper = nativeObjectWrapper; - if (!nativeObjectWrapper.IsUniqueInstance) - { - // Add our entry to the cache here, using an already existing entry if someone else beat us to it. - (actualWrapper, actualProxy) = _rcwCache.GetOrAddProxyForComInstance(identity, nativeObjectWrapper, comProxy); - if (actualWrapper != nativeObjectWrapper) - { - // We raced with another thread to map identity to nativeObjectWrapper - // and lost the race. We will use the other thread's nativeObjectWrapper, so we can release ours. - nativeObjectWrapper.Release(); - } - } - - // At this point, actualProxy is the RCW object for the identity - // and actualWrapper is the NativeObjectWrapper that is in the RCW cache (if not unique) that associates the identity with actualProxy. - // Register the NativeObjectWrapper to handle lifetime tracking of the references to the COM object. - RegisterWrapperForObject(actualWrapper, actualProxy); - - return actualProxy; - } - - private void RegisterWrapperForObject(NativeObjectWrapper wrapper, object comProxy) - { - // When we call into RegisterWrapperForObject, there is only one valid non-"unique instance" wrapper for a given - // COM instance, which is already registered in the RCW cache. - // If we find a wrapper in the table that is a different NativeObjectWrapper instance - // then it must be for a different COM instance. - // It's possible that we could race here with another thread that is trying to register the same comProxy - // for the same COM instance, but in that case we'll be passed the same NativeObjectWrapper instance - // for both threads. In that case, it doesn't matter which thread adds the entry to the NativeObjectWrapper table - // as the entry is always the same pair. - Debug.Assert(wrapper.ProxyHandle.Target == comProxy); - Debug.Assert(wrapper.IsUniqueInstance || _rcwCache.FindProxyForComInstance(wrapper.ExternalComObject) == comProxy); - - // Add the input wrapper bound to the COM proxy, if there isn't one already. If another thread raced - // against this one and this lost, we'd get the wrapper added from that thread instead. - NativeObjectWrapper registeredWrapper = s_nativeObjectWrapperTable.GetOrAdd(comProxy, wrapper); - - // We lost the race, so we cannot register the incoming wrapper with the target object - if (registeredWrapper != wrapper) - { - Debug.Assert(registeredWrapper.ExternalComObject != wrapper.ExternalComObject); - wrapper.Release(); - throw new NotSupportedException(); - } - - // Always register our wrapper to the reference tracker handle cache here. - // We may not be the thread that registered the handle, but we need to ensure that the wrapper - // is registered before we return to user code. Otherwise the wrapper won't be walked by the - // TrackerObjectManager and we could end up missing a section of the object graph. - // This cache deduplicates, so it is okay that the wrapper will be registered multiple times. - AddWrapperToReferenceTrackerHandleCache(registeredWrapper); - } - - private static void AddWrapperToReferenceTrackerHandleCache(NativeObjectWrapper wrapper) - { - if (wrapper is ReferenceTrackerNativeObjectWrapper referenceTrackerNativeObjectWrapper) - { - s_referenceTrackerNativeObjectWrapperCache.Add(referenceTrackerNativeObjectWrapper._nativeObjectWrapperWeakHandle); - } - } - - private sealed class RcwCache - { - private readonly Lock _lock = new Lock(useTrivialWaits: true); - private readonly Dictionary _cache = []; - - /// - /// Gets the current RCW proxy object for if it exists in the cache or inserts a new entry with . - /// - /// The com instance we want to get or record an RCW for. - /// The for . - /// The proxy object that is associated with . - /// The proxy object currently in the cache for or the proxy object owned by if no entry exists and the corresponding native wrapper. - public (NativeObjectWrapper actualWrapper, object actualProxy) GetOrAddProxyForComInstance(IntPtr comPointer, NativeObjectWrapper wrapper, object comProxy) - { - lock (_lock) - { - Debug.Assert(wrapper.ProxyHandle.Target == comProxy); - ref GCHandle rcwEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(_cache, comPointer, out bool exists); - if (!exists) - { - // Someone else didn't beat us to adding the entry to the cache. - // Add our entry here. - rcwEntry = GCHandle.Alloc(wrapper, GCHandleType.Weak); - } - else if (rcwEntry.Target is not (NativeObjectWrapper cachedWrapper)) - { - Debug.Assert(rcwEntry.IsAllocated); - // The target was collected, so we need to update the cache entry. - rcwEntry.Target = wrapper; - } - else - { - object? existingProxy = cachedWrapper.ProxyHandle.Target; - // The target NativeObjectWrapper was not collected, but we need to make sure - // that the proxy object is still alive. - if (existingProxy is not null) - { - // The existing proxy object is still alive, we will use that. - return (cachedWrapper, existingProxy); - } - - // The proxy object was collected, so we need to update the cache entry. - rcwEntry.Target = wrapper; - } - - // We either added an entry to the cache or updated an existing entry that was dead. - // Return our target object. - return (wrapper, comProxy); - } - } - - public object? FindProxyForComInstance(IntPtr comPointer) - { - lock (_lock) - { - if (_cache.TryGetValue(comPointer, out GCHandle existingHandle)) - { - if (existingHandle.Target is NativeObjectWrapper { ProxyHandle.Target: object cachedProxy }) - { - // The target exists and is still alive. Return it. - return cachedProxy; - } - - // The target was collected, so we need to remove the entry from the cache. - _cache.Remove(comPointer); - existingHandle.Free(); - } - - return null; - } - } - - public void Remove(IntPtr comPointer, NativeObjectWrapper wrapper) - { - lock (_lock) - { - // 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 finalizer ran. - // Only remove the entry if the target of the GC handle is the NativeObjectWrapper - // or is null (indicating that the corresponding NativeObjectWrapper has been scheduled for finalization). - if (_cache.TryGetValue(comPointer, out GCHandle cachedRef) - && (wrapper == cachedRef.Target - || cachedRef.Target is null)) - { - _cache.Remove(comPointer); - cachedRef.Free(); - } - } - } - } - - /// - /// Register a instance to be used as the global instance for reference tracker support. - /// - /// Instance to register - /// - /// This function can only be called a single time. Subsequent calls to this function will result - /// in a being thrown. - /// - /// Scenarios where this global instance may be used are: - /// * Object tracking via the and flags. - /// - public static void RegisterForTrackerSupport(ComWrappers instance) - { - ArgumentNullException.ThrowIfNull(instance); - - if (null != Interlocked.CompareExchange(ref s_globalInstanceForTrackerSupport, instance, null)) - { - throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); - } - } - - /// - /// Register a instance to be used as the global instance for marshalling in the runtime. - /// - /// Instance to register - /// - /// This function can only be called a single time. Subsequent calls to this function will result - /// in a being thrown. - /// - /// Scenarios where this global instance may be used are: - /// * Usage of COM-related Marshal APIs - /// * P/Invokes with COM-related types - /// * COM activation - /// - [SupportedOSPlatformAttribute("windows")] - public static void RegisterForMarshalling(ComWrappers instance) - { - ArgumentNullException.ThrowIfNull(instance); - - if (null != Interlocked.CompareExchange(ref s_globalInstanceForMarshalling, instance, null)) - { - throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); - } - } - - /// - /// Get the runtime provided IUnknown implementation. - /// - /// Function pointer to QueryInterface. - /// Function pointer to AddRef. - /// Function pointer to Release. - public static unsafe void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) - { - fpQueryInterface = (IntPtr)(delegate* unmanaged)&ComWrappers.IUnknown_QueryInterface; - fpAddRef = (IntPtr)(delegate*)&RuntimeImports.RhIUnknown_AddRef; // Implemented in C/C++ to avoid GC transitions - fpRelease = (IntPtr)(delegate* unmanaged)&ComWrappers.IUnknown_Release; - } - - internal static IntPtr ComInterfaceForObject(object instance) - { - if (s_globalInstanceForMarshalling == null) - { - throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperInstance); - } - - return s_globalInstanceForMarshalling.GetOrCreateComInterfaceForObject(instance, CreateComInterfaceFlags.None); - } - - internal static unsafe IntPtr ComInterfaceForObject(object instance, Guid targetIID) - { - IntPtr unknownPtr = ComInterfaceForObject(instance); - IntPtr comObjectInterface; - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)unknownPtr); - int resultCode = wrapper->QueryInterface(in targetIID, out comObjectInterface); - // We no longer need IUnknownPtr, release reference - Marshal.Release(unknownPtr); - if (resultCode != 0) - { - throw new InvalidCastException(); - } - - return comObjectInterface; - } - - internal static object ComObjectForInterface(IntPtr externalComObject) - { - if (s_globalInstanceForMarshalling == null) - { - throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperInstance); - } - - // 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() + internal sealed unsafe partial class ManagedObjectWrapperHolder { - if (s_globalInstanceForTrackerSupport == null) - { - throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperTrackerInstance); - } - - IntPtr contextToken = GetContextToken(); - - List objects = new List(); - - // Here we aren't part of a GC callback, so other threads can still be running - // who are adding and removing from the collection. This means we can possibly race - // with a handle being removed and freed and we can end up accessing a freed handle. - // To avoid this, we take a lock on modifications to the collection while we gather - // the objects. - using (s_referenceTrackerNativeObjectWrapperCache.ModificationLock.EnterScope()) + private static void RegisterIsRootedCallback() { - foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) + delegate* unmanaged callback = &IsRootedCallback; + if (!RuntimeImports.RhRegisterRefCountedHandleCallback((nint)callback, MethodTable.Of())) { - ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); - if (nativeObjectWrapper != null && - nativeObjectWrapper._contextToken == contextToken) - { - object? target = nativeObjectWrapper.ProxyHandle.Target; - if (target != null) - { - objects.Add(target); - } - - // Separate the wrapper from the tracker runtime prior to - // passing them. - nativeObjectWrapper.DisconnectTracker(); - } + throw new OutOfMemoryException(); } } - s_globalInstanceForTrackerSupport.ReleaseObjects(objects); - } - - // Used during GC callback - internal static unsafe void WalkExternalTrackerObjects() - { - bool walkFailed = false; - - foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) + [UnmanagedCallersOnly] + private static bool IsRootedCallback(IntPtr pObj) { - 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; - } + // We are paused in the GC, so this is safe. + ManagedObjectWrapperHolder* holder = (ManagedObjectWrapperHolder*)&pObj; + return holder->_wrapper->IsRooted; } - // Report whether walking failed or not. - if (walkFailed) + private static IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) { - TrackerObjectManager.s_isGlobalPeggingOn = true; + return RuntimeImports.RhHandleAllocRefCounted(holder); } - IReferenceTrackerManager.FindTrackerTargetsCompleted(TrackerObjectManager.s_trackerManager, walkFailed); } - // Used during GC callback - internal static void DetachNonPromotedObjects() + /// + /// Get the runtime provided IUnknown implementation. + /// + /// Function pointer to QueryInterface. + /// Function pointer to AddRef. + /// Function pointer to Release. + public static unsafe void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) { - 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); - } - } + fpQueryInterface = (IntPtr)(delegate* unmanaged)&ComWrappers.IUnknown_QueryInterface; + fpAddRef = (IntPtr)(delegate*)&RuntimeImports.RhIUnknown_AddRef; // Implemented in C/C++ to avoid GC transitions + fpRelease = (IntPtr)(delegate* unmanaged)&ComWrappers.IUnknown_Release; } [UnmanagedCallersOnly] @@ -1490,530 +71,129 @@ internal static unsafe uint IUnknown_Release(IntPtr pThis) return refcount; } - [UnmanagedCallersOnly] - internal static unsafe int IReferenceTrackerTarget_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) - { - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); - return wrapper->QueryInterfaceForTracker(in *guid, out *ppObject); - } - - [UnmanagedCallersOnly] - internal static unsafe uint IReferenceTrackerTarget_AddRefFromReferenceTracker(IntPtr pThis) - { - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); - return wrapper->AddRefFromReferenceTracker(); - } - - [UnmanagedCallersOnly] - internal static unsafe uint IReferenceTrackerTarget_ReleaseFromReferenceTracker(IntPtr pThis) - { - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); - return wrapper->ReleaseFromReferenceTracker(); - } - - [UnmanagedCallersOnly] - internal static unsafe uint IReferenceTrackerTarget_Peg(IntPtr pThis) - { - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); - return wrapper->Peg(); - } - - [UnmanagedCallersOnly] - internal static unsafe uint IReferenceTrackerTarget_Unpeg(IntPtr pThis) - { - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); - return wrapper->Unpeg(); - } - - [UnmanagedCallersOnly] - internal static unsafe int ITaggedImpl_IsCurrentVersion(IntPtr pThis, IntPtr version) - { - return version == (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion - ? HResults.S_OK - : HResults.E_FAIL; - } - - private static unsafe IntPtr CreateDefaultIUnknownVftbl() - { - IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ComWrappers), 3 * sizeof(IntPtr)); - GetIUnknownImpl(out vftbl[0], out vftbl[1], out vftbl[2]); - return (IntPtr)vftbl; - } - - // This IID represents an internal interface we define to tag any ManagedObjectWrappers we create. - // This interface type and GUID do not correspond to any public interface; it is an internal implementation detail. - private static unsafe IntPtr CreateTaggedImplVftbl() - { - IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ComWrappers), 4 * sizeof(IntPtr)); - GetIUnknownImpl(out vftbl[0], out vftbl[1], out vftbl[2]); - vftbl[3] = (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion; - return (IntPtr)vftbl; - } - - private static unsafe IntPtr CreateDefaultIReferenceTrackerTargetVftbl() - { - IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ComWrappers), 7 * sizeof(IntPtr)); - vftbl[0] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerTarget_QueryInterface; - GetIUnknownImpl(out _, out vftbl[1], out vftbl[2]); - vftbl[3] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerTarget_AddRefFromReferenceTracker; - vftbl[4] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerTarget_ReleaseFromReferenceTracker; - vftbl[5] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerTarget_Peg; - 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) - { - // We'd like to call GC.WaitForPendingFinalizers() here, but this could lead to deadlock - // if the finalizer thread is trying to get back to this thread, because we are not pumping - // anymore. Disable this for now. See: https://github.com/dotnet/runtime/issues/109538. - return HResults.S_OK; - } - - [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, 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, 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 + private static IntPtr GetTaggedImplCurrentVersion() { - public static int Resolve(IntPtr pThis, Guid guid, out IntPtr inspectable) + unsafe { - fixed (IntPtr* inspectablePtr = &inspectable) - return (*(delegate* unmanaged**)pThis)[3](pThis, &guid, inspectablePtr); + return (IntPtr)(delegate* unmanaged)&VtableImplementations.ITaggedImpl_IsCurrentVersion; } } - // 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); - } - } + internal static unsafe IntPtr DefaultIUnknownVftblPtr => (IntPtr)Unsafe.AsPointer(in VtableImplementations.IUnknown); + internal static unsafe IntPtr TaggedImplVftblPtr => (IntPtr)Unsafe.AsPointer(in VtableImplementations.ITaggedImpl); + internal static unsafe IntPtr DefaultIReferenceTrackerTargetVftblPtr => (IntPtr)Unsafe.AsPointer(in VtableImplementations.IReferenceTrackerTarget); - private static object? ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId) + /// + /// Define the vtable layout for the COM interfaces we provide. + /// + /// + /// This is defined as a nested class to ensure that the vtable types are the only things initialized in the class's static constructor. + /// As long as that's the case, we can easily guarantee that they are pre-initialized and that we don't end up having startup code + /// needed to set up the vtable layouts. + /// + private static class VtableImplementations { - if (wrapperId == 0) + public unsafe struct IUnknownVftbl { - 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, IID_IUnknown, out IntPtr targetIdentityPtr) == HResults.S_OK) - { - using ComHolder targetIdentity = new ComHolder(targetIdentityPtr); - return GetOrCreateObjectFromWrapper(wrapperId, targetIdentity.Ptr); - } + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; } - 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_nativeObjectWrapperTable.TryGetValue(target, out NativeObjectWrapper? wrapper) && !wrapper.IsAggregatedWithManagedObjectWrapper; - } - - private static unsafe IntPtr ObjectToComWeakRef(object target, out long wrapperId) - { - if (TryGetComInstanceForIID( - target, - IID_IWeakReferenceSource, - out IntPtr weakReferenceSourcePtr, - out wrapperId)) + public unsafe struct IReferenceTrackerTargetVftbl { - using ComHolder weakReferenceSource = new ComHolder(weakReferenceSourcePtr); - if (IWeakReferenceSource.GetWeakReference(weakReferenceSource.Ptr, out IntPtr weakReference) == HResults.S_OK) - { - return weakReference; - } + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; + public delegate* unmanaged AddRefFromReferenceTracker; + public delegate* unmanaged ReleaseFromReferenceTracker; + public delegate* unmanaged Peg; + public delegate* unmanaged Unpeg; } - return IntPtr.Zero; - } - } - - // This is a GCHandle HashSet implementation based on LowLevelDictionary. - // It uses no locking for readers. While for writers (add / remove), - // it handles the locking itself. - // This implementation specifically makes sure that any readers of this - // collection during GC aren't impacted by other threads being - // frozen while in the middle of an write. It makes no guarantees on - // whether you will observe the element being added / removed, but does - // make sure the collection is in a good state and doesn't run into issues - // while iterating. - internal sealed class GCHandleSet : IEnumerable - { - private const int DefaultSize = 7; - - private Entry?[] _buckets = new Entry[DefaultSize]; - private int _numEntries; - private readonly Lock _lock = new Lock(useTrivialWaits: true); - - public Lock ModificationLock => _lock; - - public void Add(GCHandle handle) - { - using (_lock.EnterScope()) + public unsafe struct ITaggedImplVftbl { - int bucket = GetBucket(handle, _buckets.Length); - Entry? prev = null; - Entry? entry = _buckets[bucket]; - while (entry != null) - { - // Handle already exists, nothing to add. - if (handle.Equals(entry.m_value)) - { - return; - } - - prev = entry; - entry = entry.m_next; - } - - Entry newEntry = new Entry() - { - m_value = handle - }; - - if (prev == null) - { - _buckets[bucket] = newEntry; - } - else - { - prev.m_next = newEntry; - } - - // _numEntries is only maintained for the purposes of deciding whether to - // expand the bucket and is not used during iteration to handle the - // scenario where element is in bucket but _numEntries hasn't been incremented - // yet. - _numEntries++; - if (_numEntries > (_buckets.Length * 2)) - { - ExpandBuckets(); - } + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; + public delegate* unmanaged IsCurrentVersion; } - } - - private void ExpandBuckets() - { - int newNumBuckets = _buckets.Length * 2 + 1; - Entry?[] newBuckets = new Entry[newNumBuckets]; - for (int i = 0; i < _buckets.Length; i++) - { - Entry? entry = _buckets[i]; - while (entry != null) - { - Entry? nextEntry = entry.m_next; - int bucket = GetBucket(entry.m_value, newNumBuckets); + [FixedAddressValueType] + public static readonly IUnknownVftbl IUnknown; - // We are allocating new entries for the bucket to ensure that - // if there is an enumeration already in progress, we don't - // modify what it observes by changing next in existing instances. - Entry newEntry = new Entry() - { - m_value = entry.m_value, - m_next = newBuckets[bucket], - }; - newBuckets[bucket] = newEntry; + [FixedAddressValueType] + public static readonly IReferenceTrackerTargetVftbl IReferenceTrackerTarget; - entry = nextEntry; - } - } - _buckets = newBuckets; - } + [FixedAddressValueType] + public static readonly ITaggedImplVftbl ITaggedImpl; - public void Remove(GCHandle handle) - { - using (_lock.EnterScope()) + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerTarget_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) { - int bucket = GetBucket(handle, _buckets.Length); - Entry? prev = null; - Entry? entry = _buckets[bucket]; - while (entry != null) - { - if (handle.Equals(entry.m_value)) - { - if (prev == null) - { - _buckets[bucket] = entry.m_next; - } - else - { - prev.m_next = entry.m_next; - } - _numEntries--; - return; - } - - prev = entry; - entry = entry.m_next; - } + ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); + return wrapper->QueryInterfaceForTracker(in *guid, out *ppObject); } - } - - private static int GetBucket(GCHandle handle, int numBuckets) - { - int h = handle.GetHashCode(); - return (int)((uint)h % (uint)numBuckets); - } - - public Enumerator GetEnumerator() => new Enumerator(this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); - - private sealed class Entry - { - public GCHandle m_value; - public Entry? m_next; - } - - public struct Enumerator : IEnumerator - { - private readonly Entry?[] _buckets; - private int _currentIdx; - private Entry? _currentEntry; - - public Enumerator(GCHandleSet set) + [UnmanagedCallersOnly] + internal static unsafe uint IReferenceTrackerTarget_AddRefFromReferenceTracker(IntPtr pThis) { - // We hold onto the buckets of the set rather than the set itself - // so that if it is ever expanded, we are not impacted by that during - // enumeration. - _buckets = set._buckets; - Reset(); + ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); + return wrapper->AddRefFromReferenceTracker(); } - public GCHandle Current + [UnmanagedCallersOnly] + internal static unsafe uint IReferenceTrackerTarget_ReleaseFromReferenceTracker(IntPtr pThis) { - get - { - if (_currentEntry == null) - { - throw new InvalidOperationException("InvalidOperation_EnumOpCantHappen"); - } - - return _currentEntry.m_value; - } + ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); + return wrapper->ReleaseFromReferenceTracker(); } - object IEnumerator.Current => Current; - - public void Dispose() + [UnmanagedCallersOnly] + internal static unsafe uint IReferenceTrackerTarget_Peg(IntPtr pThis) { + ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); + return wrapper->Peg(); } - public bool MoveNext() + [UnmanagedCallersOnly] + internal static unsafe uint IReferenceTrackerTarget_Unpeg(IntPtr pThis) { - if (_currentEntry != null) - { - _currentEntry = _currentEntry.m_next; - } - - if (_currentEntry == null) - { - // Certain buckets might be empty, so loop until we find - // one with an entry. - while (++_currentIdx != _buckets.Length) - { - _currentEntry = _buckets[_currentIdx]; - if (_currentEntry != null) - { - return true; - } - } - - return false; - } - - return true; + ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); + return wrapper->Unpeg(); } - public void Reset() - { - _currentIdx = -1; - _currentEntry = null; + [UnmanagedCallersOnly] + internal static unsafe int ITaggedImpl_IsCurrentVersion(IntPtr pThis, IntPtr version) + { + return version == (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion + ? HResults.S_OK + : HResults.E_FAIL; + } + + static unsafe VtableImplementations() + { + // Use the "pre-inited vtable" pattern to ensure that ILC can pre-compile these vtables. + GetIUnknownImpl( + fpQueryInterface: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IUnknown))->QueryInterface, + fpAddRef: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IUnknown))->AddRef, + fpRelease: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IUnknown))->Release); + + IReferenceTrackerTarget.QueryInterface = (delegate* unmanaged)&IReferenceTrackerTarget_QueryInterface; + GetIUnknownImpl( + fpQueryInterface: out _, + fpAddRef: out *(nint*)&((IReferenceTrackerTargetVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget))->AddRef, + fpRelease: out *(nint*)&((IReferenceTrackerTargetVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget))->Release); + IReferenceTrackerTarget.AddRefFromReferenceTracker = (delegate* unmanaged)&IReferenceTrackerTarget_AddRefFromReferenceTracker; + IReferenceTrackerTarget.ReleaseFromReferenceTracker = (delegate* unmanaged)&IReferenceTrackerTarget_ReleaseFromReferenceTracker; + IReferenceTrackerTarget.Peg = (delegate* unmanaged)&IReferenceTrackerTarget_Peg; + IReferenceTrackerTarget.Unpeg = (delegate* unmanaged)&IReferenceTrackerTarget_Unpeg; + + GetIUnknownImpl( + fpQueryInterface: out *(nint*)&((ITaggedImplVftbl*)Unsafe.AsPointer(ref ITaggedImpl))->QueryInterface, + fpAddRef: out *(nint*)&((ITaggedImplVftbl*)Unsafe.AsPointer(ref ITaggedImpl))->AddRef, + fpRelease: out *(nint*)&((ITaggedImplVftbl*)Unsafe.AsPointer(ref ITaggedImpl))->Release); + ITaggedImpl.IsCurrentVersion = (delegate* unmanaged)&ITaggedImpl_IsCurrentVersion; } } } 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 index 2e2674fb0e1f8b..f846ca65ce99c0 100644 --- 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 @@ -10,10 +10,10 @@ namespace System.Runtime.InteropServices { - internal static class TrackerObjectManager + internal static partial class TrackerObjectManager { - internal static readonly IntPtr s_findReferencesTargetCallback = FindReferenceTargetsCallback.CreateFindReferenceTargetsCallback(); - internal static readonly IntPtr s_globalHostServices = CreateHostServices(); + [FixedAddressValueType] + internal static readonly unsafe IntPtr s_findReferencesTargetCallback = (IntPtr)Unsafe.AsPointer(in FindReferenceTargetsCallback.Vftbl); internal static volatile IntPtr s_trackerManager; internal static volatile bool s_hasTrackingStarted; @@ -29,49 +29,6 @@ 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) @@ -134,24 +91,36 @@ public static void EndReferenceTracking() s_isGlobalPeggingOn = true; s_hasTrackingStarted = false; } - - public static unsafe void RegisterGCCallbacks() + public static bool AddReferencePath(object target, object foundReference) { - delegate* unmanaged gcStartCallback = &GCStartCollection; - delegate* unmanaged gcStopCallback = &GCStopCollection; - delegate* unmanaged gcAfterMarkCallback = &GCAfterMarkPhase; + return s_referenceCache.AddDependentHandle(target, foundReference); + } - 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(); - } + private static bool HasReferenceTrackerManager + => s_trackerManager != IntPtr.Zero; + + private static bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager) + { + return Interlocked.CompareExchange(ref s_trackerManager, referenceTrackerManager, IntPtr.Zero) == IntPtr.Zero; } - public static bool AddReferencePath(object target, object foundReference) + internal static bool IsGlobalPeggingEnabled => s_isGlobalPeggingOn; + + private static void RegisterGCCallbacks() { - return s_referenceCache.AddDependentHandle(target, foundReference); + unsafe + { + 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(); + } + } } // Used during GC callback @@ -183,80 +152,50 @@ 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) + internal static unsafe void WalkExternalTrackerObjects() { - return (*(delegate* unmanaged**)pThis)[5](pThis); - } - - public static void SetReferenceTrackerHost(IntPtr pThis, IntPtr referenceTrackerHost) - { - Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[6](pThis, referenceTrackerHost)); - } - } + bool walkFailed = false; - // Wrapper for IReferenceTracker - internal static unsafe class IReferenceTracker - { - public static void ConnectFromTrackerSource(IntPtr pThis) - { - Marshal.ThrowExceptionForHR((*(delegate* unmanaged**)pThis)[3](pThis)); - } + 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, (IntPtr)Unsafe.AsPointer(in s_findReferencesTargetCallback)) != HResults.S_OK) + { + walkFailed = true; + FindReferenceTargetsCallback.s_currentRootObjectHandle = default; + break; + } + FindReferenceTargetsCallback.s_currentRootObjectHandle = default; + } + } - // Used during GC callback - public static int DisconnectFromTrackerSource(IntPtr pThis) - { - return (*(delegate* unmanaged**)pThis)[4](pThis); + // Report whether walking failed or not. + if (walkFailed) + { + s_isGlobalPeggingOn = true; + } + IReferenceTrackerManager.FindTrackerTargetsCompleted(s_trackerManager, walkFailed); } // Used during GC callback - public static int FindTrackerTargets(IntPtr pThis, IntPtr findReferenceTargetsCallback) + internal static void DetachNonPromotedObjects() { - 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)); + 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. + BeforeWrapperFinalized(nativeObjectWrapper.TrackerObject); + } + } } } @@ -306,28 +245,26 @@ private static unsafe IntPtr CreateDefaultIFindReferenceTargetsCallbackVftbl() return (IntPtr)vftbl; } - internal static unsafe IntPtr CreateFindReferenceTargetsCallback() + internal struct ReferenceTargetsVftbl { - IntPtr* wrapperMem = (IntPtr*)NativeMemory.Alloc((nuint)sizeof(IntPtr)); - wrapperMem[0] = CreateDefaultIFindReferenceTargetsCallbackVftbl(); - return (IntPtr)wrapperMem; + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; + public delegate* unmanaged FoundTrackerTarget; } - } - internal readonly struct ComHolder : IDisposable - { - private readonly IntPtr _ptr; - - internal readonly IntPtr Ptr => _ptr; - - public ComHolder(IntPtr ptr) - { - _ptr = ptr; - } + [FixedAddressValueType] + internal static readonly ReferenceTargetsVftbl Vftbl; - public readonly void Dispose() +#pragma warning disable CA1810 // Initialize reference type static fields inline + // We want this to be explicitly written out to ensure we match the "pre-inited vtable" pattern. + static FindReferenceTargetsCallback() +#pragma warning restore CA1810 // Initialize reference type static fields inline { - Marshal.Release(_ptr); + Vftbl.AddRef = &Untracked_AddRef; + Vftbl.Release = &Untracked_Release; + Vftbl.QueryInterface = &IFindReferenceTargetsCallback_QueryInterface; + Vftbl.FoundTrackerTarget = &IFindReferenceTargetsCallback_FoundTrackerTarget; } } diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 306fa3fa229802..c3eb6e1c8bb778 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -162,6 +162,7 @@ set(VM_HEADERS_DAC_AND_WKS_COMMON codeman.h codeman.inl codeversion.h + conditionalweaktable.h contractimpl.h crst.h debugdebugger.h @@ -275,6 +276,7 @@ endif(FEATURE_JIT_PITCHING) set(VM_SOURCES_DAC ${VM_SOURCES_DAC_AND_WKS_COMMON} + conditionalweaktable.cpp # The usage of conditionalweaktable is only in the DAC, but we put the headers in the VM to enable validation. threaddebugblockinginfo.cpp ) diff --git a/src/coreclr/vm/binder.cpp b/src/coreclr/vm/binder.cpp index 939aa29359649f..d3e7a9bc65e8bf 100644 --- a/src/coreclr/vm/binder.cpp +++ b/src/coreclr/vm/binder.cpp @@ -21,6 +21,8 @@ #include "sigbuilder.h" #include "olevariant.h" #include "configuration.h" +#include "conditionalweaktable.h" +#include "interoplibinterface_comwrappers.h" // // Retrieve structures from ID. @@ -565,7 +567,7 @@ namespace bool FeatureSwitchDisabled(LPCWSTR featureSwitch, bool enabledValue, bool defaultValue) { // If we don't have a feature switch, treat the switch as enabled. - return featureSwitch != nullptr && + return featureSwitch != nullptr && Configuration::GetKnobBooleanValue(featureSwitch, defaultValue) != enabledValue; } diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 93992773a4a387..58764432a16893 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -853,7 +853,7 @@ extern "C" INT64 QCALLTYPE GCInterface_GetTotalMemory() **Arguments: args->generation: The maximum generation to collect **Exceptions: Argument exception if args->generation is < 0 or > GetMaxGeneration(); ==============================================================================*/ -extern "C" void QCALLTYPE GCInterface_Collect(INT32 generation, INT32 mode) +extern "C" void QCALLTYPE GCInterface_Collect(INT32 generation, INT32 mode, CLR_BOOL lowMemoryPressure) { QCALL_CONTRACT; @@ -865,7 +865,7 @@ extern "C" void QCALLTYPE GCInterface_Collect(INT32 generation, INT32 mode) //We don't need to check the top end because the GC will take care of that. GCX_COOP(); - GCHeapUtilities::GetGCHeap()->GarbageCollect(generation, false, mode); + GCHeapUtilities::GetGCHeap()->GarbageCollect(generation, lowMemoryPressure, mode); END_QCALL; } diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 8f229352b439a4..d78d349edcafaf 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -210,7 +210,7 @@ extern "C" void QCALLTYPE GCInterface_AllocateNewArray(void* typeHandlePtr, INT3 extern "C" INT64 QCALLTYPE GCInterface_GetTotalMemory(); -extern "C" void QCALLTYPE GCInterface_Collect(INT32 generation, INT32 mode); +extern "C" void QCALLTYPE GCInterface_Collect(INT32 generation, INT32 mode, CLR_BOOL lowMemoryPressure); extern "C" void* QCALLTYPE GCInterface_GetNextFinalizableObject(QCall::ObjectHandleOnStack pObj); diff --git a/src/coreclr/vm/conditionalweaktable.cpp b/src/coreclr/vm/conditionalweaktable.cpp new file mode 100644 index 00000000000000..03b7798fcde9f5 --- /dev/null +++ b/src/coreclr/vm/conditionalweaktable.cpp @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#include "conditionalweaktable.h" +#include "gchandleutilities.h" +#include "../debug/daccess/gcinterface.dac.h" + +bool ConditionalWeakTableContainerObject::TryGetValue(OBJECTREF key, OBJECTREF* value) +{ + STANDARD_VM_CONTRACT; + SUPPORTS_DAC; + _ASSERTE(key != nullptr && value != nullptr); + + INT32 hashCode = key->TryGetHashCode(); + + if (hashCode == 0) + { + *value = nullptr; + return false; + } + + hashCode &= INT32_MAX; + int bucket = hashCode & (_buckets->GetNumComponents() - 1); + PTR_int32_t buckets = _buckets->GetDirectPointerToNonObjectElements(); + DPTR(Entry) entries = _entries->GetDirectPointerToNonObjectElements(); + for (int entriesIndex = buckets[bucket]; entriesIndex != -1; entriesIndex = entries[entriesIndex].Next) + { + if (entries[entriesIndex].HashCode == hashCode && ObjectFromHandle(entries[entriesIndex].depHnd) == key) + { + *value = HndGetHandleExtraInfo(entries[entriesIndex].depHnd); + return true; + } + } + + *value = nullptr; + return false; +} diff --git a/src/coreclr/vm/conditionalweaktable.h b/src/coreclr/vm/conditionalweaktable.h new file mode 100644 index 00000000000000..24885ea170771f --- /dev/null +++ b/src/coreclr/vm/conditionalweaktable.h @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#ifndef CONDITIONAL_WEAK_TABLE_H +#define CONDITIONAL_WEAK_TABLE_H + +#include "object.h" + +class ConditionalWeakTableContainerObject; +class ConditionalWeakTableObject; + +#ifdef USE_CHECKED_OBJECTREFS +typedef REF CONDITIONAL_WEAK_TABLE_CONTAINER_REF; +typedef REF CONDITIONAL_WEAK_TABLE_REF; +#else +typedef DPTR(ConditionalWeakTableContainerObject) CONDITIONAL_WEAK_TABLE_CONTAINER_REF; +typedef DPTR(ConditionalWeakTableObject) CONDITIONAL_WEAK_TABLE_REF; +#endif + +class ConditionalWeakTableContainerObject final : public Object +{ + friend class CoreLibBinder; + struct Entry + { + int32_t HashCode; + int32_t Next; + OBJECTHANDLE depHnd; + }; + +#ifdef USE_CHECKED_OBJECTREFS + using ENTRYARRAYREF = REF>; +#else + using ENTRYARRAYREF = DPTR(Array); +#endif + + CONDITIONAL_WEAK_TABLE_REF _parent; + I4ARRAYREF _buckets; + ENTRYARRAYREF _entries; + +public: +#ifdef DACCESS_COMPILE + bool TryGetValue(OBJECTREF key, OBJECTREF* value); +#endif +}; + +class ConditionalWeakTableObject final : public Object +{ + friend class CoreLibBinder; + OBJECTREF _lock; + VolatilePtr _container; +public: +#ifdef DACCESS_COMPILE + // Currently, we only use this for access from the DAC, so we don't need to worry about + // locking or tracking the active enumerator count. + // If we need to use this in a context where the runtime isn't suspended, we need to add + // the locking and tracking support. + template + bool TryGetValue(TKey key, TValue* value) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(key != nullptr && value != nullptr); + *value = nullptr; + OBJECTREF valueObj = nullptr; + bool found = _container->TryGetValue((OBJECTREF)key, &valueObj); + if (found) + { + *value = (TValue)valueObj; + } + + return found; + } +#endif +}; + +#endif // CONDITIONAL_WEAK_TABLE_H diff --git a/src/coreclr/vm/corelib.cpp b/src/coreclr/vm/corelib.cpp index 1e28694d8c5935..f335fb26172eb9 100644 --- a/src/coreclr/vm/corelib.cpp +++ b/src/coreclr/vm/corelib.cpp @@ -37,6 +37,7 @@ #include "proftoeeinterfaceimpl.h" #include "appdomainnative.hpp" +#include "conditionalweaktable.h" #include "runtimehandles.h" #include "reflectioninvocation.h" #include "managedmdimport.hpp" @@ -49,6 +50,10 @@ #include "variant.h" #endif // FEATURE_COMINTEROP +#if defined(FEATURE_COMWRAPPERS) +#include "interoplibinterface_comwrappers.h" +#endif + #include "interoplibinterface.h" #include "stubhelpers.h" diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index fe86ea43697e35..de32ac9a24a25b 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -452,13 +452,34 @@ END_ILLINK_FEATURE_SWITCH() #ifdef FEATURE_COMWRAPPERS DEFINE_CLASS(COMWRAPPERS, Interop, ComWrappers) -DEFINE_CLASS(CREATECOMINTERFACEFLAGS, Interop, CreateComInterfaceFlags) DEFINE_CLASS(CREATEOBJECTFLAGS, Interop, CreateObjectFlags) -DEFINE_CLASS(COMWRAPPERSSCENARIO, Interop, ComWrappersScenario) -DEFINE_METHOD(COMWRAPPERS, COMPUTE_VTABLES, CallComputeVtables, SM_Scenario_ComWrappers_Obj_CreateFlags_RefInt_RetPtrVoid) -DEFINE_METHOD(COMWRAPPERS, CREATE_OBJECT, CallCreateObject, SM_Scenario_ComWrappers_IntPtr_CreateFlags_RetObj) -DEFINE_METHOD(COMWRAPPERS, RELEASE_OBJECTS, CallReleaseObjects, SM_ComWrappers_IEnumerable_RetVoid) -DEFINE_METHOD(COMWRAPPERS, CALL_ICUSTOMQUERYINTERFACE, CallICustomQueryInterface, SM_Obj_RefGuid_RefIntPtr_RetInt) +DEFINE_CLASS(MANAGED_OBJECT_WRAPPER_HOLDER,Interop, ComWrappers+ManagedObjectWrapperHolder) +DEFINE_CLASS(NATIVE_OBJECT_WRAPPER, Interop, ComWrappers+NativeObjectWrapper) +DEFINE_METHOD(COMWRAPPERS, CALL_ICUSTOMQUERYINTERFACE, CallICustomQueryInterface, SM_ManagedObjectWrapperHolder_RefGuid_RefIntPtr_RetInt) +DEFINE_METHOD(COMWRAPPERS, GET_OR_CREATE_COM_INTERFACE_FOR_OBJECT_WITH_GLOBAL_MARSHALLING_INSTANCE, GetOrCreateComInterfaceForObjectWithGlobalMarshallingInstance, SM_Obj_RetIntPtr) +DEFINE_METHOD(COMWRAPPERS, GET_OR_CREATE_OBJECT_FOR_COM_INSTANCE_WITH_GLOBAL_MARSHALLING_INSTANCE, GetOrCreateObjectForComInstanceWithGlobalMarshallingInstance, SM_IntPtr_CreateObjectFlags_RetObj) +DEFINE_FIELD(COMWRAPPERS, NAITVE_OBJECT_WRAPPER_TABLE, s_nativeObjectWrapperTable) +DEFINE_FIELD(COMWRAPPERS, ALL_MANAGED_OBJECT_WRAPPER_TABLE, s_allManagedObjectWrapperTable) + +DEFINE_CLASS_U(Interop, ComWrappers+ManagedObjectWrapperHolder, ManagedObjectWrapperHolderObject) +DEFINE_FIELD_U(_wrapper, ManagedObjectWrapperHolderObject, ManagedObjectWrapper) +DEFINE_CLASS_U(Interop, ComWrappers+NativeObjectWrapper, NativeObjectWrapperObject) +DEFINE_FIELD_U(_comWrappers, NativeObjectWrapperObject, _comWrappers) +DEFINE_FIELD_U(_externalComObject, NativeObjectWrapperObject, _externalComObject) +DEFINE_FIELD_U(_inner, NativeObjectWrapperObject, _inner) +DEFINE_FIELD_U(_aggregatedManagedObjectWrapper, NativeObjectWrapperObject, _aggregatedManagedObjectWrapper) +DEFINE_FIELD_U(_uniqueInstance, NativeObjectWrapperObject, _uniqueInstance) +DEFINE_FIELD_U(_proxyHandle, NativeObjectWrapperObject, _proxyHandle) +DEFINE_FIELD_U(_proxyHandleTrackingResurrection, NativeObjectWrapperObject, _proxyHandleTrackingResurrection) +DEFINE_CLASS_U(Interop, ComWrappers+ReferenceTrackerNativeObjectWrapper, ReferenceTrackerNativeObjectWrapperObject) +DEFINE_FIELD_U(_trackerObject, ReferenceTrackerNativeObjectWrapperObject, _trackerObject) +DEFINE_FIELD_U(_contextToken, ReferenceTrackerNativeObjectWrapperObject, _contextToken) +DEFINE_FIELD_U(_trackerObjectDisconnected, ReferenceTrackerNativeObjectWrapperObject, _trackerObjectDisconnected) +DEFINE_CLASS_U(Interop, GCHandleSet+Entry, GCHandleSetEntryObject) +DEFINE_FIELD_U(_next, GCHandleSetEntryObject, _next) +DEFINE_FIELD_U(_value, GCHandleSetEntryObject, _value) +DEFINE_CLASS_U(Interop, GCHandleSet, NoClass) +DEFINE_FIELD_U(_buckets, GCHandleSetObject, _buckets) #endif //FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL @@ -780,6 +801,20 @@ DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_USHRT, CompareExchange, SM_ DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_INT, CompareExchange, SM_RefInt_Int_Int_RetInt) DEFINE_METHOD(INTERLOCKED, COMPARE_EXCHANGE_LONG, CompareExchange, SM_RefLong_Long_Long_RetLong) +DEFINE_CLASS_U(CompilerServices, ConditionalWeakTable`2, NoClass) +DEFINE_FIELD_U(_lock, ConditionalWeakTableObject, _lock) +DEFINE_FIELD_U(_container, ConditionalWeakTableObject, _container) + +DEFINE_CLASS_U(CompilerServices, ConditionalWeakTable`2+Container, NoClass) +DEFINE_FIELD_U(_parent, ConditionalWeakTableContainerObject, _parent) +DEFINE_FIELD_U(_buckets, ConditionalWeakTableContainerObject, _buckets) +DEFINE_FIELD_U(_entries, ConditionalWeakTableContainerObject, _entries) + +DEFINE_CLASS_U(CompilerServices, ConditionalWeakTable`2+Entry, ConditionalWeakTableContainerObject::Entry) +DEFINE_FIELD_U(depHnd, ConditionalWeakTableContainerObject::Entry, depHnd) +DEFINE_FIELD_U(HashCode, ConditionalWeakTableContainerObject::Entry, HashCode) +DEFINE_FIELD_U(Next, ConditionalWeakTableContainerObject::Entry, Next) + DEFINE_CLASS(RAW_DATA, CompilerServices, RawData) DEFINE_FIELD(RAW_DATA, DATA, Data) @@ -1172,6 +1207,10 @@ DEFINE_CLASS(IREADONLYDICTIONARYGENERIC,CollectionsGeneric, IReadOnlyDictionary` DEFINE_CLASS(IDICTIONARYGENERIC, CollectionsGeneric, IDictionary`2) DEFINE_CLASS(KEYVALUEPAIRGENERIC, CollectionsGeneric, KeyValuePair`2) +DEFINE_CLASS(LISTGENERIC, CollectionsGeneric, List`1) +DEFINE_FIELD(LISTGENERIC, ITEMS, _items) +DEFINE_FIELD(LISTGENERIC, SIZE, _size) + DEFINE_CLASS(ICOMPARABLEGENERIC, System, IComparable`1) DEFINE_METHOD(ICOMPARABLEGENERIC, COMPARE_TO, CompareTo, NoSig) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 7fa6ae288bb697..e0df408c04e4bb 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -370,10 +370,6 @@ FCFuncStart(gGCHandleFuncs) FCFuncElement("InternalCompareExchange", MarshalNative::GCHandleInternalCompareExchange) FCFuncEnd() -FCFuncStart(gComAwareWeakReferenceFuncs) - FCFuncElement("HasInteropInfo", ComAwareWeakReferenceNative::HasInteropInfo) -FCFuncEnd() - // // // Class definitions @@ -387,7 +383,6 @@ FCClassElement("Array", "System", gArrayFuncs) FCClassElement("AssemblyLoadContext", "System.Runtime.Loader", gAssemblyLoadContextFuncs) FCClassElement("Buffer", "System", gBufferFuncs) FCClassElement("CastHelpers", "System.Runtime.CompilerServices", gCastHelpers) -FCClassElement("ComAwareWeakReference", "System", gComAwareWeakReferenceFuncs) FCClassElement("Delegate", "System", gDelegateFuncs) FCClassElement("DependentHandle", "System.Runtime", gDependentHandleFuncs) FCClassElement("Environment", "System", gEnvironmentFuncs) diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 6482ee4886f2f3..ed476d073eea73 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -388,8 +388,10 @@ bool GCToEEInterface::RefCountedHandleCallbacks(Object * pObject) #endif #ifdef FEATURE_COMWRAPPERS bool isRooted = false; - if (ComWrappersNative::HasManagedObjectComWrapper((OBJECTREF)pObject, &isRooted)) + if (ComWrappersNative::IsManagedObjectComWrapper((OBJECTREF)pObject, &isRooted)) + { return isRooted; + } #endif #ifdef FEATURE_OBJCMARSHAL bool isReferenced = false; @@ -1832,4 +1834,4 @@ void GCToEEInterface::LogErrorToHost(const char *message) uint64_t GCToEEInterface::GetThreadOSThreadId(Thread* thread) { return thread->GetOSThreadId64(); -} \ No newline at end of file +} diff --git a/src/coreclr/vm/interoplibinterface.h b/src/coreclr/vm/interoplibinterface.h index c4f23ce654016c..fb77e7b30c5a3d 100644 --- a/src/coreclr/vm/interoplibinterface.h +++ b/src/coreclr/vm/interoplibinterface.h @@ -9,108 +9,7 @@ #define _INTEROPLIBINTERFACE_H_ #ifdef FEATURE_COMWRAPPERS - -namespace InteropLibInterface -{ - // Base definition of the external object context. - struct ExternalObjectContextBase - { - PTR_VOID Identity; - DWORD SyncBlockIndex; - }; -} - -// Native calls for the managed ComWrappers API -class ComWrappersNative -{ -public: - static const INT64 InvalidWrapperId = 0; -public: // Lifetime management for COM Wrappers - static void DestroyManagedObjectComWrapper(_In_ void* wrapper); - static void DestroyExternalComObjectContext(_In_ void* context); - static void MarkExternalComObjectContextCollected(_In_ void* context); - -public: // COM activation - static void MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe); - -public: // Unwrapping support - static IUnknown* GetIdentityForObject(_In_ OBJECTREF* objectPROTECTED, _In_ REFIID riid, _Out_ INT64* wrapperId, _Out_ bool* isAggregated); - static bool HasManagedObjectComWrapper(_In_ OBJECTREF object, _Out_ bool* isActive); - -public: // GC interaction - static void OnFullGCStarted(); - static void OnFullGCFinished(); - static void AfterRefCountedHandleCallbacks(); -}; -// Native QCalls for the abstract ComWrappers managed type. -extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( - _Out_ void** fpQueryInterface, - _Out_ void** fpAddRef, - _Out_ void** fpRelease); - -extern "C" BOOL QCALLTYPE ComWrappers_TryGetComInstance( - _In_ QCall::ObjectHandleOnStack wrapperMaybe, - _Out_ void** externalComObject); - -extern "C" BOOL QCALLTYPE ComWrappers_TryGetObject( - _In_ void* wrapperMaybe, - _Inout_ QCall::ObjectHandleOnStack instance); - -extern "C" BOOL QCALLTYPE ComWrappers_TryGetOrCreateComInterfaceForObject( - _In_ QCall::ObjectHandleOnStack comWrappersImpl, - _In_ INT64 wrapperId, - _In_ QCall::ObjectHandleOnStack instance, - _In_ INT32 flags, - _Outptr_ void** wrapperRaw); - -extern "C" BOOL QCALLTYPE ComWrappers_TryGetOrCreateObjectForComInstance( - _In_ QCall::ObjectHandleOnStack comWrappersImpl, - _In_ INT64 wrapperId, - _In_ void* externalComObject, - _In_opt_ void* innerMaybe, - _In_ INT32 flags, - _In_ QCall::ObjectHandleOnStack wrapperMaybe, - _Inout_ QCall::ObjectHandleOnStack retValue); - -// Native QCall for the ComWrappers managed type to indicate a global instance -// is registered for marshalling. This should be set if the private static member -// representing the global instance for marshalling on ComWrappers is non-null. -extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForMarshalling(_In_ INT64 id); -// Native QCall for the ComWrappers managed type to indicate a global instance -// is registered for tracker support. This should be set if the private static member -// representing the global instance for tracker support on ComWrappers is non-null. -extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForTrackerSupport(_In_ INT64 id); - -class GlobalComWrappersForMarshalling -{ -public: // Functions operating on a registered global instance for marshalling - static bool IsRegisteredInstance(_In_ INT64 id); - - static bool TryGetOrCreateComInterfaceForObject( - _In_ OBJECTREF instance, - _Outptr_ void** wrapperRaw); - - static bool TryGetOrCreateObjectForComInstance( - _In_ IUnknown* externalComObject, - _In_ INT32 objFromComIPFlags, - _Out_ OBJECTREF* objRef); -}; - - -class GlobalComWrappersForTrackerSupport -{ -public: // Functions operating on a registered global instance for tracker support - static bool IsRegisteredInstance(_In_ INT64 id); - - static bool TryGetOrCreateComInterfaceForObject( - _In_ OBJECTREF instance, - _Outptr_ void** wrapperRaw); - - static bool TryGetOrCreateObjectForComInstance( - _In_ IUnknown* externalComObject, - _Out_ OBJECTREF* objRef); -}; - +#include "interoplibinterface_comwrappers.h" #endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index 4b41b68b8e6151..833e842bf264d9 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -13,2001 +13,498 @@ #include "finalizerthread.h" // Interop library header +#include #include #include "interoplibinterface.h" using CreateObjectFlags = InteropLib::Com::CreateObjectFlags; -using CreateComInterfaceFlags = InteropLib::Com::CreateComInterfaceFlags; namespace { - void* GetCurrentCtxCookieWrapper() - { - STATIC_CONTRACT_WRAPPER; - - #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT - return GetCurrentCtxCookie(); - #else - return NULL; - #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT - } - - // This class is used to track the external object within the runtime. - struct ExternalObjectContext : public InteropLibInterface::ExternalObjectContextBase - { - static const DWORD InvalidSyncBlockIndex; - - void* ThreadContext; - INT64 WrapperId; - - enum - { - Flags_None = 0, - - // The EOC has been collected and is no longer visible from managed code. - Flags_Collected = 1, - - Flags_ReferenceTracker = 2, - Flags_InCache = 4, - - // The EOC is "detached" and no longer used to map between identity and a managed object. - // This will only be set if the EOC was inserted into the cache. - Flags_Detached = 8, - - // This EOC is an aggregated instance - Flags_Aggregated = 16 - }; - DWORD Flags; - - static void Construct( - _Out_ ExternalObjectContext* cxt, - _In_ IUnknown* identity, - _In_opt_ void* threadContext, - _In_ DWORD syncBlockIndex, - _In_ INT64 wrapperId, - _In_ DWORD flags) - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(cxt != NULL); - PRECONDITION(threadContext != NULL); - PRECONDITION(syncBlockIndex != InvalidSyncBlockIndex); - } - CONTRACTL_END; - - cxt->Identity = (void*)identity; - cxt->ThreadContext = threadContext; - cxt->SyncBlockIndex = syncBlockIndex; - cxt->WrapperId = wrapperId; - cxt->Flags = flags; - } - - bool IsSet(_In_ DWORD f) const - { - return ((Flags & f) == f); - } - - bool IsActive() const - { - return !IsSet(Flags_Collected) - && (SyncBlockIndex != InvalidSyncBlockIndex); - } - - void MarkCollected() - { - _ASSERTE(GCHeapUtilities::IsGCInProgress()); - SyncBlockIndex = InvalidSyncBlockIndex; - Flags |= Flags_Collected; - } - - void MarkDetached() - { - _ASSERTE(GCHeapUtilities::IsGCInProgress()); - Flags |= Flags_Detached; - } - - void MarkNotInCache() - { - ::InterlockedAnd((LONG*)&Flags, (~Flags_InCache)); - } - - OBJECTREF GetObjectRef() - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - _ASSERTE(IsActive()); - return ObjectToOBJECTREF(g_pSyncTable[SyncBlockIndex].m_Object); - } - - struct Key - { - public: - Key(void* identity, INT64 wrapperId) - : _identity { identity } - , _wrapperId { wrapperId } - { - _ASSERTE(identity != NULL); - _ASSERTE(wrapperId != ComWrappersNative::InvalidWrapperId); - } - - DWORD Hash() const - { - DWORD hash = (_wrapperId >> 32) ^ (_wrapperId & 0xFFFFFFFF); -#if POINTER_BITS == 32 - return hash ^ (DWORD)_identity; -#else - INT64 identityInt64 = (INT64)_identity; - return hash ^ (identityInt64 >> 32) ^ (identityInt64 & 0xFFFFFFFF); -#endif - } - - bool operator==(const Key & rhs) const { return _identity == rhs._identity && _wrapperId == rhs._wrapperId; } - - private: - void* _identity; - INT64 _wrapperId; - }; - - Key GetKey() const - { - return Key(Identity, WrapperId); - } - }; - - const DWORD ExternalObjectContext::InvalidSyncBlockIndex = 0; // See syncblk.h - - static_assert((sizeof(ExternalObjectContext) % sizeof(void*)) == 0, "Keep context pointer size aligned"); - - // Holder for a External Wrapper Result - struct ExternalWrapperResultHolder - { - InteropLib::Com::ExternalWrapperResult Result; - ExternalWrapperResultHolder() - : Result{} - { } - ~ExternalWrapperResultHolder() - { - if (Result.Context != NULL) - { - GCX_PREEMP(); - // We also request collection notification since this holder is presently - // only used for new activation of wrappers therefore the notification won't occur - // at the typical time of before finalization. - InteropLib::Com::DestroyWrapperForExternal(Result.Context, /* notifyIsBeingCollected */ true); - } - } - InteropLib::Com::ExternalWrapperResult* operator&() - { - return &Result; - } - ExternalObjectContext* GetContext() - { - return static_cast(Result.Context); - } - ExternalObjectContext* DetachContext() - { - ExternalObjectContext* t = GetContext(); - Result.Context = NULL; - return t; - } - }; - - using ExtObjCxtRefCache = RCWRefCache; - - class ExtObjCxtCache - { - static Volatile g_Instance; - - public: // static - static ExtObjCxtCache* GetInstanceNoThrow() noexcept - { - CONTRACT(ExtObjCxtCache*) - { - NOTHROW; - GC_NOTRIGGER; - POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); - } - CONTRACT_END; - - RETURN g_Instance; - } - - static ExtObjCxtCache* GetInstance() - { - CONTRACT(ExtObjCxtCache*) - { - THROWS; - GC_NOTRIGGER; - POSTCONDITION(RETVAL != NULL); - } - CONTRACT_END; - - if (g_Instance.Load() == NULL) - { - ExtObjCxtCache* instMaybe = new ExtObjCxtCache(); - - // Attempt to set the global instance. - if (NULL != InterlockedCompareExchangeT(&g_Instance, instMaybe, NULL)) - delete instMaybe; - } - - RETURN g_Instance; - } - - public: // Inner class definitions - class Traits : public DefaultSHashTraits - { - public: - using key_t = ExternalObjectContext::Key; - static const key_t GetKey(_In_ element_t e) { LIMITED_METHOD_CONTRACT; return e->GetKey(); } - static count_t Hash(_In_ key_t key) { LIMITED_METHOD_CONTRACT; return (count_t)key.Hash(); } - static bool Equals(_In_ key_t lhs, _In_ key_t rhs) { LIMITED_METHOD_CONTRACT; return lhs == rhs; } - }; - - // Alias some useful types - using Element = SHash::element_t; - using Iterator = SHash::Iterator; - - class ReaderLock final - { - SimpleReadLockHolder _lock; - public: - ReaderLock(_In_ ExtObjCxtCache* cache) - : _lock{ &cache->_lock } - { } - - ~ReaderLock() = default; - }; - - class WriterLock final - { - SimpleWriteLockHolder _lock; - public: - WriterLock(_In_ ExtObjCxtCache* cache) - : _lock{ &cache->_lock } - { } - - ~WriterLock() = default; - }; - - private: - friend struct InteropLibImports::RuntimeCallContext; - SHash _hashMap; - SimpleRWLock _lock; - ExtObjCxtRefCache* _refCache; - - ExtObjCxtCache() - : _lock(COOPERATIVE, LOCK_TYPE_DEFAULT) - , _refCache(GetAppDomain()->GetRCWRefCache()) - { } - ~ExtObjCxtCache() = default; - - public: -#if _DEBUG - bool IsLockHeld() - { - WRAPPER_NO_CONTRACT; - return (_lock.LockTaken() != FALSE); - } -#endif // _DEBUG - - // Get the associated reference cache with this external object cache. - ExtObjCxtRefCache* GetRefCache() - { - WRAPPER_NO_CONTRACT; - return _refCache; - } - - // Create a managed IEnumerable instance for this collection. - // The collection should respect the supplied arguments. - // withFlags - If Flag_None, then ignore. Otherwise objects must have these flags. - // threadContext - The object must be associated with the supplied thread context. - OBJECTREF CreateManagedEnumerable(_In_ DWORD withFlags, _In_opt_ void* threadContext) - { - CONTRACT(OBJECTREF) - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(!IsLockHeld()); - PRECONDITION(!(withFlags & ExternalObjectContext::Flags_Collected)); - PRECONDITION(!(withFlags & ExternalObjectContext::Flags_Detached)); - POSTCONDITION(RETVAL != NULL); - } - CONTRACT_END; - - struct - { - PTRARRAYREF arrRefTmp; - PTRARRAYREF arrRef; - } gc; - gc.arrRefTmp = NULL; - gc.arrRef = NULL; - GCPROTECT_BEGIN(gc); - - // Only add objects that are in the correct thread - // context, haven't been detached from the cache, and have - // the appropriate flags set. - // Define a macro predicate since it used in multiple places. - // If an instance is in the hashmap, it is active. This invariant - // holds because the GC is what marks and removes from the cache. -#define SELECT_OBJECT(XX) XX->ThreadContext == threadContext \ - && !XX->IsSet(ExternalObjectContext::Flags_Detached) \ - && (withFlags == ExternalObjectContext::Flags_None || XX->IsSet(withFlags)) - - // Determine the count of objects to return. - SIZE_T objCountMax = 0; - { - ReaderLock lock(this); - Iterator end = _hashMap.End(); - for (Iterator curr = _hashMap.Begin(); curr != end; ++curr) - { - ExternalObjectContext* inst = *curr; - if (SELECT_OBJECT(inst)) - { - objCountMax++; - } - } - } - - // Allocate enumerable type to return. - gc.arrRef = (PTRARRAYREF)AllocateObjectArray((DWORD)objCountMax, g_pObjectClass); - - CQuickArrayList localList; - - // Iterate over the hashmap again while populating the above array - // using the same predicate as before and holding onto context instances. - SIZE_T objCount = 0; - if (0 < objCountMax) - { - ReaderLock lock(this); - Iterator end = _hashMap.End(); - for (Iterator curr = _hashMap.Begin(); curr != end; ++curr) - { - ExternalObjectContext* inst = *curr; - if (SELECT_OBJECT(inst)) - { - localList.Push(inst); - - gc.arrRef->SetAt(objCount, inst->GetObjectRef()); - objCount++; - - STRESS_LOG1(LF_INTEROP, LL_INFO1000, "Add EOC to Enumerable: 0x%p\n", inst); - } - - // There is a chance more objects were added to the hash while the - // lock was released during array allocation. Once we hit the computed max - // we stop to avoid looking longer than needed. - if (objCount == objCountMax) - break; - } - } - -#undef SELECT_OBJECT - - // During the allocation of the array to return, a GC could have - // occurred and objects detached from this cache. In order to avoid - // having null array elements we will allocate a new array. - // This subsequent allocation is okay because the array we are - // replacing extends all object lifetimes. - if (objCount < objCountMax) - { - gc.arrRefTmp = (PTRARRAYREF)AllocateObjectArray((DWORD)objCount, g_pObjectClass); - - void* dest = gc.arrRefTmp->GetDataPtr(); - void* src = gc.arrRef->GetDataPtr(); - SIZE_T elementSize = gc.arrRef->GetComponentSize(); - - memmoveGCRefs(dest, src, objCount * elementSize); - gc.arrRef = gc.arrRefTmp; - } - - // All objects are now referenced from the array so won't be collected - // at this point. This means we can safely iterate over the ExternalObjectContext - // instances. - { - // Separate the wrapper from the tracker runtime prior to - // passing them onto the caller. This call is okay to make - // even if the instance isn't from the tracker runtime. - // We switch to Preemptive mode since separating a wrapper - // requires us to call out to non-runtime code which could - // call back into the runtime and/or trigger a GC. - GCX_PREEMP(); - for (SIZE_T i = 0; i < localList.Size(); i++) - { - ExternalObjectContext* inst = localList[i]; - InteropLib::Com::SeparateWrapperFromTrackerRuntime(inst); - } - } - - GCPROTECT_END(); - - RETURN gc.arrRef; - } - - ExternalObjectContext* Find(_In_ const ExternalObjectContext::Key& key) - { - CONTRACT(ExternalObjectContext*) - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(IsLockHeld()); - POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); - } - CONTRACT_END; - - // Forbid the GC from messing with the hash table. - GCX_FORBID(); - - RETURN _hashMap.Lookup(key); - } - - ExternalObjectContext* Add(_In_ ExternalObjectContext* cxt) - { - CONTRACT(ExternalObjectContext*) - { - THROWS; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(IsLockHeld()); - PRECONDITION(!Traits::IsNull(cxt) && !Traits::IsDeleted(cxt)); - PRECONDITION(cxt->Identity != NULL); - PRECONDITION(Find(cxt->GetKey()) == NULL); - POSTCONDITION(RETVAL == cxt); - } - CONTRACT_END; - - _hashMap.Add(cxt); - RETURN cxt; - } - - ExternalObjectContext* FindOrAdd(_In_ const ExternalObjectContext::Key& key, _In_ ExternalObjectContext* newCxt) - { - CONTRACT(ExternalObjectContext*) - { - THROWS; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(IsLockHeld()); - PRECONDITION(!Traits::IsNull(newCxt) && !Traits::IsDeleted(newCxt)); - PRECONDITION(key == newCxt->GetKey()); - POSTCONDITION(CheckPointer(RETVAL)); - } - CONTRACT_END; - - // Forbid the GC from messing with the hash table. - GCX_FORBID(); - - ExternalObjectContext* cxt = Find(key); - if (cxt == NULL) - cxt = Add(newCxt); - - RETURN cxt; - } - - void Remove(_In_ ExternalObjectContext* cxt) - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(!Traits::IsNull(cxt) && !Traits::IsDeleted(cxt)); - PRECONDITION(cxt->Identity != NULL); - - // The GC thread doesn't have to take the lock - // since all other threads access in cooperative mode - PRECONDITION( - (IsLockHeld() && GetThread()->PreemptiveGCDisabled()) - || Debug_IsLockedViaThreadSuspension()); - } - CONTRACTL_END; - - _hashMap.Remove(cxt->GetKey()); - } - - void DetachNotPromotedEOCs() - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(GCHeapUtilities::IsGCInProgress()); // GC is in progress and the runtime is suspended - } - CONTRACTL_END; - - Iterator curr = _hashMap.Begin(); - Iterator end = _hashMap.End(); - - ExternalObjectContext* cxt; - for (; curr != end; ++curr) - { - cxt = *curr; - if (!cxt->IsSet(ExternalObjectContext::Flags_Detached) - && !GCHeapUtilities::GetGCHeap()->IsPromoted(OBJECTREFToObject(cxt->GetObjectRef()))) - { - // Indicate the wrapper entry has been detached. - cxt->MarkDetached(); - - // Notify the wrapper it was not promoted and is being collected. - InteropLib::Com::NotifyWrapperForExternalIsBeingCollected(cxt); - } - } - } - }; - - // Global instance of the external object cache - Volatile ExtObjCxtCache::g_Instance; - - // Indicator for if a ComWrappers implementation is globally registered - INT64 g_marshallingGlobalInstanceId = ComWrappersNative::InvalidWrapperId; - INT64 g_trackerSupportGlobalInstanceId = ComWrappersNative::InvalidWrapperId; - - // Defined handle types for the specific object uses. - const HandleType InstanceHandleType{ HNDTYPE_REFCOUNTED }; - - // Scenarios for ComWrappers usage. - // These values should match the managed definition in ComWrappers. - enum class ComWrappersScenario - { - Instance = 0, - TrackerSupportGlobalInstance = 1, - MarshallingGlobalInstance = 2, - }; - - void* CallComputeVTables( - _In_ ComWrappersScenario scenario, - _In_ OBJECTREF* implPROTECTED, - _In_ OBJECTREF* instancePROTECTED, - _In_ INT32 flags, - _Out_ DWORD* vtableCount) - { - CONTRACTL - { - THROWS; - MODE_COOPERATIVE; - PRECONDITION(implPROTECTED != NULL); - PRECONDITION(instancePROTECTED != NULL); - PRECONDITION(vtableCount != NULL); - } - CONTRACTL_END; - - void* vtables = NULL; - - PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__COMPUTE_VTABLES); - DECLARE_ARGHOLDER_ARRAY(args, 5); - args[ARGNUM_0] = DWORD_TO_ARGHOLDER(scenario); - args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); - args[ARGNUM_2] = OBJECTREF_TO_ARGHOLDER(*instancePROTECTED); - args[ARGNUM_3] = DWORD_TO_ARGHOLDER(flags); - args[ARGNUM_4] = PTR_TO_ARGHOLDER(vtableCount); - CALL_MANAGED_METHOD(vtables, void*, args); - - return vtables; - } - - OBJECTREF CallCreateObject( - _In_ ComWrappersScenario scenario, - _In_ OBJECTREF* implPROTECTED, - _In_ IUnknown* externalComObject, - _In_ INT32 flags) - { - CONTRACTL - { - THROWS; - MODE_COOPERATIVE; - PRECONDITION(implPROTECTED != NULL); - PRECONDITION(externalComObject != NULL); - } - CONTRACTL_END; - - OBJECTREF retObjRef; - - PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__CREATE_OBJECT); - DECLARE_ARGHOLDER_ARRAY(args, 4); - args[ARGNUM_0] = DWORD_TO_ARGHOLDER(scenario); - args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); - args[ARGNUM_2] = PTR_TO_ARGHOLDER(externalComObject); - args[ARGNUM_3] = DWORD_TO_ARGHOLDER(flags); - CALL_MANAGED_METHOD_RETREF(retObjRef, OBJECTREF, args); - - return retObjRef; - } - - void CallReleaseObjects( - _In_ OBJECTREF* implPROTECTED, - _In_ OBJECTREF* objsEnumPROTECTED) - { - CONTRACTL - { - THROWS; - MODE_COOPERATIVE; - PRECONDITION(implPROTECTED != NULL); - PRECONDITION(objsEnumPROTECTED != NULL); - } - CONTRACTL_END; - - PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__RELEASE_OBJECTS); - DECLARE_ARGHOLDER_ARRAY(args, 2); - args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); - args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*objsEnumPROTECTED); - CALL_MANAGED_METHOD_NORET(args); - } - - int CallICustomQueryInterface( - _In_ OBJECTREF* implPROTECTED, - _In_ REFGUID iid, - _Outptr_result_maybenull_ void** ppObject) - { - CONTRACTL - { - THROWS; - MODE_COOPERATIVE; - PRECONDITION(implPROTECTED != NULL); - PRECONDITION(ppObject != NULL); - } - CONTRACTL_END; - - int result; - - PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__CALL_ICUSTOMQUERYINTERFACE); - DECLARE_ARGHOLDER_ARRAY(args, 3); - args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); - args[ARGNUM_1] = PTR_TO_ARGHOLDER(&iid); - args[ARGNUM_2] = PTR_TO_ARGHOLDER(ppObject); - CALL_MANAGED_METHOD(result, int, args); - - return result; - } - - bool TryGetOrCreateComInterfaceForObjectInternal( - _In_opt_ OBJECTREF impl, - _In_ INT64 wrapperId, - _In_ OBJECTREF instance, - _In_ CreateComInterfaceFlags flags, - _In_ ComWrappersScenario scenario, - _Outptr_ void** wrapperRaw) - { - CONTRACT(bool) - { - THROWS; - MODE_COOPERATIVE; - PRECONDITION(instance != NULL); - PRECONDITION(wrapperRaw != NULL); - PRECONDITION((impl != NULL && scenario == ComWrappersScenario::Instance) || (impl == NULL && scenario != ComWrappersScenario::Instance)); - PRECONDITION(wrapperId != ComWrappersNative::InvalidWrapperId); - } - CONTRACT_END; - - HRESULT hr; - - SafeComHolder newWrapper; - void* wrapperRawMaybe = NULL; - - struct - { - OBJECTREF implRef; - OBJECTREF instRef; - } gc; - gc.implRef = impl; - gc.instRef = instance; - GCPROTECT_BEGIN(gc); - - // Check the object's SyncBlock for a managed object wrapper. - SyncBlock* syncBlock = gc.instRef->GetSyncBlock(); - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); - _ASSERTE(syncBlock->IsPrecious()); - - // Query the associated InteropSyncBlockInfo for an existing managed object wrapper. - if (!interopInfo->TryGetManagedObjectComWrapper(wrapperId, &wrapperRawMaybe)) - { - // Compute VTables for the new existing COM object using the supplied COM Wrappers implementation. - // - // N.B. Calling to compute the associated VTables is perhaps early since no lock - // is taken. However, a key assumption here is that the returned memory will be - // idempotent for the same object. - DWORD vtableCount; - void* vtables = CallComputeVTables(scenario, &gc.implRef, &gc.instRef, flags, &vtableCount); - - // Re-query the associated InteropSyncBlockInfo for an existing managed object wrapper. - if (!interopInfo->TryGetManagedObjectComWrapper(wrapperId, &wrapperRawMaybe) - && ((vtables != nullptr && vtableCount > 0) || (vtableCount == 0))) - { - OBJECTHANDLE instHandle = GetAppDomain()->CreateTypedHandle(gc.instRef, InstanceHandleType); - - // Call the InteropLib and create the associated managed object wrapper. - { - GCX_PREEMP(); - hr = InteropLib::Com::CreateWrapperForObject( - instHandle, - vtableCount, - vtables, - flags, - &newWrapper); - } - if (FAILED(hr)) - { - DestroyHandleCommon(instHandle, InstanceHandleType); - COMPlusThrowHR(hr); - } - _ASSERTE(!newWrapper.IsNull()); - - // Try setting the newly created managed object wrapper on the InteropSyncBlockInfo. - if (!interopInfo->TrySetManagedObjectComWrapper(wrapperId, newWrapper)) - { - // The new wrapper couldn't be set which means a wrapper already exists. - newWrapper.Release(); - - // If the managed object wrapper couldn't be set, then - // it should be possible to get the current one. - if (!interopInfo->TryGetManagedObjectComWrapper(wrapperId, &wrapperRawMaybe)) - { - UNREACHABLE(); - } - } - } - } - - // Determine what to return. - if (!newWrapper.IsNull()) - { - // A new managed object wrapper was created, remove the object from the holder. - // No AddRef() here since the wrapper should be created with a reference. - wrapperRawMaybe = newWrapper.Extract(); - STRESS_LOG1(LF_INTEROP, LL_INFO100, "Created MOW: 0x%p\n", wrapperRawMaybe); - } - else if (wrapperRawMaybe != NULL) - { - // AddRef() the existing wrapper. - IUnknown* wrapper = static_cast(wrapperRawMaybe); - (void)wrapper->AddRef(); - } - - GCPROTECT_END(); - - *wrapperRaw = wrapperRawMaybe; - RETURN (wrapperRawMaybe != NULL); - } - - bool TryGetOrCreateObjectForComInstanceInternal( - _In_opt_ OBJECTREF impl, - _In_ INT64 wrapperId, - _In_ IUnknown* identity, - _In_opt_ IUnknown* inner, - _In_ CreateObjectFlags flags, - _In_ ComWrappersScenario scenario, - _In_opt_ OBJECTREF wrapperMaybe, - _Out_ OBJECTREF* objRef) - { - CONTRACT(bool) - { - THROWS; - MODE_COOPERATIVE; - PRECONDITION(identity != NULL); - PRECONDITION(objRef != NULL); - PRECONDITION((impl != NULL && scenario == ComWrappersScenario::Instance) || (impl == NULL && scenario != ComWrappersScenario::Instance)); - PRECONDITION(wrapperId != ComWrappersNative::InvalidWrapperId); - } - CONTRACT_END; - - HRESULT hr; - ExternalObjectContext* extObjCxt = NULL; - - struct - { - OBJECTREF implRef; - OBJECTREF wrapperMaybeRef; - OBJECTREF objRefMaybe; - } gc; - gc.implRef = impl; - gc.wrapperMaybeRef = wrapperMaybe; - gc.objRefMaybe = NULL; - GCPROTECT_BEGIN(gc); - - STRESS_LOG4(LF_INTEROP, LL_INFO1000, "Get or Create EOC: (Identity: 0x%p) (Flags: %x) (Maybe: 0x%p) (ID: %lld)\n", identity, flags, OBJECTREFToObject(wrapperMaybe), wrapperId); - - ExtObjCxtCache* cache = ExtObjCxtCache::GetInstance(); - InteropLib::OBJECTHANDLE handle = NULL; - - ExternalObjectContext::Key cacheKey(identity, wrapperId); - - // Check if the user requested a unique instance. - bool uniqueInstance = !!(flags & CreateObjectFlags::CreateObjectFlags_UniqueInstance); - if (!uniqueInstance) - { - bool objectFound = false; - bool tryRemove = false; - { - // Perform a quick look up to determine if we know of the object and if - // we need to perform a more expensive cleanup operation below. - ExtObjCxtCache::ReaderLock lock(cache); - extObjCxt = cache->Find(cacheKey); - objectFound = extObjCxt != NULL; - tryRemove = objectFound && extObjCxt->IsSet(ExternalObjectContext::Flags_Detached); - } - - if (tryRemove) - { - // Perform the slower cleanup operation that may be appropriate - // if the object still exists and has been detached. - ExtObjCxtCache::WriterLock lock(cache); - extObjCxt = cache->Find(cacheKey); - objectFound = extObjCxt != NULL; - if (objectFound && extObjCxt->IsSet(ExternalObjectContext::Flags_Detached)) - { - // If an EOC has been found but is marked detached, then we will remove it from the - // cache here instead of letting the GC do it later and pretend like it wasn't found. - STRESS_LOG1(LF_INTEROP, LL_INFO10, "Detached EOC requested: 0x%p\n", extObjCxt); - cache->Remove(extObjCxt); - extObjCxt->MarkNotInCache(); - extObjCxt = NULL; - } - } - - // If is no object found in the cache, check if the object COM instance is actually the CCW - // representing a managed object. If the user passed the Unwrap flag, COM instances that are - // actually CCWs should be unwrapped to the original managed object to allow for round - // tripping object -> COM instance -> object. - if (!objectFound && (flags & CreateObjectFlags::CreateObjectFlags_Unwrap)) - { - GCX_PREEMP(); - - // If the COM instance is a CCW that is not COM-activated, use the object of that wrapper object. - InteropLib::OBJECTHANDLE handleLocal; - if (InteropLib::Com::GetObjectForWrapper(identity, &handleLocal) == S_OK - && InteropLib::Com::IsComActivated(identity) == S_FALSE) - { - handle = handleLocal; - } - } - } - - STRESS_LOG2(LF_INTEROP, LL_INFO1000, "EOC: 0x%p or Handle: 0x%p\n", extObjCxt, handle); - - if (extObjCxt != NULL) - { - gc.objRefMaybe = extObjCxt->GetObjectRef(); - } - else if (handle != NULL) - { - // We have an object handle from the COM instance which is a CCW. - ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - - // Now we need to check if this object is a CCW from the same ComWrappers instance - // as the one creating the EOC. If it is not, we need to create a new EOC for it. - // Otherwise, use it. This allows for the round-trip from object -> COM instance -> object. - OBJECTREF objRef = NULL; - GCPROTECT_BEGIN(objRef); - objRef = ObjectFromHandle(objectHandle); - - SyncBlock* syncBlock = objRef->GetSyncBlock(); - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); - - // If we found a managed object wrapper in this ComWrappers instance - // and it's the same identity pointer as the one we're creating an EOC for, - // unwrap it. We don't AddRef the wrapper as we don't take a reference to it. - // - // A managed object can have multiple managed object wrappers, with a max of one per context. - // Let's say we have a managed object A and ComWrappers instances C1 and C2. Let B1 and B2 be the - // managed object wrappers for A created with C1 and C2 respectively. - // If we are asked to create an EOC for B1 with the unwrap flag on the C2 ComWrappers instance, - // we will create a new wrapper. In this scenario, we'll only unwrap B2. - void* wrapperRawMaybe = NULL; - if (interopInfo->TryGetManagedObjectComWrapper(wrapperId, &wrapperRawMaybe) - && wrapperRawMaybe == identity) - { - gc.objRefMaybe = objRef; - } - else - { - STRESS_LOG2(LF_INTEROP, LL_INFO1000, "Not unwrapping handle (0x%p) because the object's MOW in this ComWrappers instance (if any) (0x%p) is not the provided identity\n", handle, wrapperRawMaybe); - } - GCPROTECT_END(); - } - - if (gc.objRefMaybe == NULL) - { - // Create context instance for the possibly new external object. - ExternalWrapperResultHolder resultHolder; - - { - GCX_PREEMP(); - hr = InteropLib::Com::CreateWrapperForExternal( - identity, - inner, - flags, - sizeof(ExternalObjectContext), - &resultHolder); - } - if (FAILED(hr)) - COMPlusThrowHR(hr); - - // The user could have supplied a wrapper so assign that now. - gc.objRefMaybe = gc.wrapperMaybeRef; - - // If the wrapper hasn't been set yet, call the implementation to create one. - if (gc.objRefMaybe == NULL) - { - gc.objRefMaybe = CallCreateObject(scenario, &gc.implRef, identity, flags); - } - - // The object may be null if the specified ComWrapper implementation returns null - // or there is no registered global instance. It is the caller's responsibility - // to handle this case and error if necessary. - if (gc.objRefMaybe != NULL) - { - // Construct the new context with the object details. - DWORD eocFlags = (resultHolder.Result.FromTrackerRuntime - ? ExternalObjectContext::Flags_ReferenceTracker - : ExternalObjectContext::Flags_None) | - (uniqueInstance - ? ExternalObjectContext::Flags_None - : ExternalObjectContext::Flags_InCache) | - ((flags & CreateObjectFlags::CreateObjectFlags_Aggregated) != 0 - ? ExternalObjectContext::Flags_Aggregated - : ExternalObjectContext::Flags_None); - - ExternalObjectContext::Construct( - resultHolder.GetContext(), - identity, - GetCurrentCtxCookieWrapper(), - gc.objRefMaybe->GetSyncBlockIndex(), - wrapperId, - eocFlags); - - if (uniqueInstance) - { - extObjCxt = resultHolder.GetContext(); - } - else - { - // Attempt to insert the new context into the cache. - ExtObjCxtCache::WriterLock lock(cache); - extObjCxt = cache->FindOrAdd(cacheKey, resultHolder.GetContext()); - } - - STRESS_LOG2(LF_INTEROP, LL_INFO100, "EOC cache insert: 0x%p == 0x%p\n", extObjCxt, resultHolder.GetContext()); - - // If the returned context matches the new context it means the - // new context was inserted or a unique instance was requested. - if (extObjCxt == resultHolder.GetContext()) - { - // Update the object's SyncBlock with a handle to the context for runtime cleanup. - SyncBlock* syncBlock = gc.objRefMaybe->GetSyncBlock(); - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); - _ASSERTE(syncBlock->IsPrecious()); - - // Since the caller has the option of providing a wrapper, it is - // possible the supplied wrapper already has an associated external - // object and an object can only be associated with one external object. - if (!interopInfo->TrySetExternalComObjectContext((void**)extObjCxt)) - { - // Failed to set the context; one must already exist. - // Remove from the cache above as well. - ExtObjCxtCache::WriterLock lock(cache); - cache->Remove(resultHolder.GetContext()); - - COMPlusThrow(kNotSupportedException); - } - - // Detach from the holder to avoid cleanup. - (void)resultHolder.DetachContext(); - STRESS_LOG2(LF_INTEROP, LL_INFO100, "Created EOC (Unique Instance: %d): 0x%p\n", (int)uniqueInstance, extObjCxt); - - // 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. - if (flags & CreateObjectFlags::CreateObjectFlags_Aggregated - && resultHolder.Result.ManagedObjectWrapper) - { - (void)identity->Release(); - STRESS_LOG1(LF_INTEROP, LL_INFO100, "EOC aggregated with MOW: 0x%p\n", identity); - } - } - - _ASSERTE(extObjCxt->IsActive()); - } - } - - STRESS_LOG3(LF_INTEROP, LL_INFO1000, "EOC: 0x%p, 0x%p => 0x%p\n", extObjCxt, identity, OBJECTREFToObject(gc.objRefMaybe)); - - GCPROTECT_END(); - - *objRef = gc.objRefMaybe; - RETURN (gc.objRefMaybe != NULL); - } -} - -namespace -{ - BOOL g_isGlobalPeggingOn = TRUE; -} - -namespace InteropLibImports -{ - void* MemAlloc(_In_ size_t sizeInBytes, _In_ AllocScenario scenario) noexcept - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(sizeInBytes != 0); - } - CONTRACTL_END; - - return ::malloc(sizeInBytes); - } - - void MemFree(_In_ void* mem, _In_ AllocScenario scenario) noexcept - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(mem != NULL); - } - CONTRACTL_END; - - ::free(mem); - } - - HRESULT AddMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - HRESULT hr = S_OK; - BEGIN_EXTERNAL_ENTRYPOINT(&hr) - { - GCInterface::AddMemoryPressure(memoryInBytes); - } - END_EXTERNAL_ENTRYPOINT; - - return hr; - } - - HRESULT RemoveMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - HRESULT hr = S_OK; - BEGIN_EXTERNAL_ENTRYPOINT(&hr) - { - GCInterface::RemoveMemoryPressure(memoryInBytes); - } - END_EXTERNAL_ENTRYPOINT; - - return hr; - } - - HRESULT RequestGarbageCollectionForExternal(_In_ GcRequest req) noexcept - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - HRESULT hr = S_OK; - BEGIN_EXTERNAL_ENTRYPOINT(&hr) - { - GCX_COOP_THREAD_EXISTS(GET_THREAD()); - if (req == GcRequest::FullBlocking) - { - GCHeapUtilities::GetGCHeap()->GarbageCollect(2, true, collection_blocking | collection_optimized); - } - else - { - _ASSERTE(req == GcRequest::Default); - GCHeapUtilities::GetGCHeap()->GarbageCollect(); - } - } - END_EXTERNAL_ENTRYPOINT; - - return hr; - } - - HRESULT WaitForRuntimeFinalizerForExternal() noexcept - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - HRESULT hr = S_OK; - BEGIN_EXTERNAL_ENTRYPOINT(&hr) - { - FinalizerThread::FinalizerThreadWait(); - } - END_EXTERNAL_ENTRYPOINT; - - return hr; - } - - HRESULT ReleaseExternalObjectsFromCurrentThread() noexcept - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - } - CONTRACTL_END; - - HRESULT hr = S_OK; - BEGIN_EXTERNAL_ENTRYPOINT(&hr) - { - // Switch to cooperative mode so the cache can be queried. - GCX_COOP(); - - struct - { - OBJECTREF implRef; - OBJECTREF objsEnumRef; - } gc; - gc.implRef = NULL; // Use the globally registered implementation. - gc.objsEnumRef = NULL; - GCPROTECT_BEGIN(gc); - - // Pass the objects along to get released. - ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); - gc.objsEnumRef = cache->CreateManagedEnumerable( - ExternalObjectContext::Flags_ReferenceTracker, - GetCurrentCtxCookieWrapper()); - - CallReleaseObjects(&gc.implRef, &gc.objsEnumRef); - - GCPROTECT_END(); - } - END_EXTERNAL_ENTRYPOINT; - - return hr; - } - - void DeleteObjectInstanceHandle(_In_ InteropLib::OBJECTHANDLE handle) noexcept - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(handle != NULL); - } - CONTRACTL_END; - - DestroyHandleCommon(static_cast<::OBJECTHANDLE>(handle), InstanceHandleType); - } - - bool HasValidTarget(_In_ InteropLib::OBJECTHANDLE handle) noexcept - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(handle != NULL); - } - CONTRACTL_END; - - ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - - // A valid target is one that is not null. - bool isNotNull = ObjectHandleIsNull(objectHandle) == FALSE; - return isNotNull; - } - - bool GetGlobalPeggingState() noexcept - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - return (VolatileLoad(&g_isGlobalPeggingOn) != FALSE); - } - - void SetGlobalPeggingState(_In_ bool state) noexcept - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - BOOL newState = state ? TRUE : FALSE; - VolatileStore(&g_isGlobalPeggingOn, newState); - } - - HRESULT GetOrCreateTrackerTargetForExternal( - _In_ IUnknown* externalComObject, - _In_ CreateObjectFlags externalObjectFlags, - _In_ CreateComInterfaceFlags trackerTargetFlags, - _Outptr_ void** trackerTarget) noexcept - { - CONTRACTL - { - NOTHROW; - MODE_PREEMPTIVE; - PRECONDITION(externalComObject != NULL); - PRECONDITION(trackerTarget != NULL); - } - CONTRACTL_END; - - HRESULT hr = S_OK; - BEGIN_EXTERNAL_ENTRYPOINT(&hr) - { - // Switch to Cooperative mode since object references - // are being manipulated. - GCX_COOP(); - - struct - { - OBJECTREF implRef; - OBJECTREF wrapperMaybeRef; - OBJECTREF objRef; - } gc; - gc.implRef = NULL; // Use the globally registered implementation. - gc.wrapperMaybeRef = NULL; // No supplied wrapper here. - gc.objRef = NULL; - GCPROTECT_BEGIN(gc); - - // Get wrapper for external object - bool success = TryGetOrCreateObjectForComInstanceInternal( - gc.implRef, - g_trackerSupportGlobalInstanceId, - externalComObject, - NULL, - externalObjectFlags, - ComWrappersScenario::TrackerSupportGlobalInstance, - gc.wrapperMaybeRef, - &gc.objRef); - - if (!success) - COMPlusThrow(kArgumentNullException); - - // Get wrapper for managed object - success = TryGetOrCreateComInterfaceForObjectInternal( - gc.implRef, - g_trackerSupportGlobalInstanceId, - gc.objRef, - trackerTargetFlags, - ComWrappersScenario::TrackerSupportGlobalInstance, - trackerTarget); - - if (!success) - COMPlusThrow(kArgumentException); - - STRESS_LOG2(LF_INTEROP, LL_INFO100, "Created Target for External: 0x%p => 0x%p\n", OBJECTREFToObject(gc.objRef), *trackerTarget); - GCPROTECT_END(); - } - END_EXTERNAL_ENTRYPOINT; - - return hr; - } - - TryInvokeICustomQueryInterfaceResult TryInvokeICustomQueryInterface( - _In_ InteropLib::OBJECTHANDLE handle, - _In_ REFGUID iid, - _Outptr_result_maybenull_ void** obj) noexcept - { - CONTRACTL - { - NOTHROW; - MODE_ANY; - PRECONDITION(handle != NULL); - PRECONDITION(obj != NULL); - } - CONTRACTL_END; - - *obj = NULL; - - // If this is a GC thread, then someone is trying to query for something - // at a time when we can't run managed code. - if (IsGCThread()) - return TryInvokeICustomQueryInterfaceResult::OnGCThread; - - // Ideally the BEGIN_EXTERNAL_ENTRYPOINT/END_EXTERNAL_ENTRYPOINT pairs - // would be used here. However, this code path can be entered from within - // and from outside the runtime. - MAKE_CURRENT_THREAD_AVAILABLE_EX(GetThreadNULLOk()); - if (CURRENT_THREAD == NULL) - { - CURRENT_THREAD = SetupThreadNoThrow(); - - // If we failed to set up a new thread, we are going to indicate - // there was a general failure to invoke instead of failing fast. - if (CURRENT_THREAD == NULL) - return TryInvokeICustomQueryInterfaceResult::FailedToInvoke; - } - - HRESULT hr; - auto result = TryInvokeICustomQueryInterfaceResult::FailedToInvoke; - EX_TRY_THREAD(CURRENT_THREAD) - { - // Switch to Cooperative mode since object references - // are being manipulated. - GCX_COOP(); - - struct - { - OBJECTREF objRef; - } gc; - gc.objRef = NULL; - GCPROTECT_BEGIN(gc); - - // Get the target of the external object's reference. - ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - gc.objRef = ObjectFromHandle(objectHandle); - - result = (TryInvokeICustomQueryInterfaceResult)CallICustomQueryInterface(&gc.objRef, iid, obj); - - GCPROTECT_END(); - } - EX_CATCH_HRESULT(hr); - - // Assert valid value. - _ASSERTE(TryInvokeICustomQueryInterfaceResult::Min <= result - && result <= TryInvokeICustomQueryInterfaceResult::Max); - - return result; - } - - struct RuntimeCallContext - { - // Iterators for all known external objects. - ExtObjCxtCache::Iterator Curr; - ExtObjCxtCache::Iterator End; - - // Pointer to cache used to create object references. - ExtObjCxtRefCache* RefCache; - - RuntimeCallContext(_In_ ExtObjCxtCache* cache) - : Curr{ cache->_hashMap.Begin() } - , End{ cache->_hashMap.End() } - , RefCache{ cache->GetRefCache() } - { } - }; - - HRESULT IteratorNext( - _In_ RuntimeCallContext* runtimeContext, - _Outptr_result_maybenull_ void** extObjContext) noexcept - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(runtimeContext != NULL); - PRECONDITION(extObjContext != NULL); - - // Should only be called during a GC suspension - PRECONDITION(Debug_IsLockedViaThreadSuspension()); - } - CONTRACTL_END; - - if (runtimeContext->Curr == runtimeContext->End) - { - *extObjContext = NULL; - return S_FALSE; - } - - ExtObjCxtCache::Element e = *runtimeContext->Curr++; - *extObjContext = e; - return S_OK; - } - - HRESULT FoundReferencePath( - _In_ RuntimeCallContext* runtimeContext, - _In_ void* extObjContextRaw, - _In_ InteropLib::OBJECTHANDLE handle) noexcept + int CallICustomQueryInterface( + _In_ OBJECTREF* implPROTECTED, + _In_ REFGUID iid, + _Outptr_result_maybenull_ void** ppObject) { CONTRACTL { - NOTHROW; - GC_NOTRIGGER; + THROWS; MODE_COOPERATIVE; - PRECONDITION(runtimeContext != NULL); - PRECONDITION(extObjContextRaw != NULL); - PRECONDITION(handle != NULL); - - // Should only be called during a GC suspension - PRECONDITION(Debug_IsLockedViaThreadSuspension()); + PRECONDITION(implPROTECTED != NULL); + PRECONDITION(ppObject != NULL); } CONTRACTL_END; - // Get the external object's managed wrapper - ExternalObjectContext* extObjContext = static_cast(extObjContextRaw); - OBJECTREF source = extObjContext->GetObjectRef(); - - // Get the target of the external object's reference. - ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - OBJECTREF target = ObjectFromHandle(objectHandle); + int result; - // Return if the target has been collected or these are the same object. - if (target == NULL - || source->PassiveGetSyncBlock() == target->PassiveGetSyncBlock()) - { - return S_FALSE; - } + PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__CALL_ICUSTOMQUERYINTERFACE); + DECLARE_ARGHOLDER_ARRAY(args, 3); + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); + args[ARGNUM_1] = PTR_TO_ARGHOLDER(&iid); + args[ARGNUM_2] = PTR_TO_ARGHOLDER(ppObject); + CALL_MANAGED_METHOD(result, int, args); - STRESS_LOG2(LF_INTEROP, LL_INFO1000, "Found reference path: 0x%p => 0x%p\n", - OBJECTREFToObject(source), - OBJECTREFToObject(target)); - return runtimeContext->RefCache->AddReferenceFromObjectToObject(source, target); + return result; } + + BOOL g_isGlobalPeggingOn = TRUE; } -extern "C" BOOL QCALLTYPE ComWrappers_TryGetOrCreateComInterfaceForObject( - _In_ QCall::ObjectHandleOnStack comWrappersImpl, - _In_ INT64 wrapperId, - _In_ QCall::ObjectHandleOnStack instance, - _In_ INT32 flags, - _Outptr_ void** wrapper) +extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( + _Out_ void** fpQueryInterface, + _Out_ void** fpAddRef, + _Out_ void** fpRelease) { - QCALL_CONTRACT; + QCALL_CONTRACT_NO_GC_TRANSITION; - bool success = false; + _ASSERTE(fpQueryInterface != NULL); + _ASSERTE(fpAddRef != NULL); + _ASSERTE(fpRelease != NULL); - BEGIN_QCALL; + InteropLib::Com::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); +} - // Switch to Cooperative mode since object references - // are being manipulated. +void ComWrappersNative::MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe) +{ + CONTRACTL { - GCX_COOP(); - success = TryGetOrCreateComInterfaceForObjectInternal( - ObjectToOBJECTREF(*comWrappersImpl.m_ppObject), - wrapperId, - ObjectToOBJECTREF(*instance.m_ppObject), - (CreateComInterfaceFlags)flags, - ComWrappersScenario::Instance, - wrapper); + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(wrapperMaybe != NULL); } + CONTRACTL_END; - END_QCALL; - - return (success ? TRUE : FALSE); + { + GCX_PREEMP(); + // The IUnknown may or may not represent a wrapper, so E_INVALIDARG is okay here. + HRESULT hr = InteropLib::Com::MarkComActivated(wrapperMaybe); + _ASSERTE(SUCCEEDED(hr) || hr == E_INVALIDARG); + } } -extern "C" BOOL QCALLTYPE ComWrappers_TryGetOrCreateObjectForComInstance( - _In_ QCall::ObjectHandleOnStack comWrappersImpl, - _In_ INT64 wrapperId, - _In_ void* ext, - _In_opt_ void* innerMaybe, - _In_ INT32 flags, - _In_ QCall::ObjectHandleOnStack wrapperMaybe, - _Inout_ QCall::ObjectHandleOnStack retValue) +bool GlobalComWrappersForMarshalling::TryGetOrCreateComInterfaceForObject( + _In_ OBJECTREF instance, + _Outptr_ void** wrapperRaw) { - QCALL_CONTRACT; - - _ASSERTE(ext != NULL); - - bool success = false; - - BEGIN_QCALL; - - HRESULT hr; - IUnknown* externalComObject = reinterpret_cast(ext); - IUnknown* inner = reinterpret_cast(innerMaybe); - - // Determine the true identity and inner of the object - SafeComHolder identity; - hr = InteropLib::Com::DetermineIdentityAndInnerForExternal( - externalComObject, - (CreateObjectFlags)flags, - &identity, - &inner); - _ASSERTE(hr == S_OK); - - // Switch to Cooperative mode since object references - // are being manipulated. + CONTRACTL { - GCX_COOP(); - - OBJECTREF newObj; - success = TryGetOrCreateObjectForComInstanceInternal( - ObjectToOBJECTREF(*comWrappersImpl.m_ppObject), - wrapperId, - identity, - inner, - (CreateObjectFlags)flags, - ComWrappersScenario::Instance, - ObjectToOBJECTREF(*wrapperMaybe.m_ppObject), - &newObj); - - // Set the return value - if (success) - retValue.Set(newObj); + THROWS; + MODE_COOPERATIVE; } + CONTRACTL_END; - END_QCALL; - - return (success ? TRUE : FALSE); -} + void* wrapper; -extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( - _Out_ void** fpQueryInterface, - _Out_ void** fpAddRef, - _Out_ void** fpRelease) -{ - QCALL_CONTRACT; + GCPROTECT_BEGIN(instance); - _ASSERTE(fpQueryInterface != NULL); - _ASSERTE(fpAddRef != NULL); - _ASSERTE(fpRelease != NULL); + PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__GET_OR_CREATE_COM_INTERFACE_FOR_OBJECT_WITH_GLOBAL_MARSHALLING_INSTANCE); + DECLARE_ARGHOLDER_ARRAY(args, 1); + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(instance); + CALL_MANAGED_METHOD(wrapper, void*, args); - BEGIN_QCALL; + GCPROTECT_END(); - InteropLib::Com::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); + *wrapperRaw = wrapper; - END_QCALL; + return wrapper != nullptr; } -extern "C" BOOL QCALLTYPE ComWrappers_TryGetComInstance( - _In_ QCall::ObjectHandleOnStack wrapperMaybe, - _Out_ void** externalComObject) +bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( + _In_ IUnknown* externalComObject, + _In_ INT32 objFromComIPFlags, + _Out_ OBJECTREF* objRef) { - QCALL_CONTRACT; - - _ASSERTE(externalComObject != NULL); - - bool success = false; + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + } + CONTRACTL_END; - BEGIN_QCALL; + // TrackerObject support and unwrapping matches the built-in semantics that the global marshalling scenario mimics. + int flags = CreateObjectFlags::CreateObjectFlags_TrackerObject | CreateObjectFlags::CreateObjectFlags_Unwrap; + if ((objFromComIPFlags & ObjFromComIP::UNIQUE_OBJECT) != 0) + flags |= CreateObjectFlags::CreateObjectFlags_UniqueInstance; - // Switch to Cooperative mode since object references - // are being manipulated. - { - GCX_COOP(); + OBJECTREF obj = NULL; + GCPROTECT_BEGIN(obj); - SyncBlock* syncBlock = ObjectToOBJECTREF(*wrapperMaybe.m_ppObject)->PassiveGetSyncBlock(); - if (syncBlock != nullptr) - { - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); - if (interopInfo != nullptr) - { - void* contextMaybe; - if (interopInfo->TryGetExternalComObjectContext(&contextMaybe)) - { - ExternalObjectContext* context = reinterpret_cast(contextMaybe); - IUnknown* identity = reinterpret_cast(context->Identity); - GCX_PREEMP(); - success = SUCCEEDED(identity->QueryInterface(IID_IUnknown, externalComObject)); - } - } - } - } + PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__GET_OR_CREATE_OBJECT_FOR_COM_INSTANCE_WITH_GLOBAL_MARSHALLING_INSTANCE); + DECLARE_ARGHOLDER_ARRAY(args, 2); + args[ARGNUM_0] = PTR_TO_ARGHOLDER(externalComObject); + args[ARGNUM_1] = DWORD_TO_ARGHOLDER(flags); + CALL_MANAGED_METHOD_RETREF(obj, OBJECTREF, args); - END_QCALL; + GCPROTECT_END(); - return (success ? TRUE : FALSE); + *objRef = obj; + return obj != NULL; } -extern "C" BOOL QCALLTYPE ComWrappers_TryGetObject( - _In_ void* wrapperMaybe, - _Inout_ QCall::ObjectHandleOnStack instance) +extern "C" void* QCALLTYPE ComWrappers_AllocateRefCountedHandle(_In_ QCall::ObjectHandleOnStack obj) { QCALL_CONTRACT; - _ASSERTE(wrapperMaybe != NULL); - - bool success = false; + void* handle = NULL; BEGIN_QCALL; - // Determine the true identity of the object - SafeComHolder identity; - HRESULT hr = ((IUnknown*)wrapperMaybe)->QueryInterface(IID_IUnknown, &identity); - _ASSERTE(hr == S_OK); - - InteropLib::OBJECTHANDLE handle; - if (InteropLib::Com::GetObjectForWrapper(identity, &handle) == S_OK) { - // Switch to Cooperative mode since object references - // are being manipulated. GCX_COOP(); - // We have an object handle from the COM instance which is a CCW. - ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - instance.Set(ObjectFromHandle(objectHandle)); - success = true; + handle = GetAppDomain()->CreateTypedHandle(obj.Get(), HNDTYPE_REFCOUNTED); } END_QCALL; - return (success ? TRUE : FALSE); + return handle; } -void ComWrappersNative::DestroyManagedObjectComWrapper(_In_ void* wrapper) +extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl() { - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(wrapper != NULL); - } - CONTRACTL_END; + QCALL_CONTRACT_NO_GC_TRANSITION; - STRESS_LOG1(LF_INTEROP, LL_INFO100, "Destroying MOW: 0x%p\n", wrapper); + void const* vftbl = NULL; - { - GCX_PREEMP(); - InteropLib::Com::DestroyWrapperForObject(wrapper); - } + return InteropLib::Com::GetIReferenceTrackerTargetVftbl(); } -void ComWrappersNative::DestroyExternalComObjectContext(_In_ void* contextRaw) +extern "C" void const* QCALLTYPE ComWrappers_GetTaggedImpl() { - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(contextRaw != NULL); - } - CONTRACTL_END; + QCALL_CONTRACT_NO_GC_TRANSITION; -#ifdef _DEBUG - ExternalObjectContext* context = static_cast(contextRaw); - _ASSERTE(!context->IsActive()); -#endif + return InteropLib::Com::GetTaggedCurrentVersionImpl(); +} - STRESS_LOG1(LF_INTEROP, LL_INFO100, "Destroying EOC: 0x%p\n", contextRaw); +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager() +{ + QCALL_CONTRACT_NO_GC_TRANSITION; - { - GCX_PREEMP(); - InteropLib::Com::DestroyWrapperForExternal(contextRaw); - } + return InteropLib::Com::HasReferenceTrackerManager(); } -void ComWrappersNative::MarkExternalComObjectContextCollected(_In_ void* contextRaw) +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager) { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(contextRaw != NULL); - PRECONDITION(GCHeapUtilities::IsGCInProgress()); - } - CONTRACTL_END; - - ExternalObjectContext* context = static_cast(contextRaw); - _ASSERTE(context->IsActive()); - context->MarkCollected(); + QCALL_CONTRACT_NO_GC_TRANSITION; - bool inCache = context->IsSet(ExternalObjectContext::Flags_InCache); - STRESS_LOG2(LF_INTEROP, LL_INFO100, "Mark Collected EOC (In Cache: %d): 0x%p\n", (int)inCache, contextRaw); + return InteropLib::Com::TryRegisterReferenceTrackerManager(manager); +} - // Verify the caller didn't ignore the cache during creation. - if (inCache) - { - ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); - cache->Remove(context); - } +OBJECTHANDLE GCHandleSetObject::Iterator::Current() const +{ + LIMITED_METHOD_CONTRACT; + return _currentEntry->_value; } -void ComWrappersNative::MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe) +bool GCHandleSetObject::Iterator::MoveNext() { CONTRACTL { - THROWS; - GC_TRIGGERS; + NOTHROW; + GC_NOTRIGGER; MODE_COOPERATIVE; - PRECONDITION(wrapperMaybe != NULL); + + // Should only be called during a GC suspension + PRECONDITION(Debug_IsLockedViaThreadSuspension()); } CONTRACTL_END; + if (_currentEntry != NULL) { - GCX_PREEMP(); - // The IUnknown may or may not represent a wrapper, so E_INVALIDARG is okay here. - HRESULT hr = InteropLib::Com::MarkComActivated(wrapperMaybe); - _ASSERTE(SUCCEEDED(hr) || hr == E_INVALIDARG); + _currentEntry = _currentEntry->_next; } -} -extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForMarshalling(INT64 id) -{ - QCALL_CONTRACT_NO_GC_TRANSITION; + if (_currentEntry == NULL) + { + // Certain buckets might be empty, so loop until we find + // one with an entry. + while (++_currentIndex != (int32_t)_buckets->GetNumComponents()) + { + _currentEntry = (HANDLESETENTRYREF)_buckets->GetAt(_currentIndex); + if (_currentEntry != NULL) + { + return true; + } + } - _ASSERTE(g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId && id != ComWrappersNative::InvalidWrapperId); - g_marshallingGlobalInstanceId = id; -} + return false; + } -bool GlobalComWrappersForMarshalling::IsRegisteredInstance(INT64 id) -{ - return g_marshallingGlobalInstanceId != ComWrappersNative::InvalidWrapperId - && g_marshallingGlobalInstanceId == id; + return true; } -bool GlobalComWrappersForMarshalling::TryGetOrCreateComInterfaceForObject( - _In_ OBJECTREF instance, - _Outptr_ void** wrapperRaw) +namespace InteropLibImports { - CONTRACTL - { - THROWS; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; - - // Switch to Cooperative mode since object references - // are being manipulated. + bool HasValidTarget(_In_ InteropLib::OBJECTHANDLE handle) noexcept { - GCX_COOP(); + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(handle != NULL); + } + CONTRACTL_END; - CreateComInterfaceFlags flags = CreateComInterfaceFlags::CreateComInterfaceFlags_TrackerSupport; + ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - // Passing NULL as the ComWrappers implementation indicates using the globally registered instance - return TryGetOrCreateComInterfaceForObjectInternal( - NULL, - g_marshallingGlobalInstanceId, - instance, - flags, - ComWrappersScenario::MarshallingGlobalInstance, - wrapperRaw); + // A valid target is one that is not null. + bool isNotNull = ObjectHandleIsNull(objectHandle) == FALSE; + return isNotNull; } -} -bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( - _In_ IUnknown* externalComObject, - _In_ INT32 objFromComIPFlags, - _Out_ OBJECTREF* objRef) -{ - CONTRACTL + void DestroyHandle(_In_ InteropLib::OBJECTHANDLE handle) noexcept { - THROWS; - MODE_COOPERATIVE; - } - CONTRACTL_END; + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(handle != NULL); + } + CONTRACTL_END; + ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; + DestroyRefcountedHandle(objectHandle); + } - // Determine the true identity of the object - SafeComHolder identity; + bool GetGlobalPeggingState() noexcept { - GCX_PREEMP(); + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; - HRESULT hr = externalComObject->QueryInterface(IID_IUnknown, &identity); - _ASSERTE(hr == S_OK); + return (VolatileLoad(&g_isGlobalPeggingOn) != FALSE); } - // Switch to Cooperative mode since object references - // are being manipulated. + bool IsObjectPromoted(_In_ InteropLib::OBJECTHANDLE handle) noexcept { - GCX_COOP(); + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; - // TrackerObject support and unwrapping matches the built-in semantics that the global marshalling scenario mimics. - int flags = CreateObjectFlags::CreateObjectFlags_TrackerObject | CreateObjectFlags::CreateObjectFlags_Unwrap; - if ((objFromComIPFlags & ObjFromComIP::UNIQUE_OBJECT) != 0) - flags |= CreateObjectFlags::CreateObjectFlags_UniqueInstance; - - // Passing NULL as the ComWrappers implementation indicates using the globally registered instance - return TryGetOrCreateObjectForComInstanceInternal( - NULL /*comWrappersImpl*/, - g_marshallingGlobalInstanceId, - identity, - NULL, - (CreateObjectFlags)flags, - ComWrappersScenario::MarshallingGlobalInstance, - NULL /*wrapperMaybe*/, - objRef); - } -} + if (handle == nullptr) + return false; -extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForTrackerSupport(INT64 id) -{ - QCALL_CONTRACT_NO_GC_TRANSITION; + ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - _ASSERTE(g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId && id != ComWrappersNative::InvalidWrapperId); - g_trackerSupportGlobalInstanceId = id; -} + OBJECTREF obj = ObjectFromHandle(objectHandle); -bool GlobalComWrappersForTrackerSupport::IsRegisteredInstance(INT64 id) -{ - return g_trackerSupportGlobalInstanceId != ComWrappersNative::InvalidWrapperId - && g_trackerSupportGlobalInstanceId == id; -} + if (obj == nullptr) + return false; -bool GlobalComWrappersForTrackerSupport::TryGetOrCreateComInterfaceForObject( - _In_ OBJECTREF instance, - _Outptr_ void** wrapperRaw) -{ - CONTRACTL - { - THROWS; - MODE_COOPERATIVE; + return GCHeapUtilities::GetGCHeap()->IsPromoted(OBJECTREFToObject(obj)); } - CONTRACTL_END; - if (g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; + void SetGlobalPeggingState(_In_ bool state) noexcept + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; - // Passing NULL as the ComWrappers implementation indicates using the globally registered instance - return TryGetOrCreateComInterfaceForObjectInternal( - NULL, - g_trackerSupportGlobalInstanceId, - instance, - CreateComInterfaceFlags::CreateComInterfaceFlags_TrackerSupport, - ComWrappersScenario::TrackerSupportGlobalInstance, - wrapperRaw); -} + BOOL newState = state ? TRUE : FALSE; + VolatileStore(&g_isGlobalPeggingOn, newState); + } -bool GlobalComWrappersForTrackerSupport::TryGetOrCreateObjectForComInstance( - _In_ IUnknown* externalComObject, - _Out_ OBJECTREF* objRef) -{ - CONTRACTL + TryInvokeICustomQueryInterfaceResult TryInvokeICustomQueryInterface( + _In_ InteropLib::OBJECTHANDLE handle, + _In_ REFGUID iid, + _Outptr_result_maybenull_ void** obj) noexcept { - THROWS; - MODE_COOPERATIVE; - } - CONTRACTL_END; + CONTRACTL + { + NOTHROW; + MODE_ANY; + PRECONDITION(handle != NULL); + PRECONDITION(obj != NULL); + } + CONTRACTL_END; - if (g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; + *obj = NULL; - // Determine the true identity of the object - SafeComHolder identity; - { - GCX_PREEMP(); + // If this is a GC thread, then someone is trying to query for something + // at a time when we can't run managed code. + if (IsGCThread()) + return TryInvokeICustomQueryInterfaceResult::OnGCThread; - HRESULT hr = externalComObject->QueryInterface(IID_IUnknown, &identity); - _ASSERTE(hr == S_OK); - } + // Ideally the BEGIN_EXTERNAL_ENTRYPOINT/END_EXTERNAL_ENTRYPOINT pairs + // would be used here. However, this code path can be entered from within + // and from outside the runtime. + MAKE_CURRENT_THREAD_AVAILABLE_EX(GetThreadNULLOk()); + if (CURRENT_THREAD == NULL) + { + CURRENT_THREAD = SetupThreadNoThrow(); - // Passing NULL as the ComWrappers implementation indicates using the globally registered instance - return TryGetOrCreateObjectForComInstanceInternal( - NULL /*comWrappersImpl*/, - g_trackerSupportGlobalInstanceId, - identity, - NULL, - CreateObjectFlags::CreateObjectFlags_TrackerObject, - ComWrappersScenario::TrackerSupportGlobalInstance, - NULL /*wrapperMaybe*/, - objRef); -} + // If we failed to set up a new thread, we are going to indicate + // there was a general failure to invoke instead of failing fast. + if (CURRENT_THREAD == NULL) + return TryInvokeICustomQueryInterfaceResult::FailedToInvoke; + } -IUnknown* ComWrappersNative::GetIdentityForObject(_In_ OBJECTREF* objectPROTECTED, _In_ REFIID riid, _Out_ INT64* wrapperId, _Out_ bool* isAggregated) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(objectPROTECTED)); - PRECONDITION(CheckPointer(wrapperId)); - } - CONTRACTL_END; + HRESULT hr; + auto result = TryInvokeICustomQueryInterfaceResult::FailedToInvoke; + EX_TRY_THREAD(CURRENT_THREAD) + { + // Switch to Cooperative mode since object references + // are being manipulated. + GCX_COOP(); + + struct + { + OBJECTREF objRef; + } gc; + gc.objRef = NULL; + GCPROTECT_BEGIN(gc); + + // Get the target of the external object's reference. + ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); + gc.objRef = ObjectFromHandle(objectHandle); - ASSERT_PROTECTED(objectPROTECTED); + result = (TryInvokeICustomQueryInterfaceResult)CallICustomQueryInterface(&gc.objRef, iid, obj); - *wrapperId = ComWrappersNative::InvalidWrapperId; + GCPROTECT_END(); + } + EX_CATCH_HRESULT(hr); - SyncBlock* syncBlock = (*objectPROTECTED)->PassiveGetSyncBlock(); - if (syncBlock == nullptr) - { - return nullptr; + // Assert valid value. + _ASSERTE(TryInvokeICustomQueryInterfaceResult::Min <= result + && result <= TryInvokeICustomQueryInterfaceResult::Max); + + return result; } - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); - if (interopInfo == nullptr) + struct RuntimeCallContext { - return nullptr; - } + RCWRefCache* RefCache; + GCHandleSetObject::Iterator _iterator; + }; - void* contextMaybe; - if (interopInfo->TryGetExternalComObjectContext(&contextMaybe)) + bool IteratorNext( + _In_ RuntimeCallContext* runtimeContext, + _Outptr_result_maybenull_ void** referenceTracker, + _Outptr_result_maybenull_ InteropLib::OBJECTHANDLE* proxyHandle) noexcept { - ExternalObjectContext* context = reinterpret_cast(contextMaybe); - *wrapperId = context->WrapperId; - *isAggregated = context->IsSet(ExternalObjectContext::Flags_Aggregated); + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(runtimeContext != NULL); + PRECONDITION(referenceTracker != NULL); + PRECONDITION(proxyHandle != NULL); - IUnknown* identity = reinterpret_cast(context->Identity); - GCX_PREEMP(); - IUnknown* result; - if (SUCCEEDED(identity->QueryInterface(riid, (void**)&result))) + // Should only be called during a GC suspension + PRECONDITION(Debug_IsLockedViaThreadSuspension()); + } + CONTRACTL_END; + + if (!runtimeContext->_iterator.MoveNext()) + { + *referenceTracker = NULL; + *proxyHandle = NULL; + return false; + } + OBJECTHANDLE nativeObjectWrapperHandle = runtimeContext->_iterator.Current(); + REFTRACKEROBJECTWRAPPERREF nativeObjectWrapper = (REFTRACKEROBJECTWRAPPERREF)ObjectFromHandle(nativeObjectWrapperHandle); + + if (nativeObjectWrapper == NULL) { - return result; + *referenceTracker = NULL; + *proxyHandle = NULL; + return true; } + + *referenceTracker = dac_cast(nativeObjectWrapper->GetTrackerObject()); + *proxyHandle = static_cast(nativeObjectWrapper->GetProxyHandle()); + return true; } - return nullptr; -} -namespace -{ - struct CallbackContext - { - bool HasWrapper; - bool IsRooted; - }; - bool IsWrapperRootedCallback(_In_ void* mocw, _In_ void* cxtRaw) + HRESULT FoundReferencePath( + _In_ RuntimeCallContext* runtimeContext, + _In_ InteropLib::OBJECTHANDLE sourceHandle, + _In_ InteropLib::OBJECTHANDLE targetHandle) noexcept { CONTRACTL { NOTHROW; GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(mocw != NULL); - PRECONDITION(cxtRaw != NULL); + MODE_COOPERATIVE; + PRECONDITION(runtimeContext != NULL); + PRECONDITION(sourceHandle != NULL); + PRECONDITION(targetHandle != NULL); + + // Should only be called during a GC suspension + PRECONDITION(Debug_IsLockedViaThreadSuspension()); } CONTRACTL_END; - auto cxt = static_cast(cxtRaw); - cxt->HasWrapper = true; + // Get the external object's managed wrapper + ::OBJECTHANDLE srcHandle = static_cast<::OBJECTHANDLE>(targetHandle); + OBJECTREF source = ObjectFromHandle(srcHandle); - IUnknown* wrapper = static_cast(mocw); - cxt->IsRooted = (InteropLib::Com::IsWrapperRooted(wrapper) == S_OK); + // Get the target of the external object's reference. + ::OBJECTHANDLE tgtHandle = static_cast<::OBJECTHANDLE>(targetHandle); + OBJECTREF target = ObjectFromHandle(tgtHandle ); - // If we find a single rooted wrapper then the managed object - // is considered rooted and we can stop enumerating. - if (cxt->IsRooted) - return false; + // Return if the target has been collected or these are the same object. + if (target == NULL + || source->PassiveGetSyncBlock() == target->PassiveGetSyncBlock()) + { + return S_FALSE; + } - return true; + STRESS_LOG2(LF_INTEROP, LL_INFO1000, "Found reference path: 0x%p => 0x%p\n", + OBJECTREFToObject(source), + OBJECTREFToObject(target)); + return runtimeContext->RefCache->AddReferenceFromObjectToObject(source, target); } } -bool ComWrappersNative::HasManagedObjectComWrapper(_In_ OBJECTREF object, _Out_ bool* isRooted) +namespace +{ + MethodTable* s_pManagedObjectWrapperHolderMT = NULL; +} + +bool ComWrappersNative::IsManagedObjectComWrapper(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted) { CONTRACTL { NOTHROW; GC_NOTRIGGER; - PRECONDITION(CheckPointer(isRooted)); + MODE_ANY; } CONTRACTL_END; - *isRooted = false; - SyncBlock* syncBlock = object->PassiveGetSyncBlock(); - if (syncBlock == nullptr) + if (managedObjectWrapperHolderRef->GetGCSafeMethodTable() != s_pManagedObjectWrapperHolderMT ) + { return false; + } - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); - if (interopInfo == nullptr) - return false; + MOWHOLDERREF holder = (MOWHOLDERREF)managedObjectWrapperHolderRef; - CallbackContext cxt{}; - interopInfo->EnumManagedObjectComWrappers(&IsWrapperRootedCallback, &cxt); + *pIsRooted = InteropLib::Com::IsRooted(holder->ManagedObjectWrapper); + + return true; +} - *isRooted = cxt.IsRooted; - return cxt.HasWrapper; +namespace +{ + OBJECTHANDLE NativeObjectWrapperCacheHandle = NULL; + RCWRefCache* pAppDomainRCWRefCache = NULL; } void ComWrappersNative::OnFullGCStarted() @@ -2015,27 +512,31 @@ void ComWrappersNative::OnFullGCStarted() CONTRACTL { NOTHROW; + MODE_COOPERATIVE; GC_NOTRIGGER; } CONTRACTL_END; // If no cache exists, then there is nothing to do here. - ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); - if (cache != NULL) - { - STRESS_LOG0(LF_INTEROP, LL_INFO10000, "Begin Reference Tracking\n"); - ExtObjCxtRefCache* refCache = cache->GetRefCache(); + if (NativeObjectWrapperCacheHandle == NULL) + return; - // Reset the ref cache - refCache->ResetDependentHandles(); + HANDLESETREF handleSet = (HANDLESETREF)ObjectFromHandle(NativeObjectWrapperCacheHandle); + if (handleSet == NULL) + return; - // Create a call context for the InteropLib. - InteropLibImports::RuntimeCallContext cxt(cache); - (void)InteropLib::Com::BeginExternalObjectReferenceTracking(&cxt); + STRESS_LOG0(LF_INTEROP, LL_INFO10000, "Begin Reference Tracking\n"); + RCWRefCache* refCache = pAppDomainRCWRefCache; - // Shrink cache and clear unused handles. - refCache->ShrinkDependentHandles(); - } + // Reset the ref cache + refCache->ResetDependentHandles(); + + // Create a call context for the InteropLib. + InteropLibImports::RuntimeCallContext cxt{refCache, GCHandleSetObject::Iterator{ handleSet }}; + (void)InteropLib::Com::BeginExternalObjectReferenceTracking(&cxt); + + // Shrink cache and clear unused handles. + refCache->ShrinkDependentHandles(); } void ComWrappersNative::OnFullGCFinished() @@ -2043,31 +544,74 @@ void ComWrappersNative::OnFullGCFinished() CONTRACTL { NOTHROW; + MODE_COOPERATIVE; GC_NOTRIGGER; } CONTRACTL_END; - ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); - if (cache != NULL) - { - (void)InteropLib::Com::EndExternalObjectReferenceTracking(); - STRESS_LOG0(LF_INTEROP, LL_INFO10000, "End Reference Tracking\n"); - } + if (NativeObjectWrapperCacheHandle == NULL || ObjectFromHandle(NativeObjectWrapperCacheHandle) == NULL) + return; + + (void)InteropLib::Com::EndExternalObjectReferenceTracking(); + STRESS_LOG0(LF_INTEROP, LL_INFO10000, "End Reference Tracking\n"); } -void ComWrappersNative::AfterRefCountedHandleCallbacks() +void ComWrappersNative::OnAfterGCScanRoots() { CONTRACTL { NOTHROW; + MODE_COOPERATIVE; GC_NOTRIGGER; - MODE_ANY; } CONTRACTL_END; - ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); - if (cache != NULL) - cache->DetachNotPromotedEOCs(); + if (NativeObjectWrapperCacheHandle == NULL) + return; + + HANDLESETREF handleSet = (HANDLESETREF)ObjectFromHandle(NativeObjectWrapperCacheHandle); + if (handleSet == NULL) + return; + + STRESS_LOG0(LF_INTEROP, LL_INFO10000, "Detach Non-promoted object from the Reference Tracker\n"); + + // Create a call context for the InteropLib. + InteropLibImports::RuntimeCallContext cxt{pAppDomainRCWRefCache, GCHandleSetObject::Iterator{ handleSet }}; + + (void)InteropLib::Com::DetachNonPromotedObjects(&cxt); +} + +extern "C" void QCALLTYPE ComWrappers_RegisterIsRootedCallback() +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + // Grab the method table for the objects we inspect in our ref-counted handle callback. + s_pManagedObjectWrapperHolderMT = CoreLibBinder::GetClass(CLASS__MANAGED_OBJECT_WRAPPER_HOLDER); + + END_QCALL; +} + +extern "C" void QCALLTYPE TrackerObjectManager_RegisterNativeObjectWrapperCache(_In_ QCall::ObjectHandleOnStack cache) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + GCX_COOP(); + NativeObjectWrapperCacheHandle = GetAppDomain()->CreateHandle(cache.Get()); + // Fetch the RCWRefCache here so we don't try to allocate it during GC. + pAppDomainRCWRefCache = GetAppDomain()->GetRCWRefCache(); + + END_QCALL; +} + +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled() +{ + QCALL_CONTRACT_NO_GC_TRANSITION; + + return InteropLibImports::GetGlobalPeggingState(); } #endif // FEATURE_COMWRAPPERS diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h new file mode 100644 index 00000000000000..4d534b33a247b5 --- /dev/null +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -0,0 +1,165 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// +// Interface between the VM and Interop library. +// + +#ifndef _INTEROPLIBINTERFACE_COMWRAPPERS_H_ +#define _INTEROPLIBINTERFACE_COMWRAPPERS_H_ + +#include + +// Native calls for the managed ComWrappers API +class ComWrappersNative +{ +public: // COM activation + static void MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe); + +public: // Unwrapping support + static bool IsManagedObjectComWrapper(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted); + +public: // GC interaction + static void OnFullGCStarted(); + static void OnFullGCFinished(); + static void OnAfterGCScanRoots(); +}; + +// Native QCalls for the abstract ComWrappers managed type. +extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( + _Out_ void** fpQueryInterface, + _Out_ void** fpAddRef, + _Out_ void** fpRelease); + +extern "C" void* QCALLTYPE ComWrappers_AllocateRefCountedHandle(_In_ QCall::ObjectHandleOnStack obj); + +extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl(); + +extern "C" void const* QCALLTYPE ComWrappers_GetTaggedImpl(); + +extern "C" void QCALLTYPE ComWrappers_RegisterIsRootedCallback(); + +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager(); + +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager); + +extern "C" void QCALLTYPE TrackerObjectManager_RegisterNativeObjectWrapperCache(_In_ QCall::ObjectHandleOnStack cache); + +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled(); + +class GlobalComWrappersForMarshalling +{ +public: + static bool TryGetOrCreateComInterfaceForObject( + _In_ OBJECTREF instance, + _Outptr_ void** wrapperRaw); + + static bool TryGetOrCreateObjectForComInstance( + _In_ IUnknown* externalComObject, + _In_ INT32 objFromComIPFlags, + _Out_ OBJECTREF* objRef); +}; + +// Define "manually managed" definitions of the ComWrappers types +// that are used in diagnostics and during GC. +class GCHandleSetObject; +class GCHandleSetEntryObject; + +class ManagedObjectWrapperHolderObject : public Object +{ + friend class CoreLibBinder; + friend class ClrDataAccess; +private: + OBJECTREF _releaser; + OBJECTREF _wrappedObject; +public: + DPTR(InteropLib::ABI::ManagedObjectWrapperLayout) ManagedObjectWrapper; +}; + +class NativeObjectWrapperObject : public Object +{ + friend class CoreLibBinder; + OBJECTREF _comWrappers; + TADDR _externalComObject; + TADDR _inner; + CLR_BOOL _aggregatedManagedObjectWrapper; + CLR_BOOL _uniqueInstance; + OBJECTHANDLE _proxyHandle; + OBJECTHANDLE _proxyHandleTrackingResurrection; +public: + OBJECTHANDLE GetProxyHandle() const + { + return _proxyHandle; + } + + TADDR GetExternalComObject() const + { + return _externalComObject; + } +}; + +class ReferenceTrackerNativeObjectWrapperObject final : public NativeObjectWrapperObject +{ + friend class CoreLibBinder; + TADDR _trackerObject; + TADDR _contextToken; + int _trackerObjectDisconnected; + CLR_BOOL _releaseTrackerObject; + OBJECTHANDLE _nativeObjectWrapperWeakHandle; +public: + TADDR GetTrackerObject() const + { + return (_trackerObject == (TADDR)nullptr || _trackerObjectDisconnected == 1) ? (TADDR)nullptr : _trackerObject; + } +}; + +#ifdef USE_CHECKED_OBJECTREFS +using MOWHOLDERREF = REF; +using NATIVEOBJECTWRAPPERREF = REF; +using REFTRACKEROBJECTWRAPPERREF = REF; +using HANDLESETENTRYREF = REF; +using HANDLESETREF = REF; +#else +using MOWHOLDERREF = DPTR(ManagedObjectWrapperHolderObject); +using NATIVEOBJECTWRAPPERREF = DPTR(NativeObjectWrapperObject); +using REFTRACKEROBJECTWRAPPERREF = DPTR(ReferenceTrackerNativeObjectWrapperObject); +using HANDLESETENTRYREF = DPTR(GCHandleSetEntryObject); +using HANDLESETREF = DPTR(GCHandleSetObject); +#endif + +class GCHandleSetEntryObject final : public Object +{ + friend class CoreLibBinder; + public: + HANDLESETENTRYREF _next; + OBJECTHANDLE _value; +}; + +class GCHandleSetObject final : public Object +{ + friend class CoreLibBinder; +private: + PTRARRAYREF _buckets; + +public: + class Iterator final + { + PTRARRAYREF _buckets; + HANDLESETENTRYREF _currentEntry; + int32_t _currentIndex; + public: + Iterator(HANDLESETREF obj) + : _buckets(obj->_buckets) + , _currentEntry(nullptr) + , _currentIndex(-1) + { + LIMITED_METHOD_CONTRACT; + } + + OBJECTHANDLE Current() const; + + bool MoveNext(); + }; +}; + +#endif // _INTEROPLIBINTERFACE_COMWRAPPERS_H_ diff --git a/src/coreclr/vm/interoplibinterface_shared.cpp b/src/coreclr/vm/interoplibinterface_shared.cpp index f31b128e47cfb1..2fc5d604ba394c 100644 --- a/src/coreclr/vm/interoplibinterface_shared.cpp +++ b/src/coreclr/vm/interoplibinterface_shared.cpp @@ -145,7 +145,7 @@ void Interop::OnAfterGCScanRoots(_In_ bool isConcurrent) CONTRACTL_END; #ifdef FEATURE_COMWRAPPERS - ComWrappersNative::AfterRefCountedHandleCallbacks(); + ComWrappersNative::OnAfterGCScanRoots(); #endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL diff --git a/src/coreclr/vm/interoputil.cpp b/src/coreclr/vm/interoputil.cpp index 2484638e3c85d0..d2ef66159a2425 100644 --- a/src/coreclr/vm/interoputil.cpp +++ b/src/coreclr/vm/interoputil.cpp @@ -1174,7 +1174,7 @@ HRESULT SafeQueryInterfacePreemp(IUnknown* pUnk, REFIID riid, IUnknown** pResUnk } #include -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) +#if defined(FEATURE_COMINTEROP) //-------------------------------------------------------------------------------- // Cleanup helpers @@ -1197,12 +1197,6 @@ void MinorCleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo) if (pRCW) pRCW->MinorCleanup(); #endif // FEATURE_COMINTEROP - -#ifdef FEATURE_COMWRAPPERS - void* eoc; - if (pInteropInfo->TryGetExternalComObjectContext(&eoc)) - ComWrappersNative::MarkExternalComObjectContextCollected(eoc); -#endif // FEATURE_COMWRAPPERS } void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo) @@ -1245,20 +1239,9 @@ void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo) pCCW->Cleanup(); } #endif // FEATURE_COMINTEROP - -#ifdef FEATURE_COMWRAPPERS - pInteropInfo->ClearManagedObjectComWrappers(&ComWrappersNative::DestroyManagedObjectComWrapper); - - void* eoc; - if (pInteropInfo->TryGetExternalComObjectContext(&eoc)) - { - (void)pInteropInfo->TrySetExternalComObjectContext(NULL, eoc); - ComWrappersNative::DestroyExternalComObjectContext(eoc); - } -#endif // FEATURE_COMWRAPPERS } -#endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS +#endif // FEATURE_COMINTEROP #ifdef FEATURE_COMINTEROP @@ -2497,7 +2480,7 @@ BOOL IsMethodVisibleFromCom(MethodDesc *pMD) // TODO: (async) revisit and examine if this needs to be supported somehow if (pMD->IsAsyncMethod()) return false; - + mdMethodDef md = pMD->GetMemberDef(); // See if there is property information for this member. diff --git a/src/coreclr/vm/interoputil.h b/src/coreclr/vm/interoputil.h index 6f58d44d663fce..89026337b1cdd6 100644 --- a/src/coreclr/vm/interoputil.h +++ b/src/coreclr/vm/interoputil.h @@ -153,24 +153,16 @@ HRESULT LoadRegTypeLib(_In_ REFGUID guid, // Called from EEStartup, to initialize com Interop specific data structures. void InitializeComInterop(); -#endif // FEATURE_COMINTEROP - //-------------------------------------------------------------------------------- // Clean up Helpers //-------------------------------------------------------------------------------- -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) - // called by syncblock, on the finalizer thread to do major cleanup void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo); // called by syncblock, during GC, do only minimal work void MinorCleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo); -#endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS) - -#ifdef FEATURE_COMINTEROP - // A wrapper that catches all exceptions - used in the OnThreadTerminate case. void ReleaseRCWsInCachesNoThrow(LPVOID pCtxCookie); diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 840cd137c94e96..d49bb894c15543 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -193,10 +193,8 @@ DEFINE_METASIG(SM(Obj_IntPtr_RefIntPtr_RefBool_RetIntPtr, j I r(I) r(F), I)) DEFINE_METASIG(SM(Obj_IntPtr_RefIntPtr_RetIntPtr, j I r(I), I)) #endif // FEATURE_COMINTEROP #ifdef FEATURE_COMWRAPPERS -DEFINE_METASIG_T(SM(Scenario_ComWrappers_Obj_CreateFlags_RefInt_RetPtrVoid, g(COMWRAPPERSSCENARIO) C(COMWRAPPERS) j g(CREATECOMINTERFACEFLAGS) r(i), P(v))) -DEFINE_METASIG_T(SM(Scenario_ComWrappers_IntPtr_CreateFlags_RetObj, g(COMWRAPPERSSCENARIO) C(COMWRAPPERS) I g(CREATEOBJECTFLAGS), j)) -DEFINE_METASIG_T(SM(ComWrappers_IEnumerable_RetVoid, C(COMWRAPPERS) C(IENUMERABLE), v)) -DEFINE_METASIG_T(SM(Obj_RefGuid_RefIntPtr_RetInt, j r(g(GUID)) r(I), i)) +DEFINE_METASIG_T(SM(IntPtr_CreateObjectFlags_RetObj, I g(CREATEOBJECTFLAGS), j)) +DEFINE_METASIG_T(SM(ManagedObjectWrapperHolder_RefGuid_RefIntPtr_RetInt, C(MANAGED_OBJECT_WRAPPER_HOLDER) r(g(GUID)) r(I), i)) #endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL DEFINE_METASIG_T(SM(Exception_Obj_RefIntPtr_RetVoidPtr, C(EXCEPTION) j r(I), P(v))) diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 663a4161f83917..d467499594101d 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -264,6 +264,7 @@ class Object static DWORD ComputeHashCode(); static DWORD GetGlobalNewHashCode(); + inline INT32 TryGetHashCode(); #ifndef DACCESS_COMPILE INT32 GetHashCodeEx(); #endif // #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/object.inl b/src/coreclr/vm/object.inl index 9b11f554dcf8a6..467ec47a1b3be4 100644 --- a/src/coreclr/vm/object.inl +++ b/src/coreclr/vm/object.inl @@ -60,6 +60,31 @@ __forceinline /*static*/ SIZE_T StringObject::GetSize(DWORD strLen) return GetBaseSize() + strLen * sizeof(WCHAR); } +inline INT32 Object::TryGetHashCode() +{ + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + DWORD bits = GetHeader()->GetBits(); + if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX) + { + if (bits & BIT_SBLK_IS_HASHCODE) + { + // Common case: the object already has a hash code + return bits & MASK_HASHCODE; + } + else + { + // We have a sync block index. This means if we already have a hash code, + // it is in the sync block, otherwise we will return 0, which means "not set". + SyncBlock *psb = PassiveGetSyncBlock(); + if (psb != NULL) + return psb->GetHashCode(); + } + } + return 0; +} + #ifdef DACCESS_COMPILE inline void Object::EnumMemoryRegions(void) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 19ce7a5ffa05ab..a45e3d69a249d7 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -424,12 +424,14 @@ static const Entry s_QCall[] = DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo) #if defined(FEATURE_COMWRAPPERS) DllImportEntry(ComWrappers_GetIUnknownImpl) - DllImportEntry(ComWrappers_TryGetComInstance) - DllImportEntry(ComWrappers_TryGetObject) - DllImportEntry(ComWrappers_TryGetOrCreateComInterfaceForObject) - DllImportEntry(ComWrappers_TryGetOrCreateObjectForComInstance) - DllImportEntry(ComWrappers_SetGlobalInstanceRegisteredForMarshalling) - DllImportEntry(ComWrappers_SetGlobalInstanceRegisteredForTrackerSupport) + DllImportEntry(ComWrappers_AllocateRefCountedHandle) + DllImportEntry(ComWrappers_GetIReferenceTrackerTargetVftbl) + DllImportEntry(ComWrappers_GetTaggedImpl) + DllImportEntry(ComWrappers_RegisterIsRootedCallback) + DllImportEntry(TrackerObjectManager_HasReferenceTrackerManager) + DllImportEntry(TrackerObjectManager_TryRegisterReferenceTrackerManager) + DllImportEntry(TrackerObjectManager_RegisterNativeObjectWrapperCache) + DllImportEntry(TrackerObjectManager_IsGlobalPeggingEnabled) #endif #if defined(FEATURE_OBJCMARSHAL) DllImportEntry(ObjCMarshal_TrySetGlobalMessageSendCallback) @@ -514,7 +516,7 @@ static const Entry s_QCall[] = DllImportEntry(InterfaceMarshaler_ConvertToNative) DllImportEntry(InterfaceMarshaler_ConvertToManaged) #endif -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) +#if defined(FEATURE_COMINTEROP) DllImportEntry(ComWeakRefToObject) DllImportEntry(ObjectToComWeakRef) #endif diff --git a/src/coreclr/vm/syncblk.cpp b/src/coreclr/vm/syncblk.cpp index 073dc9c3f3f839..10d88882e71776 100644 --- a/src/coreclr/vm/syncblk.cpp +++ b/src/coreclr/vm/syncblk.cpp @@ -69,10 +69,6 @@ InteropSyncBlockInfo::~InteropSyncBlockInfo() CONTRACTL_END; FreeUMEntryThunk(); - -#if defined(FEATURE_COMWRAPPERS) - delete m_managedObjectComWrapperMap; -#endif // FEATURE_COMWRAPPERS } #ifndef TARGET_UNIX @@ -714,7 +710,7 @@ void SyncBlockCache::InsertCleanupSyncBlock(SyncBlock* psb) continue; } -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) +#if defined(FEATURE_COMINTEROP) if (psb->m_pInteropInfo) { // called during GC @@ -976,9 +972,9 @@ void SyncBlockCache::DeleteSyncBlock(SyncBlock *psb) // clean up comdata if (psb->m_pInteropInfo) { -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) +#if defined(FEATURE_COMINTEROP) CleanupSyncBlockComData(psb->m_pInteropInfo); -#endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS +#endif // FEATURE_COMINTEROP #ifndef TARGET_UNIX if (g_fEEShutDown) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index e0ef98c3fcd153..403428fe266f71 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -608,11 +608,7 @@ class ComClassFactory; struct RCW; class RCWHolder; typedef DPTR(class ComCallWrapper) PTR_ComCallWrapper; - -#include "shash.h" #endif // FEATURE_COMINTEROP - -using ManagedObjectComWrapperByIdMap = MapSHash; class InteropSyncBlockInfo { friend class RCWHolder; @@ -633,22 +629,12 @@ class InteropSyncBlockInfo #endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION , m_pRCW{} #endif // FEATURE_COMINTEROP -#ifdef FEATURE_COMWRAPPERS - , m_externalComObjectContext{} - , m_managedObjectComWrapperLock{} - , m_managedObjectComWrapperMap{} -#endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL , m_taggedMemory{} , m_taggedAlloc{} #endif // FEATURE_OBJCMARSHAL { LIMITED_METHOD_CONTRACT; - -#if defined(FEATURE_COMWRAPPERS) - // The GC thread does enumerate these objects so add CRST_UNSAFE_COOPGC. - m_managedObjectComWrapperLock.Init(CrstManagedObjectWrapperMap, CRST_UNSAFE_COOPGC); -#endif // FEATURE_COMWRAPPERS } #ifndef DACCESS_COMPILE ~InteropSyncBlockInfo(); @@ -802,122 +788,6 @@ class InteropSyncBlockInfo #endif // FEATURE_COMINTEROP -#if defined(FEATURE_COMWRAPPERS) -public: - bool TryGetManagedObjectComWrapper(_In_ INT64 wrapperId, _Out_ void** mocw) - { - LIMITED_METHOD_DAC_CONTRACT; - - *mocw = NULL; - if (m_managedObjectComWrapperMap == NULL) - return false; - - CrstHolder lock(&m_managedObjectComWrapperLock); - return m_managedObjectComWrapperMap->Lookup(wrapperId, mocw); - } - -#ifndef DACCESS_COMPILE - bool TrySetManagedObjectComWrapper(_In_ INT64 wrapperId, _In_ void* mocw, _In_ void* curr = NULL) - { - LIMITED_METHOD_CONTRACT; - - if (m_managedObjectComWrapperMap == NULL) - { - NewHolder map = new ManagedObjectComWrapperByIdMap(); - if (InterlockedCompareExchangeT(&m_managedObjectComWrapperMap, (ManagedObjectComWrapperByIdMap *)map, NULL) == NULL) - { - map.SuppressRelease(); - } - - _ASSERTE(m_managedObjectComWrapperMap != NULL); - } - - CrstHolder lock(&m_managedObjectComWrapperLock); - - if (m_managedObjectComWrapperMap->LookupPtr(wrapperId) != curr) - return false; - - m_managedObjectComWrapperMap->Add(wrapperId, mocw); - return true; - } - - using ClearWrappersCallback = void(void* mocw); - void ClearManagedObjectComWrappers(ClearWrappersCallback* callback) - { - LIMITED_METHOD_CONTRACT; - - if (m_managedObjectComWrapperMap == NULL) - return; - - CQuickArrayList localList; - { - CrstHolder lock(&m_managedObjectComWrapperLock); - if (callback != NULL) - { - ManagedObjectComWrapperByIdMap::Iterator iter = m_managedObjectComWrapperMap->Begin(); - while (iter != m_managedObjectComWrapperMap->End()) - { - localList.Push(iter->Value()); - ++iter; - } - } - - m_managedObjectComWrapperMap->RemoveAll(); - } - - for (SIZE_T i = 0; i < localList.Size(); i++) - callback(localList[i]); - } - - using EnumWrappersCallback = bool(void* mocw, void* cxt); - void EnumManagedObjectComWrappers(EnumWrappersCallback* callback, void* cxt) - { - LIMITED_METHOD_CONTRACT; - - _ASSERTE(callback != NULL); - - if (m_managedObjectComWrapperMap == NULL) - return; - - CrstHolder lock(&m_managedObjectComWrapperLock); - - ManagedObjectComWrapperByIdMap::Iterator iter = m_managedObjectComWrapperMap->Begin(); - while (iter != m_managedObjectComWrapperMap->End()) - { - if (!callback(iter->Value(), cxt)) - break; - ++iter; - } - } -#endif // !DACCESS_COMPILE - - bool TryGetExternalComObjectContext(_Out_ void** eoc) - { - LIMITED_METHOD_DAC_CONTRACT; - *eoc = m_externalComObjectContext; - return (*eoc != NULL); - } - -#ifndef DACCESS_COMPILE - bool TrySetExternalComObjectContext(_In_ void* eoc, _In_ void* curr = NULL) - { - LIMITED_METHOD_CONTRACT; - - return (InterlockedCompareExchangeT( - &m_externalComObjectContext, - eoc, - curr) == curr); - } -#endif // !DACCESS_COMPILE - -private: - // See InteropLib API for usage. - void* m_externalComObjectContext; - - CrstExplicitInit m_managedObjectComWrapperLock; - ManagedObjectComWrapperByIdMap* m_managedObjectComWrapperMap; -#endif // FEATURE_COMWRAPPERS - #ifdef FEATURE_OBJCMARSHAL public: #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/weakreferencenative.cpp b/src/coreclr/vm/weakreferencenative.cpp index bb0ac10906ef00..3f1724ef211b6b 100644 --- a/src/coreclr/vm/weakreferencenative.cpp +++ b/src/coreclr/vm/weakreferencenative.cpp @@ -45,10 +45,9 @@ void FinalizeWeakReference(Object* obj) GCHandleUtilities::GetGCHandleManager()->DestroyHandleOfType(handle, handleType); } -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) +#if defined(FEATURE_COMINTEROP) -// static -extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference* pComWeakReference, INT64 wrapperId, QCall::ObjectHandleOnStack retRcw) +extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference* pComWeakReference, QCall::ObjectHandleOnStack retRcw) { QCALL_CONTRACT; BEGIN_QCALL; @@ -78,25 +77,9 @@ extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference* pComWeakReference, OBJECTREF rcwRef = NULL; GCPROTECT_BEGIN(rcwRef); - if (wrapperId != ComWrappersNative::InvalidWrapperId) - { - // Try the global COM wrappers - if (GlobalComWrappersForTrackerSupport::IsRegisteredInstance(wrapperId)) - { - (void)GlobalComWrappersForTrackerSupport::TryGetOrCreateObjectForComInstance(pTargetIdentity, &rcwRef); - } - else if (GlobalComWrappersForMarshalling::IsRegisteredInstance(wrapperId)) - { - (void)GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance(pTargetIdentity, ObjFromComIP::NONE, &rcwRef); - } - } -#ifdef FEATURE_COMINTEROP - else - { - // If the original RCW was not created through ComWrappers, fall back to the built-in system. - GetObjectRefFromComIP(&rcwRef, pTargetIdentity); - } -#endif // FEATURE_COMINTEROP + // If the original RCW was not created through ComWrappers, fall back to the built-in system. + GetObjectRefFromComIP(&rcwRef, pTargetIdentity); + GCPROTECT_END(); retRcw.Set(rcwRef); } @@ -105,49 +88,32 @@ extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference* pComWeakReference, return; } -// static -extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj, INT64* pWrapperId) +extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj) { QCALL_CONTRACT; IWeakReference* pWeakReference = nullptr; BEGIN_QCALL; - *pWrapperId = ComWrappersNative::InvalidWrapperId; SafeComHolder pWeakReferenceSource(nullptr); - _ASSERTE(obj.m_ppObject != nullptr); { // COM helpers assume COOP mode and the arguments are protected refs. GCX_COOP(); OBJECTREF objRef = obj.Get(); + _ASSERTE(objRef != nullptr); GCPROTECT_BEGIN(objRef); // If the object is not an RCW, then we do not want to use a native COM weak reference to it // If the object is a managed type deriving from a COM type, then we also do not want to use a native COM // weak reference to it. (Otherwise, we'll wind up resolving IWeakReference-s back into the CLR // when we don't want to have reentrancy). -#ifdef FEATURE_COMINTEROP MethodTable* pMT = objRef->GetMethodTable(); if (pMT->IsComObjectType() && (pMT == g_pBaseCOMObject || !pMT->IsExtensibleRCW())) { pWeakReferenceSource = reinterpret_cast(GetComIPFromObjectRef(&objRef, IID_IWeakReferenceSource, false /* throwIfNoComIP */)); } - else -#endif - { -#ifdef FEATURE_COMWRAPPERS - bool isAggregated = false; - pWeakReferenceSource = reinterpret_cast(ComWrappersNative::GetIdentityForObject(&objRef, IID_IWeakReferenceSource, pWrapperId, &isAggregated)); - if (isAggregated) - { - // 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. - pWeakReferenceSource = nullptr; - } -#endif - } GCPROTECT_END(); } @@ -165,15 +131,4 @@ extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnSt END_QCALL; return pWeakReference; } - -FCIMPL1(FC_BOOL_RET, ComAwareWeakReferenceNative::HasInteropInfo, Object* pObject) -{ - FCALL_CONTRACT; - _ASSERTE(pObject != nullptr); - - SyncBlock* pSyncBlock = pObject->PassiveGetSyncBlock(); - return pSyncBlock != nullptr && pSyncBlock->GetInteropInfoNoCreate() != nullptr; -} -FCIMPLEND - -#endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS +#endif // FEATURE_COMINTEROP diff --git a/src/coreclr/vm/weakreferencenative.h b/src/coreclr/vm/weakreferencenative.h index c95f84119503de..df2ba1dc23e4dd 100644 --- a/src/coreclr/vm/weakreferencenative.h +++ b/src/coreclr/vm/weakreferencenative.h @@ -13,17 +13,11 @@ #include "weakreference.h" -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) +#ifdef FEATURE_COMINTEROP -class ComAwareWeakReferenceNative -{ -public: - static FCDECL1(FC_BOOL_RET, HasInteropInfo, Object* pObject); -}; +extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference * pComWeakReference, QCall::ObjectHandleOnStack retRcw); +extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj); -extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference * pComWeakReference, INT64 wrapperId, QCall::ObjectHandleOnStack retRcw); -extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj, INT64* wrapperId); - -#endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS +#endif // FEATURE_COMINTEROP #endif // _WEAKREFERENCENATIVE_H diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d3f62e4c34b7d9..5d1bfacb65637f 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -959,7 +959,8 @@ - + + @@ -2132,6 +2133,9 @@ Common\Interop\Windows\Ole32\Interop.CoCreateGuid.cs + + Interop\Windows\Ole32\Interop.CoGetContextToken.cs + Common\Interop\Windows\Ole32\Interop.CoGetStandardMarshal.cs @@ -2259,6 +2263,13 @@ + + + + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs b/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs index 7d71cd93846526..199b64bc8bb4b4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ComAwareWeakReference.cs @@ -24,11 +24,11 @@ internal sealed class ComInfo // _pComWeakRef is effectively readonly. // the only place where we change it after construction is in finalizer. private IntPtr _pComWeakRef; - private readonly long _wrapperId; + private readonly object? _context; internal object? ResolveTarget() { - return ComWeakRefToObject(_pComWeakRef, _wrapperId); + return ComWeakRefToObject(_pComWeakRef, _context); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -43,13 +43,13 @@ internal sealed class ComInfo [MethodImpl(MethodImplOptions.NoInlining)] private static ComInfo? FromObjectSlow(object target) { - IntPtr pComWeakRef = ObjectToComWeakRef(target, out long wrapperId); + IntPtr pComWeakRef = ObjectToComWeakRef(target, out object? context); if (pComWeakRef == 0) return null; try { - return new ComInfo(pComWeakRef, wrapperId); + return new ComInfo(pComWeakRef, context); } catch (OutOfMemoryException) { @@ -59,11 +59,11 @@ internal sealed class ComInfo } } - private ComInfo(IntPtr pComWeakRef, long wrapperId) + private ComInfo(IntPtr pComWeakRef, object? context) { Debug.Assert(pComWeakRef != IntPtr.Zero); _pComWeakRef = pComWeakRef; - _wrapperId = wrapperId; + _context = context; } ~ComInfo() diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs index 77e97ba62f4653..cda3e83972a345 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace System.Diagnostics @@ -17,5 +18,15 @@ public static partial class Debugger public static void BreakForUserUnhandledException(Exception exception) { } + + [FeatureSwitchDefinition("System.Diagnostics.Debugger.IsSupported")] + internal static bool IsSupported { get; } = InitializeIsSupported(); + + private static bool InitializeIsSupported() + { + return AppContext.TryGetSwitch("System.Diagnostics.Debugger.IsSupported", out bool isSupported) + ? isSupported + : true; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs index eb6af90b5a5043..c955082f547e2a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Numerics; +using System.Runtime.InteropServices; using System.Threading; namespace System.Runtime.CompilerServices @@ -538,6 +539,7 @@ private void CreateEntry(TKey key, TValue value) // When the dictionary grows the _entries table, it scours it for expired keys and does not // add those to the new container. //-------------------------------------------------------------------------------------------- + [StructLayout(LayoutKind.Auto)] private struct Entry { public DependentHandle depHnd; // Holds key and value using a weak reference for the key and a strong reference diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.ComWrappers.cs similarity index 63% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs rename to src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.ComWrappers.cs index d95eab971603a2..2aae61b9390df9 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.ComWrappers.cs @@ -4,8 +4,6 @@ using System; using System.Runtime.CompilerServices; -#if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS - namespace System { internal sealed partial class ComAwareWeakReference @@ -14,36 +12,35 @@ internal sealed partial class ComAwareWeakReference // 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_comWeakRefToObjectCallback; private static unsafe delegate* s_possiblyComObjectCallback; - private static unsafe delegate* s_objectToComWeakRefCallback; + private static unsafe delegate* s_objectToComWeakRefCallback; internal static unsafe void InitializeCallbacks( - delegate* comWeakRefToObject, + delegate* comWeakRefToObject, delegate* possiblyComObject, - delegate* objectToComWeakRef) + delegate* objectToComWeakRef) { s_comWeakRefToObjectCallback = comWeakRefToObject; s_objectToComWeakRefCallback = objectToComWeakRef; s_possiblyComObjectCallback = possiblyComObject; } - internal static unsafe object? ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId) + internal static unsafe object? ComWeakRefToComWrappersObject(IntPtr pComWeakRef, object? context) { - return s_comWeakRefToObjectCallback != null ? s_comWeakRefToObjectCallback(pComWeakRef, wrapperId) : null; + return s_comWeakRefToObjectCallback != null ? s_comWeakRefToObjectCallback(pComWeakRef, context) : null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe bool PossiblyComObject(object target) + internal static unsafe bool PossiblyComWrappersObject(object target) { return s_possiblyComObjectCallback != null ? s_possiblyComObjectCallback(target) : false; } - internal static unsafe IntPtr ObjectToComWeakRef(object target, out long wrapperId) + internal static unsafe IntPtr ComWrappersObjectToComWeakRef(object target, out object? context) { - wrapperId = 0; - return s_objectToComWeakRefCallback != null ? s_objectToComWeakRefCallback(target, out wrapperId) : IntPtr.Zero; + context = null; + return s_objectToComWeakRefCallback != null ? s_objectToComWeakRefCallback(target, out context) : IntPtr.Zero; } } } -#endif diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs index 37c25f851538a7..1d87d2b2e9dbba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs @@ -7,8 +7,26 @@ namespace System.Runtime.InteropServices { - public abstract partial class ComWrappers + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [CLSCompliant(false)] + public abstract class ComWrappers { + public struct ComInterfaceEntry + { + public Guid IID; + + public IntPtr Vtable; + } + + protected abstract unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count); + + protected abstract object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags); + + protected internal abstract void ReleaseObjects(IEnumerable objects); + public static unsafe bool TryGetComInstance(object obj, out IntPtr unknown) { unknown = default; @@ -21,8 +39,9 @@ public static unsafe bool TryGetObject(IntPtr unknown, [NotNullWhen(true)] out o return false; } - public partial struct ComInterfaceDispatch + public struct ComInterfaceDispatch { + public IntPtr Vtable; public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class { throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index afc824b8025e6f..639f72b2d93416 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -2,101 +2,1366 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.Versioning; +using System.Threading; + +using Internal.Runtime; namespace System.Runtime.InteropServices { + /// - /// Enumeration of flags for . + /// Class for managing wrappers of COM IUnknown types. /// - [Flags] - public enum CreateComInterfaceFlags + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [CLSCompliant(false)] + public abstract partial class ComWrappers { - None = 0, + /// + /// Interface type and pointer to targeted VTable. + /// + public struct ComInterfaceEntry + { + /// + /// Interface IID. + /// + public Guid IID; + + /// + /// Memory must have the same lifetime as the memory returned from the call to . + /// + public IntPtr Vtable; + } + + private const int TrackerRefShift = 32; + private const ulong TrackerRefCounter = 1UL << TrackerRefShift; + private const ulong DestroySentinel = 0x0000000080000000UL; + private const ulong TrackerRefCountMask = 0xffffffff00000000UL; + private const ulong ComRefCountMask = 0x000000007fffffffUL; + private const int COR_E_ACCESSING_CCW = unchecked((int)0x80131544); + + 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_nativeObjectWrapperTable = []; /// - /// The caller will provide an IUnknown Vtable. + /// Associates an object with all the s that were created for it. /// - /// - /// This is useful in scenarios when the caller has no need to rely on an IUnknown instance - /// that is used when running managed code is not possible (i.e. during a GC). In traditional - /// COM scenarios this is common, but scenarios involving Reference Tracker hosting - /// calling of the IUnknown API during a GC is possible. - /// - CallerDefinedIUnknown = 1, + private static readonly ConditionalWeakTable> s_allManagedObjectWrapperTable = []; /// - /// Flag used to indicate the COM interface should implement IReferenceTrackerTarget. - /// When this flag is passed, the resulting COM interface will have an internal implementation of IUnknown - /// and as such none should be supplied by the caller. + /// Associates a managed object with the that was created for it by this instance. /// - TrackerSupport = 2, - } + private readonly ConditionalWeakTable _managedObjectWrapperTable = []; + private readonly RcwCache _rcwCache = new(); - /// - /// Enumeration of flags for . - /// - [Flags] - public enum CreateObjectFlags - { - None = 0, + internal static bool TryGetComInstanceForIID(object obj, Guid iid, out IntPtr unknown, out ComWrappers? comWrappers) + { + if (obj == null + || !s_nativeObjectWrapperTable.TryGetValue(obj, out NativeObjectWrapper? wrapper)) + { + unknown = IntPtr.Zero; + comWrappers = null; + return false; + } + + comWrappers = wrapper.ComWrappers; + return Marshal.QueryInterface(wrapper.ExternalComObject, iid, out unknown) == HResults.S_OK; + } + + public static unsafe bool TryGetComInstance(object obj, out IntPtr unknown) + { + unknown = IntPtr.Zero; + if (obj == null + || !s_nativeObjectWrapperTable.TryGetValue(obj, out NativeObjectWrapper? wrapper)) + { + return false; + } + + return Marshal.QueryInterface(wrapper.ExternalComObject, IID_IUnknown, out unknown) == HResults.S_OK; + } + + public static unsafe bool TryGetObject(IntPtr unknown, [NotNullWhen(true)] out object? obj) + { + obj = null; + if (unknown == IntPtr.Zero) + { + return false; + } + + ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(unknown); + if (comInterfaceDispatch == null || + ComInterfaceDispatch.ToManagedObjectWrapper(comInterfaceDispatch)->MarkedToDestroy) + { + return false; + } + + obj = ComInterfaceDispatch.GetInstance(comInterfaceDispatch); + return true; + } /// - /// Indicate if the supplied external COM object implements the IReferenceTracker. + /// ABI for function dispatch of a COM interface. /// - TrackerObject = 1, + public unsafe partial struct ComInterfaceDispatch + { + public IntPtr Vtable; + + /// + /// Given a pointer from a generated Vtable, convert to the target type. + /// + /// Desired type. + /// Pointer supplied to Vtable function entry. + /// Instance of type associated with dispatched function call. + public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class + { + ManagedObjectWrapper* comInstance = ToManagedObjectWrapper(dispatchPtr); + return Unsafe.As(comInstance->Holder!.WrappedObject); + } + + internal static unsafe ManagedObjectWrapper* ToManagedObjectWrapper(ComInterfaceDispatch* dispatchPtr) + { + InternalComInterfaceDispatch* dispatch = (InternalComInterfaceDispatch*)unchecked((nuint)dispatchPtr & (nuint)InternalComInterfaceDispatch.DispatchAlignmentMask); + return dispatch->_thisPtr; + } + } + + internal unsafe struct InternalComInterfaceDispatch + { +#if TARGET_64BIT + internal const int DispatchAlignment = 64; + internal const int NumEntriesInDispatchTable = DispatchAlignment / 8 /* sizeof(void*) */ - 1; +#else + internal const int DispatchAlignment = 16; + internal const int NumEntriesInDispatchTable = DispatchAlignment / 4 /* sizeof(void*) */ - 1; +#endif + internal const ulong DispatchAlignmentMask = unchecked((ulong)~(InternalComInterfaceDispatch.DispatchAlignment - 1)); + + internal ManagedObjectWrapper* _thisPtr; + + public DispatchTable Vtables; + + [InlineArray(NumEntriesInDispatchTable)] + internal unsafe struct DispatchTable + { + private IntPtr _element; + } + } + + internal enum CreateComInterfaceFlagsEx + { + None = 0, + + /// + /// The caller will provide an IUnknown Vtable. + /// + /// + /// This is useful in scenarios when the caller has no need to rely on an IUnknown instance + /// that is used when running managed code is not possible (i.e. during a GC). In traditional + /// COM scenarios this is common, but scenarios involving Reference Tracker hosting + /// calling of the IUnknown API during a GC is possible. + /// + CallerDefinedIUnknown = 1, + + /// + /// Flag used to indicate the COM interface should implement IReferenceTrackerTarget. + /// When this flag is passed, the resulting COM interface will have an internal implementation of IUnknown + /// and as such none should be supplied by the caller. + /// + TrackerSupport = 2, + + LacksICustomQueryInterface = 1 << 29, + IsComActivated = 1 << 30, + IsPegged = 1 << 31, + + InternalMask = IsPegged | IsComActivated | LacksICustomQueryInterface, + } + + internal unsafe struct ManagedObjectWrapper + { + public volatile IntPtr HolderHandle; // This is GC Handle + public ulong RefCount; + + internal CreateComInterfaceFlagsEx Flags; + public int UserDefinedCount; + public ComInterfaceEntry* UserDefined; + internal InternalComInterfaceDispatch* Dispatches; + + public bool IsRooted + { + get + { + ulong refCount = Interlocked.Read(ref RefCount); + bool rooted = GetComCount(refCount) > 0; + if (!rooted) + { + rooted = GetTrackerCount(refCount) > 0 && + ((Flags & CreateComInterfaceFlagsEx.IsPegged) != 0 || TrackerObjectManager.IsGlobalPeggingEnabled); + } + return rooted; + } + } + + public ManagedObjectWrapperHolder? Holder + { + get + { + IntPtr handle = HolderHandle; + if (handle == IntPtr.Zero) + return null; + else + return Unsafe.As(GCHandle.FromIntPtr(handle).Target); + } + } + + public readonly bool MarkedToDestroy => IsMarkedToDestroy(RefCount); + + public uint AddRef() + { + return GetComCount(Interlocked.Increment(ref RefCount)); + } + + public uint Release() + { + Debug.Assert(GetComCount(RefCount) != 0); + return GetComCount(Interlocked.Decrement(ref RefCount)); + } + + public uint AddRefFromReferenceTracker() + { + ulong prev; + ulong curr; + do + { + prev = RefCount; + curr = prev + TrackerRefCounter; + } while (Interlocked.CompareExchange(ref RefCount, curr, prev) != prev); + + return GetTrackerCount(curr); + } + + public uint ReleaseFromReferenceTracker() + { + Debug.Assert(GetTrackerCount(RefCount) != 0); + ulong prev; + ulong curr; + do + { + prev = RefCount; + curr = prev - TrackerRefCounter; + } + while (Interlocked.CompareExchange(ref RefCount, curr, prev) != prev); + + // If we observe the destroy sentinel, then this release + // must destroy the wrapper. + if (curr == DestroySentinel) + Destroy(); + + return GetTrackerCount(curr); + } + + public uint Peg() + { + SetFlag(CreateComInterfaceFlagsEx.IsPegged); + return HResults.S_OK; + } + + public uint Unpeg() + { + ResetFlag(CreateComInterfaceFlagsEx.IsPegged); + return HResults.S_OK; + } + + + public unsafe int QueryInterfaceForTracker(in Guid riid, out IntPtr ppvObject) + { + if (IsMarkedToDestroy(RefCount) || Holder is null) + { + ppvObject = IntPtr.Zero; + return COR_E_ACCESSING_CCW; + } + + return QueryInterface(in riid, out ppvObject); + } + + public unsafe int QueryInterface(in Guid riid, out IntPtr ppvObject) + { + ppvObject = AsRuntimeDefined(in riid); + if (ppvObject == IntPtr.Zero) + { + if ((Flags & CreateComInterfaceFlagsEx.LacksICustomQueryInterface) == 0) + { + Debug.Assert(Holder is not null); + if (Holder.WrappedObject is not ICustomQueryInterface customQueryInterface) + { + SetFlag(CreateComInterfaceFlagsEx.LacksICustomQueryInterface); + } + else + { + Guid riidLocal = riid; + switch (customQueryInterface.GetInterface(ref riidLocal, out ppvObject)) + { + case CustomQueryInterfaceResult.Handled: + return HResults.S_OK; + case CustomQueryInterfaceResult.NotHandled: + break; + case CustomQueryInterfaceResult.Failed: + return HResults.COR_E_INVALIDCAST; + } + } + } + + ppvObject = AsUserDefined(in riid); + if (ppvObject == IntPtr.Zero) + return HResults.COR_E_INVALIDCAST; + } + + AddRef(); + return HResults.S_OK; + } + + public IntPtr As(in Guid riid) + { + // Find target interface and return dispatcher or null if not found. + IntPtr typeMaybe = AsRuntimeDefined(in riid); + if (typeMaybe == IntPtr.Zero) + typeMaybe = AsUserDefined(in riid); + + return typeMaybe; + } + + /// true if actually destroyed + public unsafe bool Destroy() + { + Debug.Assert(GetComCount(RefCount) == 0 || HolderHandle == IntPtr.Zero); + + if (HolderHandle == IntPtr.Zero) + { + // We either were previously destroyed or multiple ManagedObjectWrapperHolder + // were created by the ConditionalWeakTable for the same object and we lost the race. + return true; + } + + ulong prev, refCount; + do + { + prev = RefCount; + refCount = prev | DestroySentinel; + } while (Interlocked.CompareExchange(ref RefCount, refCount, prev) != prev); + + if (refCount == DestroySentinel) + { + IntPtr handle = Interlocked.Exchange(ref HolderHandle, IntPtr.Zero); + if (handle != IntPtr.Zero) + { + GCHandle.InternalFree(handle); + } + return true; + } + else + { + return false; + } + } + + private unsafe IntPtr GetDispatchPointerAtIndex(int index) + { + InternalComInterfaceDispatch* dispatch = &Dispatches[index / InternalComInterfaceDispatch.NumEntriesInDispatchTable]; + IntPtr* vtables = (IntPtr*)(void*)&dispatch->Vtables; + return (IntPtr)(&vtables[index % InternalComInterfaceDispatch.NumEntriesInDispatchTable]); + } + + private unsafe IntPtr AsRuntimeDefined(in Guid riid) + { + // The order of interface lookup here is important. + // See CreateManagedObjectWrapper() for the expected order. + int i = UserDefinedCount; + if ((Flags & CreateComInterfaceFlagsEx.CallerDefinedIUnknown) == 0) + { + if (riid == IID_IUnknown) + { + return GetDispatchPointerAtIndex(i); + } + + i++; + } + + if ((Flags & CreateComInterfaceFlagsEx.TrackerSupport) != 0) + { + if (riid == IID_IReferenceTrackerTarget) + { + return GetDispatchPointerAtIndex(i); + } + + i++; + } + + { + if (riid == IID_TaggedImpl) + { + return GetDispatchPointerAtIndex(i); + } + } + + return IntPtr.Zero; + } + + private unsafe IntPtr AsUserDefined(in Guid riid) + { + for (int i = 0; i < UserDefinedCount; ++i) + { + if (UserDefined[i].IID == riid) + { + return GetDispatchPointerAtIndex(i); + } + } + + return IntPtr.Zero; + } + + private void SetFlag(CreateComInterfaceFlagsEx flag) + { + int setMask = (int)flag; + Interlocked.Or(ref Unsafe.As(ref Flags), setMask); + } + + private void ResetFlag(CreateComInterfaceFlagsEx flag) + { + int resetMask = ~(int)flag; + Interlocked.And(ref Unsafe.As(ref Flags), resetMask); + } + + private static uint GetTrackerCount(ulong c) + { + return (uint)((c & TrackerRefCountMask) >> TrackerRefShift); + } + + private static uint GetComCount(ulong c) + { + return (uint)(c & ComRefCountMask); + } + + private static bool IsMarkedToDestroy(ulong c) + { + return (c & DestroySentinel) != 0; + } + } + + internal sealed unsafe partial class ManagedObjectWrapperHolder + { + static ManagedObjectWrapperHolder() + { + RegisterIsRootedCallback(); + } + + private readonly ManagedObjectWrapperReleaser _releaser; + private readonly object _wrappedObject; + + private readonly ManagedObjectWrapper* _wrapper; + + public ManagedObjectWrapperHolder(ManagedObjectWrapper* wrapper, object wrappedObject) + { + _wrapper = wrapper; + _wrappedObject = wrappedObject; + _releaser = new ManagedObjectWrapperReleaser(wrapper); + _wrapper->HolderHandle = AllocateRefCountedHandle(this); + } + + public unsafe IntPtr ComIp => _wrapper->As(in ComWrappers.IID_IUnknown); + + public object WrappedObject => _wrappedObject; + + public uint AddRef() => _wrapper->AddRef(); + + public bool IsActivated => _wrapper->Flags.HasFlag(CreateComInterfaceFlagsEx.IsComActivated); + + internal ManagedObjectWrapper* Wrapper => _wrapper; + } + + internal sealed unsafe class ManagedObjectWrapperReleaser + { + private ManagedObjectWrapper* _wrapper; + + public ManagedObjectWrapperReleaser(ManagedObjectWrapper* wrapper) + { + _wrapper = wrapper; + } + + ~ManagedObjectWrapperReleaser() + { + IntPtr refCountedHandle = _wrapper->HolderHandle; + if (refCountedHandle != IntPtr.Zero && GCHandle.InternalGet(refCountedHandle) != null) + { + // The ManagedObjectWrapperHolder has not been fully collected, so it is still + // potentially reachable via the Conditional Weak Table. + // Keep ourselves alive in case the wrapped object is resurrected. + GC.ReRegisterForFinalize(this); + return; + } + + // Release GC handle created when MOW was built. + if (_wrapper->Destroy()) + { + NativeMemory.AlignedFree(_wrapper); + _wrapper = null; + } + else + { + // There are still outstanding references on the COM side. + // This case should only be hit when an outstanding + // tracker refcount exists from AddRefFromReferenceTracker. + GC.ReRegisterForFinalize(this); + } + } + } + + internal unsafe class NativeObjectWrapper + { + private ComWrappers _comWrappers; + private IntPtr _externalComObject; + private IntPtr _inner; + private GCHandle _proxyHandle; + private GCHandle _proxyHandleTrackingResurrection; + private readonly bool _aggregatedManagedObjectWrapper; + private readonly bool _uniqueInstance; + + 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, + ref IntPtr referenceTrackerMaybe) + { + if (flags.HasFlag(CreateObjectFlags.TrackerObject)) + { + IntPtr trackerObject = referenceTrackerMaybe; + + // We're taking ownership of this reference tracker object, so reset the reference + referenceTrackerMaybe = IntPtr.Zero; + + // If we already have a reference tracker (that will be the case in aggregation scenarios), then reuse it. + // Otherwise, do the 'QueryInterface' call for it here. This allows us to only ever query for this IID once. + if (trackerObject != IntPtr.Zero || + Marshal.QueryInterface(externalComObject, IID_IReferenceTracker, out trackerObject) == HResults.S_OK) + { + return new ReferenceTrackerNativeObjectWrapper(externalComObject, inner, comWrappers, comProxy, flags, trackerObject); + } + } + + return new NativeObjectWrapper(externalComObject, inner, comWrappers, comProxy, flags); + } + + protected NativeObjectWrapper(IntPtr externalComObject, IntPtr inner, ComWrappers comWrappers, object comProxy, CreateObjectFlags flags) + { + _externalComObject = externalComObject; + _inner = inner; + _comWrappers = comWrappers; + _uniqueInstance = flags.HasFlag(CreateObjectFlags.UniqueInstance); + _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 reference tracker runtime + // 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); + } + } + + internal IntPtr ExternalComObject => _externalComObject; + internal ComWrappers ComWrappers => _comWrappers; + internal GCHandle ProxyHandle => _proxyHandle; + internal bool IsUniqueInstance => _uniqueInstance; + internal bool IsAggregatedWithManagedObjectWrapper => _aggregatedManagedObjectWrapper; + + public virtual void Release() + { + if (!_uniqueInstance && _comWrappers is not null) + { + _comWrappers._rcwCache.Remove(_externalComObject, this); + _comWrappers = null!; + } + + if (_proxyHandle.IsAllocated) + { + _proxyHandle.Free(); + } + + if (_proxyHandleTrackingResurrection.IsAllocated) + { + _proxyHandleTrackingResurrection.Free(); + } + + // If the inner was supplied, we need to release our reference. + if (_inner != 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; + internal readonly IntPtr _contextToken; + private int _trackerObjectDisconnected; // Atomic boolean, so using int. + private readonly bool _releaseTrackerObject; + 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 = TrackerObjectManager.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) + { + TrackerObjectManager.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; + } + } + } /// - /// Ignore any internal caching and always create a unique instance. + /// Globally registered instance of the ComWrappers class for reference tracker support. /// - UniqueInstance = 2, + private static ComWrappers? s_globalInstanceForTrackerSupport; + + internal static ComWrappers? GlobalInstanceForTrackerSupport => s_globalInstanceForTrackerSupport; /// - /// Defined when COM aggregation is involved (that is an inner instance supplied). + /// Globally registered instance of the ComWrappers class for marshalling. /// - Aggregation = 4, + private static ComWrappers? s_globalInstanceForMarshalling; + + internal static object? GetOrCreateObjectFromWrapper(ComWrappers wrapper, IntPtr externalComObject) + { + if (s_globalInstanceForTrackerSupport != null && s_globalInstanceForTrackerSupport == wrapper) + { + return s_globalInstanceForTrackerSupport.GetOrCreateObjectForComInstance(externalComObject, CreateObjectFlags.TrackerObject); + } + else if (s_globalInstanceForMarshalling != null && s_globalInstanceForMarshalling == wrapper) + { + return ComObjectForInterface(externalComObject); + } + else + { + return null; + } + } /// - /// Check if the supplied instance is actually a wrapper and if so return the underlying - /// managed object rather than creating a new wrapper. + /// Create a COM representation of the supplied object that can be passed to a non-managed environment. /// + /// The managed object to expose outside the .NET runtime. + /// Flags used to configure the generated interface. + /// The generated COM interface that can be passed outside the .NET runtime. /// - /// This matches the built-in RCW semantics for COM interop. + /// If a COM representation was previously created for the specified using + /// this instance, the previously created COM interface will be returned. + /// If not, a new one will be created. /// - Unwrap = 8, - } + public unsafe IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags) + { + ArgumentNullException.ThrowIfNull(instance); + + ManagedObjectWrapperHolder managedObjectWrapper = _managedObjectWrapperTable.GetOrAdd(instance, static (c, state) => + { + ManagedObjectWrapper* value = state.This!.CreateManagedObjectWrapper(c, state.flags); + return new ManagedObjectWrapperHolder(value, c); + }, new { This = this, flags }); + + managedObjectWrapper.AddRef(); + RegisterManagedObjectWrapperForDiagnostics(instance, managedObjectWrapper); + + return managedObjectWrapper.ComIp; + } + + private static void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper) + { + if (!Debugger.IsSupported) + { + // If managed debugging support is disabled, don't add to the second table. + // Only the debugger will ever read it. + return; + } + + // Record the relationship between the managed object and this wrapper. + // This will ensure that we keep all ManagedObjectWrapperHolders alive as long as the managed object is alive, + // even if the ComWrappers instance dies. + // We must do this as the runtime's recording of the relationship between the managed object and the wrapper + // lives as long as the object is alive, and we record the raw pointer to the wrapper. + List allWrappersForThisInstance = s_allManagedObjectWrapperTable.GetOrCreateValue(instance); + lock (allWrappersForThisInstance) + { + allWrappersForThisInstance.Add(wrapper); + } + } + + private static nuint AlignUp(nuint value, nuint alignment) + { + nuint alignMask = alignment - 1; + return (nuint)((value + alignMask) & ~alignMask); + } + + private unsafe ManagedObjectWrapper* CreateManagedObjectWrapper(object instance, CreateComInterfaceFlags flags) + { + ComInterfaceEntry* userDefined = ComputeVtables(instance, flags, out int userDefinedCount); + if ((userDefined == null && userDefinedCount != 0) || userDefinedCount < 0) + { + throw new ArgumentException(); + } + + // Maximum number of runtime supplied vtables. + Span runtimeDefinedVtable = stackalloc IntPtr[3]; + int runtimeDefinedCount = 0; + + // Check if the caller will provide the IUnknown table. + if ((flags & CreateComInterfaceFlags.CallerDefinedIUnknown) == CreateComInterfaceFlags.None) + { + runtimeDefinedVtable[runtimeDefinedCount++] = DefaultIUnknownVftblPtr; + } + + if ((flags & CreateComInterfaceFlags.TrackerSupport) != 0) + { + runtimeDefinedVtable[runtimeDefinedCount++] = DefaultIReferenceTrackerTargetVftblPtr; + } + + { + runtimeDefinedVtable[runtimeDefinedCount++] = TaggedImplVftblPtr; + } + + // Compute size for ManagedObjectWrapper instance. + int totalDefinedCount = runtimeDefinedCount + userDefinedCount; + + int numSections = totalDefinedCount / InternalComInterfaceDispatch.NumEntriesInDispatchTable; + if (totalDefinedCount % InternalComInterfaceDispatch.NumEntriesInDispatchTable != 0) + { + // Account for a trailing partial section to fit all of the defined interfaces. + numSections++; + } + + nuint headerSize = AlignUp((nuint)sizeof(ManagedObjectWrapper), InternalComInterfaceDispatch.DispatchAlignment); + + // Instead of allocating a full section even when we have a trailing one, we'll allocate only + // as much space as we need to store all of our dispatch tables. + nuint dispatchSectionSize = (nuint)totalDefinedCount * (nuint)sizeof(void*) + (nuint)numSections * (nuint)sizeof(void*); + + // Allocate memory for the ManagedObjectWrapper with the correct alignment for our dispatch tables. + IntPtr wrapperMem = (IntPtr)NativeMemory.AlignedAlloc( + headerSize + dispatchSectionSize, + InternalComInterfaceDispatch.DispatchAlignment); + + // Dispatches follow the ManagedObjectWrapper. + InternalComInterfaceDispatch* pDispatches = (InternalComInterfaceDispatch*)((nuint)wrapperMem + headerSize); + Span dispatches = new Span(pDispatches, numSections); + for (int i = 0; i < dispatches.Length; i++) + { + dispatches[i]._thisPtr = (ManagedObjectWrapper*)wrapperMem; + Span dispatchVtables = dispatches[i].Vtables; + for (int j = 0; j < dispatchVtables.Length; j++) + { + int index = i * dispatchVtables.Length + j; + if (index >= totalDefinedCount) + { + break; + } + dispatchVtables[j] = (index < userDefinedCount) ? userDefined[index].Vtable : runtimeDefinedVtable[index - userDefinedCount]; + } + } + + ManagedObjectWrapper* mow = (ManagedObjectWrapper*)wrapperMem; + mow->HolderHandle = IntPtr.Zero; + mow->RefCount = 0; + mow->UserDefinedCount = userDefinedCount; + mow->UserDefined = userDefined; + mow->Flags = (CreateComInterfaceFlagsEx)flags; + mow->Dispatches = pDispatches; + return mow; + } - /// - /// Class for managing wrappers of COM IUnknown types. - /// - [UnsupportedOSPlatform("android")] - [UnsupportedOSPlatform("browser")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - [CLSCompliant(false)] - public abstract partial class ComWrappers - { /// - /// Interface type and pointer to targeted VTable. + /// Get the currently registered managed object or creates a new managed object and registers it. /// - public struct ComInterfaceEntry + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// Returns a managed object associated with the supplied external COM object. + /// + /// If a managed object was previously created for the specified + /// using this instance, the previously created object will be returned. + /// If not, a new one will be created. + /// + public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags) { - /// - /// Interface IID. - /// - public Guid IID; + object? obj; + if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, IntPtr.Zero, flags, null, out obj)) + throw new ArgumentNullException(nameof(externalComObject)); + + return obj; + } + + /// + /// Get the currently registered managed object or uses the supplied managed object and registers it. + /// + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// The to be used as the wrapper for the external object + /// Returns a managed object associated with the supplied external COM object. + /// + /// If the instance already has an associated external object a will be thrown. + /// + public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper) + { + return GetOrRegisterObjectForComInstance(externalComObject, flags, wrapper, IntPtr.Zero); + } + + /// + /// Get the currently registered managed object or uses the supplied managed object and registers it. + /// + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// The to be used as the wrapper for the external object + /// Inner for COM aggregation scenarios + /// Returns a managed object associated with the supplied external COM object. + /// + /// This method override is for registering an aggregated COM instance with its associated inner. The inner + /// will be released when the associated wrapper is eventually freed. Note that it will be released on a thread + /// in an unknown apartment state. If the supplied inner is not known to be a free-threaded instance then + /// it is advised to not supply the inner. + /// + /// If the instance already has an associated external object a will be thrown. + /// + public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper, IntPtr inner) + { + ArgumentNullException.ThrowIfNull(wrapper); + + object? obj; + if (!TryGetOrCreateObjectForComInstanceInternal(externalComObject, inner, flags, wrapper, out obj)) + throw new ArgumentNullException(nameof(externalComObject)); + + return obj; + } + + private static unsafe ComInterfaceDispatch* TryGetComInterfaceDispatch(IntPtr comObject) + { + // If the first Vtable entry is part of a ManagedObjectWrapper impl, + // we know how to interpret the IUnknown. + IntPtr knownQI = ((IntPtr*)((IntPtr*)comObject)[0])[0]; + if (knownQI != ((IntPtr*)DefaultIUnknownVftblPtr)[0] + || knownQI != ((IntPtr*)DefaultIReferenceTrackerTargetVftblPtr)[0]) + { + // It is possible the user has defined their own IUnknown impl so + // we fallback to the tagged interface approach to be sure. + if (0 != Marshal.QueryInterface(comObject, IID_TaggedImpl, out nint implMaybe)) + { + return null; + } + + IntPtr currentVersion = GetTaggedImplCurrentVersion(); + int hr = ((delegate* unmanaged)(*(*(void***)implMaybe + 3 /* ITaggedImpl.IsCurrentVersion slot */)))(implMaybe, currentVersion); + Marshal.Release(implMaybe); + if (hr != 0) + { + return null; + } + } + + return (ComInterfaceDispatch*)comObject; + } + + private static void DetermineIdentityAndInner( + IntPtr externalComObject, + IntPtr innerMaybe, + CreateObjectFlags flags, + out IntPtr identity, + out IntPtr inner, + out IntPtr referenceTrackerMaybe) + { + 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, 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. This allows us to keep the reference tracker + // reference alive, so we can reuse it later. + checkForIdentity = referenceTrackerPtr; + referenceTrackerMaybe = referenceTrackerPtr; + Marshal.ThrowExceptionForHR(Marshal.QueryInterface(checkForIdentity, IID_IUnknown, out identity)); + } + else + { + referenceTrackerMaybe = IntPtr.Zero; + Marshal.ThrowExceptionForHR(Marshal.QueryInterface(externalComObject, 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; + } + } + + /// + /// Get the currently registered managed object or creates a new managed object and registers it. + /// + /// Object to import for usage into the .NET runtime. + /// The inner instance if aggregation is involved + /// Flags used to describe the external object. + /// The to be used as the wrapper for the external object. + /// The managed object associated with the supplied external COM object or null if it could not be created. + /// Returns true if a managed object could be retrieved/created, false otherwise + private unsafe bool TryGetOrCreateObjectForComInstanceInternal( + IntPtr externalComObject, + IntPtr innerMaybe, + CreateObjectFlags flags, + object? wrapperMaybe, + [NotNullWhen(true)] out object? retValue) + { + if (externalComObject == IntPtr.Zero) + throw new ArgumentNullException(nameof(externalComObject)); + + if (innerMaybe != IntPtr.Zero && !flags.HasFlag(CreateObjectFlags.Aggregation)) + throw new InvalidOperationException(SR.InvalidOperation_SuppliedInnerMustBeMarkedAggregation); + + DetermineIdentityAndInner( + externalComObject, + innerMaybe, + flags, + out IntPtr identity, + out IntPtr inner, + out IntPtr referenceTrackerMaybe); + + try + { + // If the user has requested a unique instance, + // we will immediately create the object, register it, + // and return. + if (flags.HasFlag(CreateObjectFlags.UniqueInstance)) + { + retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, ref referenceTrackerMaybe); + return retValue is not null; + } + + // If we have a live cached wrapper currently, + // return that. + if (_rcwCache.FindProxyForComInstance(identity) is object liveCachedWrapper) + { + retValue = liveCachedWrapper; + return true; + } + + // If the user tried to provide a pre-created managed wrapper, try to register + // that object as the wrapper. + if (wrapperMaybe is not null) + { + retValue = RegisterObjectForComInstance(identity, inner, wrapperMaybe, flags, ref referenceTrackerMaybe); + return retValue is not null; + } + + // Check if the provided COM instance is actually a managed object wrapper from this + // ComWrappers instance, and use it if it is. + if (flags.HasFlag(CreateObjectFlags.Unwrap)) + { + ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(identity); + if (comInterfaceDispatch != null) + { + // If we found a managed object wrapper in this ComWrappers instance + // and it has the same identity pointer as the one we're creating a NativeObjectWrapper for, + // unwrap it. We don't AddRef the wrapper as we don't take a reference to it. + // + // A managed object can have multiple managed object wrappers, with a max of one per context. + // Let's say we have a managed object A and ComWrappers instances C1 and C2. Let B1 and B2 be the + // managed object wrappers for A created with C1 and C2 respectively. + // If we are asked to create an EOC for B1 with the unwrap flag on the C2 ComWrappers instance, + // we will create a new wrapper. In this scenario, we'll only unwrap B2. + object unwrapped = ComInterfaceDispatch.GetInstance(comInterfaceDispatch); + if (_managedObjectWrapperTable.TryGetValue(unwrapped, out ManagedObjectWrapperHolder? unwrappedWrapperInThisContext)) + { + // 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. + // Don't unwrap Activated wrappers, as we shouldn't peer through COM Activation. + if (unwrappedWrapperInThisContext.ComIp == identity + && !unwrappedWrapperInThisContext.IsActivated) + { + retValue = unwrapped; + return true; + } + } + } + } + + // If the user didn't provide a wrapper and couldn't unwrap a managed object wrapper, + // create a new wrapper. + retValue = CreateAndRegisterObjectForComInstance(identity, inner, flags, ref referenceTrackerMaybe); + return retValue is not null; + } + finally + { + // Releasing a native object can never throw (it's a native call, so exceptions can't + // go through the ABI, it'd just crash the whole process). So we can use a single + // 'finally' block to release both native pointers we're holding in this scope. + Marshal.Release(identity); + + if (referenceTrackerMaybe != IntPtr.Zero) + { + Marshal.Release(referenceTrackerMaybe); + } + } + } + + private object? CreateAndRegisterObjectForComInstance( + IntPtr identity, + IntPtr inner, + CreateObjectFlags flags, + ref IntPtr referenceTrackerMaybe) + { + object? retValue = CreateObject(identity, flags); + if (retValue is null) + { + // If ComWrappers instance cannot create wrapper, we can do nothing here. + return null; + } + + return RegisterObjectForComInstance(identity, inner, retValue, flags, ref referenceTrackerMaybe); + } + + private object RegisterObjectForComInstance( + IntPtr identity, + IntPtr inner, + object comProxy, + CreateObjectFlags flags, + ref IntPtr referenceTrackerMaybe) + { + NativeObjectWrapper nativeObjectWrapper = NativeObjectWrapper.Create( + identity, + inner, + this, + comProxy, + flags, + ref referenceTrackerMaybe); + + object actualProxy = comProxy; + NativeObjectWrapper actualWrapper = nativeObjectWrapper; + if (!nativeObjectWrapper.IsUniqueInstance) + { + // Add our entry to the cache here, using an already existing entry if someone else beat us to it. + (actualWrapper, actualProxy) = _rcwCache.GetOrAddProxyForComInstance(identity, nativeObjectWrapper, comProxy); + if (actualWrapper != nativeObjectWrapper) + { + // We raced with another thread to map identity to nativeObjectWrapper + // and lost the race. We will use the other thread's nativeObjectWrapper, so we can release ours. + nativeObjectWrapper.Release(); + } + } + + // At this point, actualProxy is the RCW object for the identity + // and actualWrapper is the NativeObjectWrapper that is in the RCW cache (if not unique) that associates the identity with actualProxy. + // Register the NativeObjectWrapper to handle lifetime tracking of the references to the COM object. + RegisterWrapperForObject(actualWrapper, actualProxy); + + return actualProxy; + } + + private void RegisterWrapperForObject(NativeObjectWrapper wrapper, object comProxy) + { + // When we call into RegisterWrapperForObject, there is only one valid non-"unique instance" wrapper for a given + // COM instance, which is already registered in the RCW cache. + // If we find a wrapper in the table that is a different NativeObjectWrapper instance + // then it must be for a different COM instance. + // It's possible that we could race here with another thread that is trying to register the same comProxy + // for the same COM instance, but in that case we'll be passed the same NativeObjectWrapper instance + // for both threads. In that case, it doesn't matter which thread adds the entry to the NativeObjectWrapper table + // as the entry is always the same pair. + Debug.Assert(wrapper.ProxyHandle.Target == comProxy); + Debug.Assert(wrapper.IsUniqueInstance || _rcwCache.FindProxyForComInstance(wrapper.ExternalComObject) == comProxy); + + // Add the input wrapper bound to the COM proxy, if there isn't one already. If another thread raced + // against this one and this lost, we'd get the wrapper added from that thread instead. + NativeObjectWrapper registeredWrapper = s_nativeObjectWrapperTable.GetOrAdd(comProxy, wrapper); + + // We lost the race, so we cannot register the incoming wrapper with the target object + if (registeredWrapper != wrapper) + { + Debug.Assert(registeredWrapper.ExternalComObject != wrapper.ExternalComObject); + wrapper.Release(); + throw new NotSupportedException(); + } + + // Always register our wrapper to the reference tracker handle cache here. + // We may not be the thread that registered the handle, but we need to ensure that the wrapper + // is registered before we return to user code. Otherwise the wrapper won't be walked by the + // TrackerObjectManager and we could end up missing a section of the object graph. + // This cache deduplicates, so it is okay that the wrapper will be registered multiple times. + AddWrapperToReferenceTrackerHandleCache(registeredWrapper); + } + + private static void AddWrapperToReferenceTrackerHandleCache(NativeObjectWrapper wrapper) + { + if (wrapper is ReferenceTrackerNativeObjectWrapper referenceTrackerNativeObjectWrapper) + { + TrackerObjectManager.s_referenceTrackerNativeObjectWrapperCache.Add(referenceTrackerNativeObjectWrapper._nativeObjectWrapperWeakHandle); + } + } + + private sealed class RcwCache + { + private readonly Lock _lock = new Lock(useTrivialWaits: true); + private readonly Dictionary _cache = []; /// - /// Memory must have the same lifetime as the memory returned from the call to . + /// Gets the current RCW proxy object for if it exists in the cache or inserts a new entry with . /// - public IntPtr Vtable; + /// The com instance we want to get or record an RCW for. + /// The for . + /// The proxy object that is associated with . + /// The proxy object currently in the cache for or the proxy object owned by if no entry exists and the corresponding native wrapper. + public (NativeObjectWrapper actualWrapper, object actualProxy) GetOrAddProxyForComInstance(IntPtr comPointer, NativeObjectWrapper wrapper, object comProxy) + { + lock (_lock) + { + Debug.Assert(wrapper.ProxyHandle.Target == comProxy); + ref GCHandle rcwEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(_cache, comPointer, out bool exists); + if (!exists) + { + // Someone else didn't beat us to adding the entry to the cache. + // Add our entry here. + rcwEntry = GCHandle.Alloc(wrapper, GCHandleType.Weak); + } + else if (rcwEntry.Target is not (NativeObjectWrapper cachedWrapper)) + { + Debug.Assert(rcwEntry.IsAllocated); + // The target was collected, so we need to update the cache entry. + rcwEntry.Target = wrapper; + } + else + { + object? existingProxy = cachedWrapper.ProxyHandle.Target; + // The target NativeObjectWrapper was not collected, but we need to make sure + // that the proxy object is still alive. + if (existingProxy is not null) + { + // The existing proxy object is still alive, we will use that. + return (cachedWrapper, existingProxy); + } + + // The proxy object was collected, so we need to update the cache entry. + rcwEntry.Target = wrapper; + } + + // We either added an entry to the cache or updated an existing entry that was dead. + // Return our target object. + return (wrapper, comProxy); + } + } + + public object? FindProxyForComInstance(IntPtr comPointer) + { + lock (_lock) + { + if (_cache.TryGetValue(comPointer, out GCHandle existingHandle)) + { + if (existingHandle.Target is NativeObjectWrapper { ProxyHandle.Target: object cachedProxy }) + { + // The target exists and is still alive. Return it. + return cachedProxy; + } + + // The target was collected, so we need to remove the entry from the cache. + _cache.Remove(comPointer); + existingHandle.Free(); + } + + return null; + } + } + + public void Remove(IntPtr comPointer, NativeObjectWrapper wrapper) + { + lock (_lock) + { + // 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 finalizer ran. + // Only remove the entry if the target of the GC handle is the NativeObjectWrapper + // or is null (indicating that the corresponding NativeObjectWrapper has been scheduled for finalization). + if (_cache.TryGetValue(comPointer, out GCHandle cachedRef) + && (wrapper == cachedRef.Target + || cachedRef.Target is null)) + { + _cache.Remove(comPointer); + cachedRef.Free(); + } + } + } } + /// - /// ABI for function dispatch of a COM interface. + /// Register a instance to be used as the global instance for reference tracker support. /// - public partial struct ComInterfaceDispatch + /// Instance to register + /// + /// This function can only be called a single time. Subsequent calls to this function will result + /// in a being thrown. + /// + /// Scenarios where this global instance may be used are: + /// * Object tracking via the and flags. + /// + public static void RegisterForTrackerSupport(ComWrappers instance) { - public IntPtr Vtable; + ArgumentNullException.ThrowIfNull(instance); + + if (null != Interlocked.CompareExchange(ref s_globalInstanceForTrackerSupport, instance, null)) + { + throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); + } + } + + /// + /// Register a instance to be used as the global instance for marshalling in the runtime. + /// + /// Instance to register + /// + /// This function can only be called a single time. Subsequent calls to this function will result + /// in a being thrown. + /// + /// Scenarios where this global instance may be used are: + /// * Usage of COM-related Marshal APIs + /// * P/Invokes with COM-related types + /// * COM activation + /// + [SupportedOSPlatformAttribute("windows")] + public static void RegisterForMarshalling(ComWrappers instance) + { + ArgumentNullException.ThrowIfNull(instance); + + if (null != Interlocked.CompareExchange(ref s_globalInstanceForMarshalling, instance, null)) + { + throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); + } } /// @@ -130,6 +1395,364 @@ public partial struct ComInterfaceDispatch /// Called when a request is made for a collection of objects to be released outside of normal object or COM interface lifetime. /// /// Collection of objects to release. - protected abstract void ReleaseObjects(IEnumerable objects); + protected internal abstract void ReleaseObjects(IEnumerable objects); + + internal static IntPtr ComInterfaceForObject(object instance) + { + if (s_globalInstanceForMarshalling == null) + { + throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperInstance); + } + + return s_globalInstanceForMarshalling.GetOrCreateComInterfaceForObject(instance, CreateComInterfaceFlags.None); + } + + internal static unsafe IntPtr ComInterfaceForObject(object instance, Guid targetIID) + { + IntPtr unknownPtr = ComInterfaceForObject(instance); + IntPtr comObjectInterface; + ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)unknownPtr); + int resultCode = wrapper->QueryInterface(in targetIID, out comObjectInterface); + // We no longer need IUnknownPtr, release reference + Marshal.Release(unknownPtr); + if (resultCode != 0) + { + throw new InvalidCastException(); + } + + return comObjectInterface; + } + + internal static object ComObjectForInterface(IntPtr externalComObject) + { + if (s_globalInstanceForMarshalling == null) + { + throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperInstance); + } + + // 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); + } + + // Lifetime maintained by stack - we don't care about ref counts + [UnmanagedCallersOnly] + internal static unsafe uint Untracked_AddRef(IntPtr _) + { + return 1; + } + + [UnmanagedCallersOnly] + internal static unsafe uint Untracked_Release(IntPtr _) + { + return 1; + } + + // 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, object? context) + { + if (context is not ComWrappers comWrappers) + { + 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, IID_IUnknown, out IntPtr targetIdentityPtr) == HResults.S_OK) + { + using ComHolder targetIdentity = new ComHolder(targetIdentityPtr); + return GetOrCreateObjectFromWrapper(comWrappers, 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_nativeObjectWrapperTable.TryGetValue(target, out NativeObjectWrapper? wrapper) && !wrapper.IsAggregatedWithManagedObjectWrapper; + } + + private static unsafe IntPtr ObjectToComWeakRef(object target, out object? context) + { + context = null; + if (TryGetComInstanceForIID( + target, + IID_IWeakReferenceSource, + out IntPtr weakReferenceSourcePtr, + out ComWrappers? wrapper)) + { + context = wrapper; + using ComHolder weakReferenceSource = new ComHolder(weakReferenceSourcePtr); + if (IWeakReferenceSource.GetWeakReference(weakReferenceSource.Ptr, out IntPtr weakReference) == HResults.S_OK) + { + return weakReference; + } + } + + return IntPtr.Zero; + } + } + + // This is a GCHandle HashSet implementation based on LowLevelDictionary. + // It uses no locking for readers. While for writers (add / remove), + // it handles the locking itself. + // This implementation specifically makes sure that any readers of this + // collection during GC aren't impacted by other threads being + // frozen while in the middle of an write. It makes no guarantees on + // whether you will observe the element being added / removed, but does + // make sure the collection is in a good state and doesn't run into issues + // while iterating. + internal sealed class GCHandleSet : IEnumerable + { + private const int DefaultSize = 7; + + private Entry?[] _buckets = new Entry[DefaultSize]; + private int _numEntries; + private readonly Lock _lock = new Lock(useTrivialWaits: true); + + public Lock ModificationLock => _lock; + + public void Add(GCHandle handle) + { + using (_lock.EnterScope()) + { + int bucket = GetBucket(handle, _buckets.Length); + Entry? prev = null; + Entry? entry = _buckets[bucket]; + while (entry != null) + { + // Handle already exists, nothing to add. + if (handle.Equals(entry._value)) + { + return; + } + + prev = entry; + entry = entry._next; + } + + Entry newEntry = new Entry() + { + _value = handle + }; + + if (prev == null) + { + _buckets[bucket] = newEntry; + } + else + { + prev._next = newEntry; + } + + // _numEntries is only maintained for the purposes of deciding whether to + // expand the bucket and is not used during iteration to handle the + // scenario where element is in bucket but _numEntries hasn't been incremented + // yet. + _numEntries++; + if (_numEntries > (_buckets.Length * 2)) + { + ExpandBuckets(); + } + } + } + + private void ExpandBuckets() + { + int newNumBuckets = _buckets.Length * 2 + 1; + Entry?[] newBuckets = new Entry[newNumBuckets]; + for (int i = 0; i < _buckets.Length; i++) + { + Entry? entry = _buckets[i]; + while (entry != null) + { + Entry? nextEntry = entry._next; + + int bucket = GetBucket(entry._value, newNumBuckets); + + // We are allocating new entries for the bucket to ensure that + // if there is an enumeration already in progress, we don't + // modify what it observes by changing next in existing instances. + Entry newEntry = new Entry() + { + _value = entry._value, + _next = newBuckets[bucket], + }; + newBuckets[bucket] = newEntry; + + entry = nextEntry; + } + } + _buckets = newBuckets; + } + + public void Remove(GCHandle handle) + { + using (_lock.EnterScope()) + { + int bucket = GetBucket(handle, _buckets.Length); + Entry? prev = null; + Entry? entry = _buckets[bucket]; + while (entry != null) + { + if (handle.Equals(entry._value)) + { + if (prev == null) + { + _buckets[bucket] = entry._next; + } + else + { + prev._next = entry._next; + } + _numEntries--; + return; + } + + prev = entry; + entry = entry._next; + } + } + } + + private static int GetBucket(GCHandle handle, int numBuckets) + { + int h = handle.GetHashCode(); + return (int)((uint)h % (uint)numBuckets); + } + + public Enumerator GetEnumerator() => new Enumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + + private sealed class Entry + { + public Entry? _next; + public GCHandle _value; + } + + public struct Enumerator : IEnumerator + { + private readonly Entry?[] _buckets; + private Entry? _currentEntry; + private int _currentIdx; + + public Enumerator(GCHandleSet set) + { + // We hold onto the buckets of the set rather than the set itself + // so that if it is ever expanded, we are not impacted by that during + // enumeration. + _buckets = set._buckets; + Reset(); + } + + public GCHandle Current + { + get + { + if (_currentEntry == null) + { + throw new InvalidOperationException("InvalidOperation_EnumOpCantHappen"); + } + + return _currentEntry._value; + } + } + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_currentEntry != null) + { + _currentEntry = _currentEntry._next; + } + + if (_currentEntry == null) + { + // Certain buckets might be empty, so loop until we find + // one with an entry. + while (++_currentIdx != _buckets.Length) + { + _currentEntry = _buckets[_currentIdx]; + if (_currentEntry != null) + { + return true; + } + } + + return false; + } + + return true; + } + + public void Reset() + { + _currentIdx = -1; + _currentEntry = null; + } + } + } + + 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); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateComInterfaceFlags.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateComInterfaceFlags.cs new file mode 100644 index 00000000000000..8d9847b8dfd64e --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateComInterfaceFlags.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + /// + /// Enumeration of flags for . + /// + [Flags] + public enum CreateComInterfaceFlags + { + None = 0, + + /// + /// The caller will provide an IUnknown Vtable. + /// + /// + /// This is useful in scenarios when the caller has no need to rely on an IUnknown instance + /// that is used when running managed code is not possible (i.e. during a GC). In traditional + /// COM scenarios this is common, but scenarios involving Reference Tracker hosting + /// calling of the IUnknown API during a GC is possible. + /// + CallerDefinedIUnknown = 1, + + /// + /// Flag used to indicate the COM interface should implement IReferenceTrackerTarget. + /// When this flag is passed, the resulting COM interface will have an internal implementation of IUnknown + /// and as such none should be supplied by the caller. + /// + TrackerSupport = 2, + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateObjectFlags.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateObjectFlags.cs new file mode 100644 index 00000000000000..337e955a57e708 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateObjectFlags.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Runtime.Versioning; + +namespace System.Runtime.InteropServices +{ + + /// + /// Enumeration of flags for . + /// + [Flags] + public enum CreateObjectFlags + { + None = 0, + + /// + /// Indicate if the supplied external COM object implements the IReferenceTracker. + /// + TrackerObject = 1, + + /// + /// Ignore any internal caching and always create a unique instance. + /// + UniqueInstance = 2, + + /// + /// Defined when COM aggregation is involved (that is an inner instance supplied). + /// + Aggregation = 4, + + /// + /// Check if the supplied instance is actually a wrapper and if so return the underlying + /// managed object rather than creating a new wrapper. + /// + /// + /// This matches the built-in RCW semantics for COM interop. + /// + Unwrap = 8, + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/IReferenceTracker.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/IReferenceTracker.cs new file mode 100644 index 00000000000000..9d56d6945761aa --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/IReferenceTracker.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + // 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)); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs new file mode 100644 index 00000000000000..a715c4e772d0a9 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs @@ -0,0 +1,208 @@ +// 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; + +namespace System.Runtime.InteropServices +{ + internal static class ReferenceTrackerHost + { + [FixedAddressValueType] + private static readonly unsafe IntPtr s_globalHostServices = (IntPtr)Unsafe.AsPointer(in HostServices.Vftbl); + + // Called when an IReferenceTracker instance is found. + public static unsafe void SetReferenceTrackerHost(IntPtr trackerManager) + { + IReferenceTrackerManager.SetReferenceTrackerHost(trackerManager, (IntPtr)Unsafe.AsPointer(in s_globalHostServices)); + } + +#pragma warning disable IDE0060 + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_DisconnectUnusedReferenceSources(IntPtr pThis, uint flags) +#pragma warning restore IDE0060 + { + try + { + // Defined in windows.ui.xaml.hosting.referencetracker.h. + const uint XAML_REFERENCETRACKER_DISCONNECT_SUSPEND = 0x00000001; + + if ((flags & XAML_REFERENCETRACKER_DISCONNECT_SUSPEND) != 0) + { + GC.Collect(2, GCCollectionMode.Optimized, blocking: true, compacting: false, lowMemoryPressure: true); + } + else + { + GC.Collect(); + } + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + +#pragma warning disable IDE0060 + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_ReleaseDisconnectedReferenceSources(IntPtr pThis) +#pragma warning restore IDE0060 + { + // We'd like to call GC.WaitForPendingFinalizers() here, but this could lead to deadlock + // if the finalizer thread is trying to get back to this thread, because we are not pumping + // anymore. Disable this for now. See: https://github.com/dotnet/runtime/issues/109538. + return HResults.S_OK; + } + +#pragma warning disable IDE0060 + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnThread(IntPtr pThis) +#pragma warning restore IDE0060 + { + try + { + TrackerObjectManager.ReleaseExternalObjectsFromCurrentThread(); + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + +#pragma warning disable IDE0060 + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_GetTrackerTarget(IntPtr pThis, IntPtr punk, IntPtr* ppNewReference) +#pragma warning restore IDE0060 + { + if (punk == IntPtr.Zero) + { + return HResults.E_INVALIDARG; + } + + if (Marshal.QueryInterface(punk, ComWrappers.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(ComWrappers.GetOrCreateTrackerTarget(identity.Ptr)); + return Marshal.QueryInterface(trackerTarget.Ptr, ComWrappers.IID_IReferenceTrackerTarget, out *ppNewReference); + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + +#pragma warning disable IDE0060 + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_AddMemoryPressure(IntPtr pThis, long bytesAllocated) +#pragma warning restore IDE0060 + { + try + { + GC.AddMemoryPressure(bytesAllocated); + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + +#pragma warning disable IDE0060 + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_RemoveMemoryPressure(IntPtr pThis, long bytesAllocated) +#pragma warning restore IDE0060 + { + try + { + GC.RemoveMemoryPressure(bytesAllocated); + return HResults.S_OK; + } + catch (Exception e) + { + return Marshal.GetHRForException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) + { + if (*guid == ComWrappers.IID_IReferenceTrackerHost || *guid == ComWrappers.IID_IUnknown) + { + *ppObject = pThis; + Marshal.AddRef(pThis); + return HResults.S_OK; + } + else + { + return HResults.COR_E_INVALIDCAST; + } + } + + private unsafe struct IReferenceTrackerHostVftbl + { + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; + public delegate* unmanaged DisconnectUnusedReferenceSources; + public delegate* unmanaged ReleaseDisconnectedReferenceSources; + public delegate* unmanaged NotifyEndOfReferenceTrackingOnThread; + public delegate* unmanaged GetTrackerTarget; + public delegate* unmanaged AddMemoryPressure; + public delegate* unmanaged RemoveMemoryPressure; + } + + private static class HostServices + { + [FixedAddressValueType] + public static readonly IReferenceTrackerHostVftbl Vftbl; + + static unsafe HostServices() + { + Vftbl.QueryInterface = &IReferenceTrackerHost_QueryInterface; + Vftbl.AddRef = &ComWrappers.Untracked_AddRef; + Vftbl.Release = &ComWrappers.Untracked_Release; + Vftbl.DisconnectUnusedReferenceSources = &IReferenceTrackerHost_DisconnectUnusedReferenceSources; + Vftbl.ReleaseDisconnectedReferenceSources = &IReferenceTrackerHost_ReleaseDisconnectedReferenceSources; + Vftbl.NotifyEndOfReferenceTrackingOnThread = &IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnThread; + Vftbl.GetTrackerTarget = &IReferenceTrackerHost_GetTrackerTarget; + Vftbl.AddMemoryPressure = &IReferenceTrackerHost_AddMemoryPressure; + Vftbl.RemoveMemoryPressure = &IReferenceTrackerHost_RemoveMemoryPressure; + } + } + } + + // 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)); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs new file mode 100644 index 00000000000000..df0053629c9ca6 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; +using static System.Runtime.InteropServices.ComWrappers; + +namespace System.Runtime.InteropServices +{ + internal static partial class TrackerObjectManager + { + internal static readonly GCHandleSet s_referenceTrackerNativeObjectWrapperCache = new GCHandleSet(); + + internal static void OnIReferenceTrackerFound(IntPtr referenceTracker) + { + Debug.Assert(referenceTracker != IntPtr.Zero); + if (HasReferenceTrackerManager) + { + return; + } + + IReferenceTracker.GetReferenceTrackerManager(referenceTracker, out IntPtr referenceTrackerManager); + + // Attempt to set the tracker instance. + // If set, the ownership of referenceTrackerManager has been transferred + if (TryRegisterReferenceTrackerManager(referenceTrackerManager)) + { + ReferenceTrackerHost.SetReferenceTrackerHost(referenceTrackerManager); + RegisterGCCallbacks(); + } + else + { + Marshal.Release(referenceTrackerManager); + } + } + + internal 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 + } + + internal static void ReleaseExternalObjectsFromCurrentThread() + { + if (GlobalInstanceForTrackerSupport == null) + { + throw new NotSupportedException(SR.InvalidOperation_ComInteropRequireComWrapperTrackerInstance); + } + + IntPtr contextToken = GetContextToken(); + + List objects = new List(); + + // Here we aren't part of a GC callback, so other threads can still be running + // who are adding and removing from the collection. This means we can possibly race + // with a handle being removed and freed and we can end up accessing a freed handle. + // To avoid this, we take a lock on modifications to the collection while we gather + // the objects. + using (s_referenceTrackerNativeObjectWrapperCache.ModificationLock.EnterScope()) + { + foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) + { + ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); + if (nativeObjectWrapper != null && + nativeObjectWrapper._contextToken == contextToken) + { + object? target = nativeObjectWrapper.ProxyHandle.Target; + if (target != null) + { + objects.Add(target); + } + + // Separate the wrapper from the tracker runtime prior to + // passing them. + nativeObjectWrapper.DisconnectTracker(); + } + } + } + + GlobalInstanceForTrackerSupport.ReleaseObjects(objects); + } + + internal static IntPtr GetContextToken() + { +#if TARGET_WINDOWS + Interop.Ole32.CoGetContextToken(out IntPtr contextToken); + return contextToken; +#else + return IntPtr.Zero; +#endif + } + } +} diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index 8bf32c97f6a347..a70277b0f85266 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -68,6 +68,7 @@ private static int Main() TestVTableNegativeScenarios.Run(); TestByRefFieldAddressEquality.Run(); TestComInterfaceEntry.Run(); + TestPreinitializedBclTypes.Run(); #else Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test."); #endif @@ -2107,6 +2108,16 @@ public static void Run() } } +unsafe class TestPreinitializedBclTypes +{ + // Verify that (given that all of the other tests have passed), that a select number of BCL types + // that depend on this optimization for high-performance scenarios are preinitialized. + public static void Run() + { + Assert.IsPreinitialized(Type.GetType("System.Runtime.InteropServices.ComWrappers+VtableImplementations, System.Private.CoreLib")); + } +} + static class Assert { [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",