From d673663f963084f66c387cd4672bc3e7f9134376 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 17 Feb 2025 17:24:05 -0800 Subject: [PATCH 01/38] Various fixes to get tests passing Move portions of ComWrappers.NativeAot.cs that aren't run during GC into ComWrappers.Common.cs Convert CoreCLR to use the managed ComWrappers implementation wherever possible (everywhere except for during GC) Move wrapper ID to be a CoreCLR-only concept (as it's only needed for diagnostics support) --- .../System.Private.CoreLib.csproj | 1 + .../System/ComAwareWeakReference.CoreCLR.cs | 35 +- .../src/System/GC.CoreCLR.cs | 14 +- .../Runtime/InteropServices/ComWrappers.cs | 387 +-- .../TrackerObjectManager.CoreCLR.cs | 48 + src/coreclr/debug/daccess/request.cpp | 98 +- src/coreclr/debug/ee/dactable.cpp | 11 +- src/coreclr/interop/comwrappers.cpp | 457 +--- src/coreclr/interop/comwrappers.hpp | 158 +- src/coreclr/interop/inc/interoplib.h | 116 +- src/coreclr/interop/inc/interoplibabi.h | 22 +- src/coreclr/interop/inc/interoplibimports.h | 55 +- src/coreclr/interop/interoplib.cpp | 197 +- src/coreclr/interop/trackerobjectmanager.cpp | 231 +- .../src/System.Private.CoreLib.csproj | 11 +- .../src/System/GC.NativeAot.cs | 7 +- .../ComAwareWeakReference.NativeAot.cs | 27 + .../InteropServices/ComWrappers.NativeAot.cs | 1907 +------------- .../TrackerObjectManager.NativeAot.cs | 203 +- src/coreclr/vm/binder.cpp | 1 + src/coreclr/vm/comutilnative.cpp | 4 +- src/coreclr/vm/comutilnative.h | 2 +- src/coreclr/vm/corelib.cpp | 4 + src/coreclr/vm/corelib.h | 31 +- src/coreclr/vm/gcenv.ee.cpp | 6 +- src/coreclr/vm/interoplibinterface.h | 103 +- .../vm/interoplibinterface_comwrappers.cpp | 2273 ++++------------- .../vm/interoplibinterface_comwrappers.h | 176 ++ src/coreclr/vm/interoplibinterface_shared.cpp | 4 +- src/coreclr/vm/interoputil.cpp | 8 - src/coreclr/vm/metasig.h | 6 +- src/coreclr/vm/qcallentrypoints.cpp | 16 +- src/coreclr/vm/syncblk.h | 61 - src/coreclr/vm/weakreferencenative.cpp | 49 +- src/coreclr/vm/weakreferencenative.h | 4 +- .../System.Private.CoreLib.Shared.projitems | 10 + .../src/System/ComAwareWeakReference.cs | 12 +- .../ComAwareWeakReference.ComWrappers.cs} | 23 +- .../InteropServices/ComWrappers.Common.cs | 1721 +++++++++++++ .../Runtime/InteropServices/ComWrappers.cs | 2 +- .../InteropServices/IReferenceTracker.cs | 47 + .../InteropServices/ReferenceTrackerHost.cs | 209 ++ .../InteropServices/TrackerObjectManager.cs | 113 + 43 files changed, 3354 insertions(+), 5516 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.CoreCLR.cs create mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.NativeAot.cs create mode 100644 src/coreclr/vm/interoplibinterface_comwrappers.h rename src/{coreclr/nativeaot/System.Private.CoreLib/src/System/ComAwareWeakReference.NativeAot.cs => libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComAwareWeakReference.ComWrappers.cs} (63%) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/IReferenceTracker.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index b77d7f3f44a1f0..de06d0d1d45fa0 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -242,6 +242,7 @@ + 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..4c0e872fab7360 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -10,13 +10,19 @@ 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 (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; + } + + return ComWeakRefToComWrappersObject(pComWeakRef, context); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -40,17 +46,24 @@ internal static unsafe bool PossiblyComObject(object target) 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 (!HasInteropInfo(target)) + { + context = null; + return IntPtr.Zero; + } + + if (target is __ComObject) { - return ObjectToComWeakRef(ObjectHandleOnStack.Create(ref target), out wrapperId); + // 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)); } - wrapperId = 0; - return IntPtr.Zero; + return ComWrappersObjectToComWeakRef(target, out context); } } } 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.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index c7c78a7325f998..0df7a9ed9ddf8b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -1,396 +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; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.Versioning; using System.Threading; +using System.Runtime.CompilerServices; 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. + /// Get the runtime provided IUnknown implementation. /// - private static ComWrappers? s_globalInstanceForTrackerSupport; + /// Function pointer to QueryInterface. + /// Function pointer to AddRef. + /// Function pointer to Release. + public static unsafe partial void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) + => GetIUnknownImplInternal(out fpQueryInterface, out fpAddRef, out fpRelease); - /// - /// Globally registered instance of the ComWrappers class for marshalling. - /// - private static ComWrappers? s_globalInstanceForMarshalling; + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIUnknownImpl")] + private static partial void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); 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) + unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper) { - ArgumentNullException.ThrowIfNull(instance); - - return TryGetOrCreateComInterfaceForObjectInternal(ObjectHandleOnStack.Create(ref impl), impl.id, ObjectHandleOnStack.Create(ref instance), flags, out retValue); + RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref instance), wrapper, id); } - [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); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterManagedObjectWrapperForDiagnostics")] + private static unsafe partial void RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack instance, ManagedObjectWrapper* wrapper, long wrapperId); - // 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) + static partial void RegisterNativeObjectWrapperForDiagnostics(NativeObjectWrapper registeredWrapper) { - 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); + object target = registeredWrapper.ProxyHandle.Target!; + RegisterNativeObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref target), ObjectHandleOnStack.Create(ref registeredWrapper)); } - /// - /// 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!; - } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterNativeObjectWrapperForDiagnostics")] + private static unsafe partial void RegisterNativeObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack target, ObjectHandleOnStack wrapper); - // Called by the runtime to execute the abstract instance function. - internal static object? CallCreateObject(ComWrappersScenario scenario, ComWrappers? comWrappersImpl, IntPtr externalComObject, CreateObjectFlags flags) + internal static int CallICustomQueryInterface(ManagedObjectWrapperHolder holder, ref Guid iid, out IntPtr ppObject) { - ComWrappers? impl = null; - switch (scenario) + if (holder.WrappedObject is ICustomQueryInterface customQueryInterface) { - case ComWrappersScenario.Instance: - impl = comWrappersImpl; - break; - case ComWrappersScenario.TrackerSupportGlobalInstance: - impl = s_globalInstanceForTrackerSupport; - break; - case ComWrappersScenario.MarshallingGlobalInstance: - impl = s_globalInstanceForMarshalling; - break; + return (int)customQueryInterface.GetInterface(ref iid, out ppObject); } - 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); + ppObject = IntPtr.Zero; + return -1; // See TryInvokeICustomQueryInterfaceResult } - /// - /// 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) + internal static IntPtr GetOrCreateComInterfaceForObjectWithGlobalMarshallingInstance(object obj) { - ArgumentNullException.ThrowIfNull(wrapper); - - object? obj; - if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, inner, flags, wrapper, out obj)) - throw new ArgumentNullException(nameof(externalComObject)); - - return obj!; + return s_globalInstanceForMarshalling is null + ? IntPtr.Zero + : s_globalInstanceForMarshalling.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.TrackerSupport); } - /// - /// 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) + internal static object? GetOrCreateObjectForComInstanceWithGlobalMarshallingInstance(IntPtr comObject, CreateObjectFlags flags) { - 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)); + return s_globalInstanceForMarshalling?.GetOrCreateObjectForComInstance(comObject, flags); } - [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); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIReferenceTrackerTargetVftbl")] + [SuppressGCTransition] + private static unsafe partial IntPtr GetDefaultIReferenceTrackerTargetVftbl(); - // Call to execute the virtual instance function - internal static void CallReleaseObjects(ComWrappers? comWrappersImpl, IEnumerable objects) - => (comWrappersImpl ?? s_globalInstanceForTrackerSupport!).ReleaseObjects(objects); + private static unsafe partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl() + => GetDefaultIReferenceTrackerTargetVftbl(); - /// - /// 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) + private static partial IntPtr GetTaggedImplCurrentVersion() { - ArgumentNullException.ThrowIfNull(instance); - - if (null != Interlocked.CompareExchange(ref s_globalInstanceForTrackerSupport, instance, null)) - { - throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); - } - - SetGlobalInstanceRegisteredForTrackerSupport(instance.id); + GetTaggedImpl(out IntPtr fpCurrentVersion); + return fpCurrentVersion; } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_SetGlobalInstanceRegisteredForTrackerSupport")] + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetTaggedImpl")] [SuppressGCTransition] - private static partial void SetGlobalInstanceRegisteredForTrackerSupport(long id); + private static partial void GetTaggedImpl(out IntPtr fpCurrentVersion); - /// - /// 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) + internal sealed unsafe partial class ManagedObjectWrapperHolder { - ArgumentNullException.ThrowIfNull(instance); - - if (null != Interlocked.CompareExchange(ref s_globalInstanceForMarshalling, instance, null)) + static partial void RegisterIsRootedCallback() { - throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); + RegisterIsRootedCallbackInternal(); } - // 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_RegisterIsRootedCallback")] + private static partial void RegisterIsRootedCallbackInternal(); - [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) + private static partial IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) { - return (int)customQueryInterface.GetInterface(ref iid, out ppObject); + return AllocateRefCountedHandle(ObjectHandleOnStack.Create(ref holder)); } - ppObject = IntPtr.Zero; - return -1; // See TryInvokeICustomQueryInterfaceResult + [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/TrackerObjectManager.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.CoreCLR.cs new file mode 100644 index 00000000000000..b05be951aee519 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.CoreCLR.cs @@ -0,0 +1,48 @@ +// 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 partial bool HasReferenceTrackerManager + => HasReferenceTrackerManagerInternal(); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_HasReferenceTrackerManager")] + [SuppressGCTransition] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool HasReferenceTrackerManagerInternal(); + + private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager) + => TryRegisterReferenceTrackerManagerInternal(referenceTrackerManager); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_TryRegisterReferenceTrackerManager")] + [SuppressGCTransition] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool TryRegisterReferenceTrackerManagerInternal(IntPtr referenceTrackerManager); + + internal static partial bool IsGlobalPeggingEnabled + => IsGlobalPeggingEnabledInternal(); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_IsGlobalPeggingEnabled")] + [SuppressGCTransition] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool IsGlobalPeggingEnabledInternal(); + + static partial 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/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 06b00e4ffa75f2..25f2a14d44d241 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -23,7 +23,6 @@ #include #include -typedef DPTR(InteropLibInterface::ExternalObjectContextBase) PTR_ExternalObjectContext; typedef DPTR(InteropLib::ABI::ManagedObjectWrapperLayout) PTR_ManagedObjectWrapper; #endif // FEATURE_COMWRAPPERS @@ -5190,7 +5189,7 @@ HRESULT ClrDataAccess::GetComWrappersCCWData(CLRDATA_ADDRESS ccw, CLRDATA_ADDRES if (refCount != NULL) { - *refCount = (int)pMOW->RefCount; + *refCount = (int)pMOW->GetRawRefCount(); } } else @@ -5206,52 +5205,86 @@ 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) +namespace +{ + BOOL IsComWrappersRCW(CLRDATA_ADDRESS rcw) { - return E_INVALIDARG; - } - - SOSDacEnter(); + NewArrayHolder region; + unsigned int count = 0; - if (isComWrappersRCW != NULL) - { - PTR_ExternalObjectContext pRCW(TO_TADDR(rcw)); - BOOL stillValid = TRUE; - if(pRCW->SyncBlockIndex >= SyncBlockCache::s_pSyncBlockCache->m_SyncTableSize) { - stillValid = FALSE; + DacHandleTableMemoryEnumerator handleEnumerator; + handleEnumerator.Init(); + if (!handleEnumerator.GetCount(&count) || count == 0) + { + return FALSE; + } + region = new(nothrow) SOSMemoryRegion[count]; + if (handleEnumerator.Next(count, region, &count) != S_OK) + { + return FALSE; + } } - PTR_SyncBlock pSyncBlk = NULL; - if (stillValid) + BOOL isGCHandle = FALSE; + for (unsigned int i = 0; i < count; i++) { - PTR_SyncTableEntry ste = PTR_SyncTableEntry(dac_cast(g_pSyncTable) + (sizeof(SyncTableEntry) * pRCW->SyncBlockIndex)); - pSyncBlk = ste->m_SyncBlock; - if(pSyncBlk == NULL) + if (region[i].Start <= rcw && rcw < region[i].Start + region[i].Size) { - stillValid = FALSE; + isGCHandle = TRUE; + break; } } - PTR_InteropSyncBlockInfo pInfo = NULL; - if (stillValid) + if (!isGCHandle) { - pInfo = pSyncBlk->GetInteropInfoNoCreate(); - if(pInfo == NULL) - { - stillValid = FALSE; - } + return FALSE; } - if (stillValid) + OBJECTHANDLE nativeObjectWrapperHandle = (OBJECTHANDLE)TO_TADDR(rcw); + OBJECTREF nativeObjectWrapper = ObjectFromHandle(nativeObjectWrapperHandle); + if (nativeObjectWrapper == NULL) { - stillValid = TO_TADDR(pInfo->m_externalComObjectContext) == PTR_HOST_TO_TADDR(pRCW); + return FALSE; } - *isComWrappersRCW = stillValid; + if (nativeObjectWrapper->GetMethodTable() != (&g_CoreLib)->GetClass(CLASS__NATIVE_OBJECT_WRAPPER)) + { + return FALSE; + } + + return TRUE; + } + + TADDR GetComWrappersRCWIdentity(CLRDATA_ADDRESS rcw) + { + OBJECTHANDLE nativeObjectWrapperHandle = (OBJECTHANDLE)TO_TADDR(rcw); + OBJECTREF nativeObjectWrapper = ObjectFromHandle(nativeObjectWrapperHandle); + if (nativeObjectWrapper == NULL) + { + return NULL; + } + + NATIVEOBJECTWRAPPERREF pNativeObjectWrapper = NATIVEOBJECTWRAPPERREF(nativeObjectWrapper); + 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; } @@ -5272,10 +5305,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..ad7e56dc455a66 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -11,9 +11,26 @@ #endif // _WIN32 using OBJECTHANDLE = InteropLib::OBJECTHANDLE; -using AllocScenario = InteropLibImports::AllocScenario; using TryInvokeICustomQueryInterfaceResult = InteropLibImports::TryInvokeICustomQueryInterfaceResult; +namespace InteropLib +{ + namespace ABI + { + struct ComInterfaceDispatch + { + const void* vtable; + }; + ABI_ASSERT(sizeof(ComInterfaceDispatch) == sizeof(void*)); + + struct ComInterfaceEntry + { + GUID IID; + const void* Vtable; + }; + } +} + namespace ABI { //--------------------------------------------------------------------------------- @@ -44,12 +61,8 @@ 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); @@ -96,66 +109,6 @@ namespace ABI 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) { @@ -338,6 +291,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 +313,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 +329,11 @@ void ManagedObjectWrapper::GetIUnknownImpl( *fpRelease = (void*)ManagedObjectWrapper_IUnknownImpl.Release; } +void const* ManagedObjectWrapper::GetTaggedCurrentVersionImpl() noexcept +{ + return &ITaggedImpl_IsCurrentVersion; +} + // The logic here should match code:ClrDataAccess::DACTryGetComWrappersObjectFromCCW in daccess/request.cpp ManagedObjectWrapper* ManagedObjectWrapper::MapFromIUnknown(_In_ IUnknown* pUnk) { @@ -428,166 +375,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; - - // Check if the caller will provide the IUnknown table. - 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; - } + // The order of interface lookup here is important. + // See ComWrappers.CreateManagedObjectWrapper() for the expected order. + int i = _userDefinedCount; - // Always add the tagged interface. This is used to confirm at run-time with certainty - // the wrapper is created by the ComWrappers API. + if ((_flags & CreateComInterfaceFlagsEx::CallerDefinedIUnknown) == CreateComInterfaceFlagsEx::None) { - 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); - - // Attempt to set the destroyed bit. - LONGLONG refCount; - LONGLONG prev; - do - { - prev = wrapper->_refCount; - refCount = prev | DestroySentinel; - } while (InterlockedCompareExchange64(&wrapper->_refCount, refCount, prev) != prev); + return ABI::IndexIntoDispatchSection(i, _dispatches); + } - // 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 +415,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 +432,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 +500,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); } @@ -781,167 +598,3 @@ 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) -{ - _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(); -} diff --git a/src/coreclr/interop/comwrappers.hpp b/src/coreclr/interop/comwrappers.hpp index 47bf008ac50126..d8132c47946d3a 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); @@ -142,82 +73,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 HRESULT HasReferenceTrackerManager(); - // Called after wrapper has been created. - static HRESULT AfterWrapperCreated(_In_ IReferenceTracker* obj); + static HRESULT 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 +90,8 @@ class TrackerObjectManager // End the reference tracking process for external object. static HRESULT EndReferenceTracking(); + + static HRESULT DetachNonPromotedObjects(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..06e450e47cbb0e 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -5,6 +5,7 @@ #define _INTEROP_INC_INTEROPLIBABI_H_ #include +#include namespace InteropLib { @@ -19,12 +20,29 @@ namespace InteropLib const intptr_t DispatchThisPtrMask = ~(DispatchAlignmentThisPtr - 1); + struct ComInterfaceDispatch; + + struct ComInterfaceEntry; + // Managed object wrapper layout. // This is designed to codify the binary layout. struct ManagedObjectWrapperLayout { - PTR_VOID ManagedObject; - long long RefCount; + public: + Volatile Target; + + LONGLONG GetRawRefCount() const + { + return _refCount; + } + + protected: + LONGLONG _refCount; + + Volatile _flags; + const int32_t _userDefinedCount; + const ComInterfaceEntry* _userDefined; + ComInterfaceDispatch* _dispatches; }; } } diff --git a/src/coreclr/interop/inc/interoplibimports.h b/src/coreclr/interop/inc/interoplibimports.h index 57824c36d78caa..b8e8bc9979fa00 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; @@ -58,22 +26,15 @@ namespace InteropLibImports // S_FALSE - Iterator has reached end and context out parameter is set to NULL. HRESULT 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..928f037646bc0b 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() == S_OK; } - HRESULT BeginExternalObjectReferenceTracking(_In_ RuntimeCallContext* cxt) noexcept + bool TryRegisterReferenceTrackerManager(_In_ void* manager) noexcept { - return TrackerObjectManager::BeginReferenceTracking(cxt); + return TrackerObjectManager::TryRegisterReferenceTrackerManager((IReferenceTrackerManager*)manager) == S_OK; } - 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..c9f27f31b99d92 100644 --- a/src/coreclr/interop/trackerobjectmanager.cpp +++ b/src/coreclr/interop/trackerobjectmanager.cpp @@ -9,146 +9,12 @@ 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; @@ -160,17 +26,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,7 +55,7 @@ namespace // Notify the runtime a reference path was found. RETURN_IF_FAILED(InteropLibImports::FoundReferencePath( _runtimeCallCxt, - _nowCxt->GetRuntimeContext(), + _sourceHandle, mow->Target)); return S_OK; @@ -232,21 +98,16 @@ namespace BOOL walkFailed = FALSE; HRESULT hr; - void* extObjContext = nullptr; - while (S_OK == (hr = InteropLibImports::IteratorNext(cxt, &extObjContext))) + IReferenceTracker* trackerTarget = nullptr; + OBJECTHANDLE proxyObject = NULL; + while (S_OK == (hr = 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; } @@ -265,47 +126,15 @@ namespace } } -HRESULT TrackerObjectManager::OnIReferenceTrackerFound(_In_ IReferenceTracker* obj) +HRESULT 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) ? S_OK : S_FALSE; } -HRESULT TrackerObjectManager::AfterWrapperCreated(_In_ IReferenceTracker* obj) +HRESULT 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) ? S_OK : S_FALSE; } HRESULT TrackerObjectManager::BeforeWrapperFinalized(_In_ IReferenceTracker* obj) @@ -376,3 +205,29 @@ HRESULT TrackerObjectManager::EndReferenceTracking() return hr; } + +HRESULT TrackerObjectManager::DetachNonPromotedObjects(_In_ RuntimeCallContext* cxt) +{ + _ASSERTE(cxt != nullptr); + + HRESULT hr; + IReferenceTracker* trackerTarget = nullptr; + OBJECTHANDLE proxyObject = NULL; + while (S_OK == (hr = InteropLibImports::IteratorNext(cxt, (void**)&trackerTarget, &proxyObject))) + { + if (trackerTarget == nullptr) + continue; + + if (proxyObject == nullptr) + continue; + + RETURN_IF_FAILED(hr); + + 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 72df9073427635..4d327780fbff68 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 @@ -159,6 +159,7 @@ + @@ -169,7 +170,6 @@ - @@ -278,9 +278,6 @@ Interop\Windows\Ole32\Interop.CoGetApartmentType.cs - - Interop\Windows\Ole32\Interop.CoGetContextToken.cs - Interop\Windows\OleAut32\Interop.VariantClear.cs @@ -576,11 +573,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 0c3e6a7165b286..9cfd7ff42f1c5a 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,428 +18,9 @@ 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 + internal sealed unsafe partial class ManagedObjectWrapperHolder { - 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() + static partial void RegisterIsRootedCallback() { delegate* unmanaged callback = &IsRootedCallback; if (!RuntimeImports.RhRegisterRefCountedHandleCallback((nint)callback, MethodTable.Of())) @@ -456,875 +37,9 @@ private static bool IsRootedCallback(IntPtr 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)) + private static partial IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) { - throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); + return RuntimeImports.RhHandleAllocRefCounted(holder); } } @@ -1334,147 +49,13 @@ public static void RegisterForMarshalling(ComWrappers instance) /// 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) + public static unsafe partial void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) { fpQueryInterface = (IntPtr)(delegate* unmanaged)&ComWrappers.IUnknown_QueryInterface; fpAddRef = RuntimeImports.RhGetIUnknownAddRef(); // 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() - { - 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()) - { - 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(); - } - } - } - - s_globalInstanceForTrackerSupport.ReleaseObjects(objects); - } - - // Used during GC callback - internal static unsafe void WalkExternalTrackerObjects() - { - bool walkFailed = false; - - foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) - { - ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); - if (nativeObjectWrapper != null && - nativeObjectWrapper.TrackerObject != IntPtr.Zero) - { - FindReferenceTargetsCallback.s_currentRootObjectHandle = nativeObjectWrapper.ProxyHandle; - if (IReferenceTracker.FindTrackerTargets(nativeObjectWrapper.TrackerObject, TrackerObjectManager.s_findReferencesTargetCallback) != HResults.S_OK) - { - walkFailed = true; - FindReferenceTargetsCallback.s_currentRootObjectHandle = default; - break; - } - FindReferenceTargetsCallback.s_currentRootObjectHandle = default; - } - } - - // Report whether walking failed or not. - if (walkFailed) - { - TrackerObjectManager.s_isGlobalPeggingOn = true; - } - IReferenceTrackerManager.FindTrackerTargetsCompleted(TrackerObjectManager.s_trackerManager, walkFailed); - } - - // Used during GC callback - internal static void DetachNonPromotedObjects() - { - foreach (GCHandle weakNativeObjectWrapperHandle in s_referenceTrackerNativeObjectWrapperCache) - { - ReferenceTrackerNativeObjectWrapper? nativeObjectWrapper = Unsafe.As(weakNativeObjectWrapperHandle.Target); - if (nativeObjectWrapper != null && - nativeObjectWrapper.TrackerObject != IntPtr.Zero && - !RuntimeImports.RhIsPromoted(nativeObjectWrapper.ProxyHandle.Target)) - { - // Notify the wrapper it was not promoted and is being collected. - TrackerObjectManager.BeforeWrapperFinalized(nativeObjectWrapper.TrackerObject); - } - } - } - [UnmanagedCallersOnly] internal static unsafe int IUnknown_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) { @@ -1525,32 +106,7 @@ internal static unsafe uint IReferenceTrackerTarget_Unpeg(IntPtr 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() + private static unsafe partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl() { IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ComWrappers), 7 * sizeof(IntPtr)); vftbl[0] = (IntPtr)(delegate* unmanaged)&ComWrappers.IReferenceTrackerTarget_QueryInterface; @@ -1562,458 +118,21 @@ private static unsafe IntPtr CreateDefaultIReferenceTrackerTargetVftbl() 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 - { - public static int Resolve(IntPtr pThis, Guid guid, out IntPtr inspectable) - { - fixed (IntPtr* inspectablePtr = &inspectable) - return (*(delegate* unmanaged**)pThis)[3](pThis, &guid, inspectablePtr); - } - } - - // Wrapper for IWeakReferenceSource - private static unsafe class IWeakReferenceSource - { - public static int GetWeakReference(IntPtr pThis, out IntPtr weakReference) - { - fixed (IntPtr* weakReferencePtr = &weakReference) - return (*(delegate* unmanaged**)pThis)[3](pThis, weakReferencePtr); - } - } - - private static object? ComWeakRefToObject(IntPtr pComWeakRef, long wrapperId) - { - if (wrapperId == 0) - { - return null; - } - - // Using the IWeakReference*, get ahold of the target native COM object's IInspectable*. If this resolve fails or - // returns null, then we assume that the underlying native COM object is no longer alive, and thus we cannot create a - // new RCW for it. - if (IWeakReference.Resolve(pComWeakRef, IID_IInspectable, out IntPtr targetPtr) == HResults.S_OK && - targetPtr != IntPtr.Zero) - { - using ComHolder target = new ComHolder(targetPtr); - if (Marshal.QueryInterface(target.Ptr, IID_IUnknown, out IntPtr targetIdentityPtr) == HResults.S_OK) - { - using ComHolder targetIdentity = new ComHolder(targetIdentityPtr); - return GetOrCreateObjectFromWrapper(wrapperId, targetIdentity.Ptr); - } - } - - return null; - } - - private static unsafe bool PossiblyComObject(object target) - { - // If the RCW is an aggregated RCW, then the managed object cannot be recreated from the IUnknown - // as the outer IUnknown wraps the managed object. In this case, don't create a weak reference backed - // by a COM weak reference. - return s_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)) - { - 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.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(); - } - } - } - - 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); - - // 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; - - 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.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; - } - } - } - - 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 + internal static unsafe int ITaggedImpl_IsCurrentVersion(IntPtr pThis, IntPtr version) { - public GCHandle m_value; - public Entry? m_next; + return version == (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion + ? HResults.S_OK + : HResults.E_FAIL; } - public struct Enumerator : IEnumerator + private static partial IntPtr GetTaggedImplCurrentVersion() { - private readonly Entry?[] _buckets; - private int _currentIdx; - private Entry? _currentEntry; - - 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.m_value; - } - } - - object IEnumerator.Current => Current; - - public void Dispose() - { - } - - public bool MoveNext() - { - 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; - } - - public void Reset() + unsafe { - _currentIdx = -1; - _currentEntry = null; + return (IntPtr)(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..6ed9677b6df062 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,9 @@ 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(); internal static volatile IntPtr s_trackerManager; internal static volatile bool s_hasTrackingStarted; @@ -29,49 +28,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 +90,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 partial bool HasReferenceTrackerManager + => s_trackerManager != IntPtr.Zero; + + private static partial 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 partial bool IsGlobalPeggingEnabled => s_isGlobalPeggingOn; + + static partial 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 +151,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) - { - return (*(delegate* unmanaged**)pThis)[5](pThis); - } - - public static void SetReferenceTrackerHost(IntPtr pThis, IntPtr referenceTrackerHost) + internal static unsafe void WalkExternalTrackerObjects() { - 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, 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) - { - return (*(delegate* unmanaged**)pThis)[5](pThis, findReferenceTargetsCallback); - } - - public static void GetReferenceTrackerManager(IntPtr pThis, out IntPtr referenceTrackerManager) + internal static void DetachNonPromotedObjects() { - 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); + } + } } } @@ -314,23 +252,6 @@ internal static unsafe IntPtr CreateFindReferenceTargetsCallback() } } - internal readonly struct ComHolder : IDisposable - { - private readonly IntPtr _ptr; - - internal readonly IntPtr Ptr => _ptr; - - public ComHolder(IntPtr ptr) - { - _ptr = ptr; - } - - public readonly void Dispose() - { - Marshal.Release(_ptr); - } - } - // This is used during a GC callback so it needs to be free of any managed allocations. internal unsafe struct DependentHandleList { diff --git a/src/coreclr/vm/binder.cpp b/src/coreclr/vm/binder.cpp index 7627e5a96c81a8..b1d9a3b6886470 100644 --- a/src/coreclr/vm/binder.cpp +++ b/src/coreclr/vm/binder.cpp @@ -21,6 +21,7 @@ #include "sigbuilder.h" #include "olevariant.h" #include "configuration.h" +#include "interoplibinterface_comwrappers.h" // // Retrieve structures from ID. diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index d039226c328cad..2b589995e18b17 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, 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 1f5fda9ed02ccc..cbe100615f3d83 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, bool lowMemoryPressure); extern "C" void* QCALLTYPE GCInterface_GetNextFinalizableObject(QCall::ObjectHandleOnStack pObj); diff --git a/src/coreclr/vm/corelib.cpp b/src/coreclr/vm/corelib.cpp index beb66a2086a226..b69d316b48aa95 100644 --- a/src/coreclr/vm/corelib.cpp +++ b/src/coreclr/vm/corelib.cpp @@ -48,6 +48,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 3b7ad29038a950..780e04dd95e911 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -421,13 +421,32 @@ 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_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(m_next, GCHandleSetEntryObject, m_next) +DEFINE_FIELD_U(m_value, GCHandleSetEntryObject, m_handle) +DEFINE_CLASS_U(Interop, GCHandleSet, NoClass) +DEFINE_FIELD_U(_buckets, GCHandleSetObject, _buckets) #endif //FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 4738b0d7aeb8fa..352f9277fed396 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::IsRooted((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..8b0daca955a1e5 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -13,639 +13,15 @@ #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, @@ -672,1342 +48,584 @@ namespace 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; + BOOL g_isGlobalPeggingOn = TRUE; +} - HRESULT hr; +extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( + _Out_ void** fpQueryInterface, + _Out_ void** fpAddRef, + _Out_ void** fpRelease) +{ + QCALL_CONTRACT; - SafeComHolder newWrapper; - void* wrapperRawMaybe = NULL; + _ASSERTE(fpQueryInterface != NULL); + _ASSERTE(fpAddRef != NULL); + _ASSERTE(fpRelease != 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(); - } - } - } - } + BEGIN_QCALL; - // 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(); - } + InteropLib::Com::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); - GCPROTECT_END(); + END_QCALL; +} - *wrapperRaw = wrapperRawMaybe; - RETURN (wrapperRawMaybe != NULL); +void ComWrappersNative::DestroyExternalComObjectContext(_In_ void* contextRaw) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(contextRaw != NULL); } + CONTRACTL_END; - 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; + DestroyLongWeakHandle((OBJECTHANDLE)contextRaw); +} - 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 ComWrappersNative::MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe) { - 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 { - 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; + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(wrapperMaybe != NULL); } + CONTRACTL_END; - 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() } - { } - }; + 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); + } +} - HRESULT IteratorNext( - _In_ RuntimeCallContext* runtimeContext, - _Outptr_result_maybenull_ void** extObjContext) noexcept +bool GlobalComWrappersForMarshalling::TryGetOrCreateComInterfaceForObject( + _In_ OBJECTREF instance, + _Outptr_ void** wrapperRaw) +{ + CONTRACTL { - 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; + THROWS; + MODE_COOPERATIVE; } + CONTRACTL_END; - HRESULT FoundReferencePath( - _In_ RuntimeCallContext* runtimeContext, - _In_ void* extObjContextRaw, - _In_ InteropLib::OBJECTHANDLE handle) noexcept + void* wrapper; { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_COOPERATIVE; - PRECONDITION(runtimeContext != NULL); - PRECONDITION(extObjContextRaw != NULL); - PRECONDITION(handle != NULL); - - // Should only be called during a GC suspension - PRECONDITION(Debug_IsLockedViaThreadSuspension()); - } - CONTRACTL_END; + GCPROTECT_BEGIN(instance); - // Get the external object's managed wrapper - ExternalObjectContext* extObjContext = static_cast(extObjContextRaw); - OBJECTREF source = extObjContext->GetObjectRef(); + 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); - // Get the target of the external object's reference. - ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); - OBJECTREF target = ObjectFromHandle(objectHandle); + GCPROTECT_END(); + } - // Return if the target has been collected or these are the same object. - if (target == NULL - || source->PassiveGetSyncBlock() == target->PassiveGetSyncBlock()) - { - return S_FALSE; - } + *wrapperRaw = wrapper; - 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 wrapper != nullptr; } -extern "C" BOOL QCALLTYPE ComWrappers_TryGetOrCreateComInterfaceForObject( - _In_ QCall::ObjectHandleOnStack comWrappersImpl, - _In_ INT64 wrapperId, - _In_ QCall::ObjectHandleOnStack instance, - _In_ INT32 flags, - _Outptr_ void** wrapper) +bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( + _In_ IUnknown* externalComObject, + _In_ INT32 objFromComIPFlags, + _Out_ OBJECTREF* objRef) { - QCALL_CONTRACT; + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + } + CONTRACTL_END; - bool success = false; + // Determine the true identity of the object + SafeComHolder identity; + { + GCX_PREEMP(); - BEGIN_QCALL; + HRESULT hr = externalComObject->QueryInterface(IID_IUnknown, &identity); + _ASSERTE(hr == S_OK); + } // Switch to Cooperative mode since object references // are being manipulated. { GCX_COOP(); - success = TryGetOrCreateComInterfaceForObjectInternal( - ObjectToOBJECTREF(*comWrappersImpl.m_ppObject), - wrapperId, - ObjectToOBJECTREF(*instance.m_ppObject), - (CreateComInterfaceFlags)flags, - ComWrappersScenario::Instance, - wrapper); - } - END_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; + + OBJECTREF obj = NULL; + GCPROTECT_BEGIN(obj); - return (success ? TRUE : FALSE); + 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(identity); + args[ARGNUM_1] = DWORD_TO_ARGHOLDER(flags); + CALL_MANAGED_METHOD_RETREF(obj, OBJECTREF, args); + + GCPROTECT_END(); + + *objRef = obj; + return obj != NULL; + } } -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) +extern "C" void* QCALLTYPE ComWrappers_AllocateRefCountedHandle(_In_ QCall::ObjectHandleOnStack obj) { QCALL_CONTRACT; - _ASSERTE(ext != NULL); - - bool success = false; + void* handle = NULL; 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. { 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); + handle = GetAppDomain()->CreateTypedHandle(obj.Get(), HNDTYPE_REFCOUNTED); } END_QCALL; - return (success ? TRUE : FALSE); + return handle; } -extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( - _Out_ void** fpQueryInterface, - _Out_ void** fpAddRef, - _Out_ void** fpRelease) +extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl() { - QCALL_CONTRACT; + QCALL_CONTRACT_NO_GC_TRANSITION; - _ASSERTE(fpQueryInterface != NULL); - _ASSERTE(fpAddRef != NULL); - _ASSERTE(fpRelease != NULL); + void const* vftbl = NULL; BEGIN_QCALL; - InteropLib::Com::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); + vftbl = InteropLib::Com::GetIReferenceTrackerTargetVftbl(); END_QCALL; + + return vftbl; } -extern "C" BOOL QCALLTYPE ComWrappers_TryGetComInstance( - _In_ QCall::ObjectHandleOnStack wrapperMaybe, - _Out_ void** externalComObject) +extern "C" void QCALLTYPE ComWrappers_GetTaggedImpl( + _Out_ void const** fpTaggedIsCurrentVersion) { - QCALL_CONTRACT; - - _ASSERTE(externalComObject != NULL); - - bool success = false; + QCALL_CONTRACT_NO_GC_TRANSITION; BEGIN_QCALL; - // Switch to Cooperative mode since object references - // are being manipulated. - { - GCX_COOP(); - - 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)); - } - } - } - } + *fpTaggedIsCurrentVersion = InteropLib::Com::GetTaggedCurrentVersionImpl(); END_QCALL; - - return (success ? TRUE : FALSE); } -extern "C" BOOL QCALLTYPE ComWrappers_TryGetObject( - _In_ void* wrapperMaybe, - _Inout_ QCall::ObjectHandleOnStack instance) +extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( + _In_ QCall::ObjectHandleOnStack obj, + _In_ void* wrapper, + _In_ long wrapperId) { QCALL_CONTRACT; - _ASSERTE(wrapperMaybe != NULL); + BEGIN_QCALL; - bool success = false; + GCX_COOP(); - BEGIN_QCALL; + OBJECTREF objRef = obj.Get(); + GCPROTECT_BEGIN(objRef); - // Determine the true identity of the object - SafeComHolder identity; - HRESULT hr = ((IUnknown*)wrapperMaybe)->QueryInterface(IID_IUnknown, &identity); - _ASSERTE(hr == S_OK); + // Check the object's SyncBlock for a managed object wrapper. + SyncBlock* syncBlock = objRef->GetSyncBlock(); + InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); + _ASSERTE(syncBlock->IsPrecious()); - InteropLib::OBJECTHANDLE handle; - if (InteropLib::Com::GetObjectForWrapper(identity, &handle) == S_OK) - { - // Switch to Cooperative mode since object references - // are being manipulated. - GCX_COOP(); + interopInfo->TrySetManagedObjectComWrapper(wrapperId, wrapper); - // 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; - } + GCPROTECT_END(); END_QCALL; - - return (success ? TRUE : FALSE); } -void ComWrappersNative::DestroyManagedObjectComWrapper(_In_ void* wrapper) +extern "C" void QCALLTYPE ComWrappers_RegisterNativeObjectWrapperForDiagnostics( + _In_ QCall::ObjectHandleOnStack target, + _In_ QCall::ObjectHandleOnStack wrapper) { - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(wrapper != NULL); - } - CONTRACTL_END; + QCALL_CONTRACT; - STRESS_LOG1(LF_INTEROP, LL_INFO100, "Destroying MOW: 0x%p\n", wrapper); + BEGIN_QCALL; - { - GCX_PREEMP(); - InteropLib::Com::DestroyWrapperForObject(wrapper); - } -} + GCX_COOP(); -void ComWrappersNative::DestroyExternalComObjectContext(_In_ void* contextRaw) -{ - CONTRACTL + struct { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(contextRaw != NULL); - } - CONTRACTL_END; + OBJECTREF targetObj; + OBJECTREF wrapperObj; + } gc; -#ifdef _DEBUG - ExternalObjectContext* context = static_cast(contextRaw); - _ASSERTE(!context->IsActive()); -#endif + gc.targetObj = target.Get(); + gc.wrapperObj = wrapper.Get(); - STRESS_LOG1(LF_INTEROP, LL_INFO100, "Destroying EOC: 0x%p\n", contextRaw); + GCPROTECT_BEGIN(gc); - { - GCX_PREEMP(); - InteropLib::Com::DestroyWrapperForExternal(contextRaw); - } -} + SyncBlock* syncBlock = gc.targetObj->GetSyncBlock(); + InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); + _ASSERTE(syncBlock->IsPrecious()); -void ComWrappersNative::MarkExternalComObjectContextCollected(_In_ void* contextRaw) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(contextRaw != NULL); - PRECONDITION(GCHeapUtilities::IsGCInProgress()); - } - CONTRACTL_END; + OBJECTHANDLE wrapperHandle = GetAppDomain()->CreateLongWeakHandle(gc.wrapperObj); - ExternalObjectContext* context = static_cast(contextRaw); - _ASSERTE(context->IsActive()); - context->MarkCollected(); + interopInfo->TrySetExternalComObjectContext((PTR_VOID)wrapperHandle); - 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); + GCPROTECT_END(); - // Verify the caller didn't ignore the cache during creation. - if (inCache) - { - ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); - cache->Remove(context); - } + END_QCALL; } -void ComWrappersNative::MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe) +extern "C" BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager() { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(wrapperMaybe != NULL); - } - CONTRACTL_END; + QCALL_CONTRACT_NO_GC_TRANSITION; - { - 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); - } + BOOL hasManager = FALSE; + + BEGIN_QCALL; + + hasManager = InteropLib::Com::HasReferenceTrackerManager() ? TRUE : FALSE; + + END_QCALL; + + return hasManager; } -extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForMarshalling(INT64 id) +extern "C" BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager) { QCALL_CONTRACT_NO_GC_TRANSITION; - _ASSERTE(g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId && id != ComWrappersNative::InvalidWrapperId); - g_marshallingGlobalInstanceId = id; + BOOL success = FALSE; + + BEGIN_QCALL; + + success = InteropLib::Com::TryRegisterReferenceTrackerManager(manager) ? TRUE : FALSE; + + END_QCALL; + + return success; } -bool GlobalComWrappersForMarshalling::IsRegisteredInstance(INT64 id) +OBJECTHANDLE GCHandleSetObject::Iterator::Current() const { - return g_marshallingGlobalInstanceId != ComWrappersNative::InvalidWrapperId - && g_marshallingGlobalInstanceId == id; + LIMITED_METHOD_CONTRACT; + return _currentEntry->m_handle; } -bool GlobalComWrappersForMarshalling::TryGetOrCreateComInterfaceForObject( - _In_ OBJECTREF instance, - _Outptr_ void** wrapperRaw) +bool GCHandleSetObject::Iterator::MoveNext() { CONTRACTL { - THROWS; + NOTHROW; + GC_NOTRIGGER; MODE_COOPERATIVE; + + // Should only be called during a GC suspension + PRECONDITION(Debug_IsLockedViaThreadSuspension()); } CONTRACTL_END; - if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; - - // Switch to Cooperative mode since object references - // are being manipulated. + if (_currentEntry != NULL) { - GCX_COOP(); + _currentEntry = _currentEntry->m_next; + } - CreateComInterfaceFlags flags = CreateComInterfaceFlags::CreateComInterfaceFlags_TrackerSupport; + if (_currentEntry == NULL) + { + // Certain buckets might be empty, so loop until we find + // one with an entry. + while (++_currentIndex != (int32_t)_buckets->GetNumComponents()) + { + _currentEntry = _buckets->GetAt(_currentIndex); + if (_currentEntry != NULL) + { + return true; + } + } - // Passing NULL as the ComWrappers implementation indicates using the globally registered instance - return TryGetOrCreateComInterfaceForObjectInternal( - NULL, - g_marshallingGlobalInstanceId, - instance, - flags, - ComWrappersScenario::MarshallingGlobalInstance, - wrapperRaw); + return false; } + + return true; } -bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( - _In_ IUnknown* externalComObject, - _In_ INT32 objFromComIPFlags, - _Out_ OBJECTREF* objRef) +namespace InteropLibImports { - CONTRACTL + bool HasValidTarget(_In_ InteropLib::OBJECTHANDLE handle) noexcept { - THROWS; - MODE_COOPERATIVE; + 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; } - CONTRACTL_END; - if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; + void DestroyHandle(_In_ InteropLib::OBJECTHANDLE handle) noexcept + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(handle != NULL); + } + CONTRACTL_END; + ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); + + 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; + if (handle == nullptr) + return false; - // 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); - } -} + ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); -extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForTrackerSupport(INT64 id) -{ - QCALL_CONTRACT_NO_GC_TRANSITION; + OBJECTREF obj = ObjectFromHandle(objectHandle); - _ASSERTE(g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId && id != ComWrappersNative::InvalidWrapperId); - g_trackerSupportGlobalInstanceId = id; -} + if (obj == nullptr) + return false; -bool GlobalComWrappersForTrackerSupport::IsRegisteredInstance(INT64 id) -{ - return g_trackerSupportGlobalInstanceId != ComWrappersNative::InvalidWrapperId - && g_trackerSupportGlobalInstanceId == id; -} + return GCHeapUtilities::GetGCHeap()->IsPromoted(OBJECTREFToObject(obj)); + } -bool GlobalComWrappersForTrackerSupport::TryGetOrCreateComInterfaceForObject( - _In_ OBJECTREF instance, - _Outptr_ void** wrapperRaw) -{ - CONTRACTL + void SetGlobalPeggingState(_In_ bool state) noexcept { - THROWS; - MODE_COOPERATIVE; + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + BOOL newState = state ? TRUE : FALSE; + VolatileStore(&g_isGlobalPeggingOn, newState); } - CONTRACTL_END; - if (g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; + 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; - // Passing NULL as the ComWrappers implementation indicates using the globally registered instance - return TryGetOrCreateComInterfaceForObjectInternal( - NULL, - g_trackerSupportGlobalInstanceId, - instance, - CreateComInterfaceFlags::CreateComInterfaceFlags_TrackerSupport, - ComWrappersScenario::TrackerSupportGlobalInstance, - wrapperRaw); -} + *obj = NULL; -bool GlobalComWrappersForTrackerSupport::TryGetOrCreateObjectForComInstance( - _In_ IUnknown* externalComObject, - _Out_ OBJECTREF* objRef) -{ - CONTRACTL - { - THROWS; - MODE_COOPERATIVE; - } - CONTRACTL_END; + // 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; - if (g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId) - return false; + // 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(); - // Determine the true identity of the object - SafeComHolder identity; - { - GCX_PREEMP(); + // 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 = externalComObject->QueryInterface(IID_IUnknown, &identity); - _ASSERTE(hr == S_OK); - } + HRESULT hr; + auto result = TryInvokeICustomQueryInterfaceResult::FailedToInvoke; + EX_TRY_THREAD(CURRENT_THREAD) + { + // Switch to Cooperative mode since object references + // are being manipulated. + GCX_COOP(); - // 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); -} + struct + { + OBJECTREF objRef; + } gc; + gc.objRef = NULL; + GCPROTECT_BEGIN(gc); -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; + // 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); - ASSERT_PROTECTED(objectPROTECTED); + GCPROTECT_END(); + } + EX_CATCH_HRESULT(hr); - *wrapperId = ComWrappersNative::InvalidWrapperId; + // Assert valid value. + _ASSERTE(TryInvokeICustomQueryInterfaceResult::Min <= result + && result <= TryInvokeICustomQueryInterfaceResult::Max); - SyncBlock* syncBlock = (*objectPROTECTED)->PassiveGetSyncBlock(); - if (syncBlock == nullptr) - { - return nullptr; + return result; } - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); - if (interopInfo == nullptr) + struct RuntimeCallContext { - return nullptr; - } + RCWRefCache* RefCache; + GCHandleSetObject::Iterator _iterator; + }; - void* contextMaybe; - if (interopInfo->TryGetExternalComObjectContext(&contextMaybe)) + HRESULT 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()) { - return result; + *referenceTracker = NULL; + *proxyHandle = NULL; + return S_FALSE; + } + OBJECTHANDLE nativeObjectWrapperHandle = runtimeContext->_iterator.Current(); + REFTRACKEROBJECTWRAPPERREF nativeObjectWrapper = (REFTRACKEROBJECTWRAPPERREF)ObjectFromHandle(nativeObjectWrapperHandle); + + if (nativeObjectWrapper == NULL) + { + *referenceTracker = NULL; + *proxyHandle = NULL; + return S_OK; } + + *referenceTracker = dac_cast(nativeObjectWrapper->GetTrackerObject()); + *proxyHandle = static_cast(nativeObjectWrapper->GetProxyHandle()); + return S_OK; } - 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* pManagedObjectWrapperHolderMT = NULL; +} + +bool ComWrappersNative::IsRooted(_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() != 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); - *isRooted = cxt.IsRooted; - return cxt.HasWrapper; + return true; +} + +namespace +{ + OBJECTHANDLE NativeObjectWrapperCacheHandle = NULL; + RCWRefCache* pAppDomainRCWRefCache = NULL; } void ComWrappersNative::OnFullGCStarted() @@ -2015,27 +633,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 +665,82 @@ 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::OnGCAfterMarkPhase() { 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. + 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" BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled() +{ + QCALL_CONTRACT_NO_GC_TRANSITION; + + BOOL isEnabled = FALSE; + + BEGIN_QCALL; + + isEnabled = InteropLibImports::GetGlobalPeggingState() ? TRUE : FALSE; + + END_QCALL; + + return isEnabled; } #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..267e8d65794e51 --- /dev/null +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -0,0 +1,176 @@ +// 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: // Lifetime management for COM Wrappers + static void DestroyExternalComObjectContext(_In_ void* context); + +public: // COM activation + static void MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe); + +public: // Unwrapping support + static bool IsRooted(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted); + +public: // GC interaction + static void OnFullGCStarted(); + static void OnFullGCFinished(); + static void OnGCAfterMarkPhase(); +}; + +// 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 QCALLTYPE ComWrappers_GetTaggedImpl( + _Out_ void const** fpTaggedIsCurrentVersion); + +extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( + _In_ QCall::ObjectHandleOnStack obj, + _In_ void* wrapper, + _In_ long wrapperId); + +extern "C" void QCALLTYPE ComWrappers_RegisterNativeObjectWrapperForDiagnostics( + _In_ QCall::ObjectHandleOnStack target, + _In_ QCall::ObjectHandleOnStack wrapper); + +extern "C" void QCALLTYPE ComWrappers_RegisterIsRootedCallback(); + +extern "C" BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager(); + +extern "C" BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager); + +extern "C" void QCALLTYPE TrackerObjectManager_RegisterNativeObjectWrapperCache(_In_ QCall::ObjectHandleOnStack cache); + +extern "C" 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 ManagedObjectWrapperHolderObject : public Object +{ + friend class CoreLibBinder; +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; + } +}; + +class GCHandleSetEntryObject final : public Object +{ + friend class CoreLibBinder; + public: + OBJECTREF m_next; + OBJECTHANDLE m_handle; +}; + +#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 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 diff --git a/src/coreclr/vm/interoplibinterface_shared.cpp b/src/coreclr/vm/interoplibinterface_shared.cpp index f31b128e47cfb1..6a53c9f097e0bb 100644 --- a/src/coreclr/vm/interoplibinterface_shared.cpp +++ b/src/coreclr/vm/interoplibinterface_shared.cpp @@ -145,8 +145,8 @@ void Interop::OnAfterGCScanRoots(_In_ bool isConcurrent) CONTRACTL_END; #ifdef FEATURE_COMWRAPPERS - ComWrappersNative::AfterRefCountedHandleCallbacks(); -#endif // FEATURE_COMWRAPPERS + ComWrappersNative::OnGCAfterMarkPhase(); +#endif #ifdef FEATURE_OBJCMARSHAL // See Interop::OnBeforeGCScanRoots for why non-concurrent. diff --git a/src/coreclr/vm/interoputil.cpp b/src/coreclr/vm/interoputil.cpp index 83657cf3759b1c..7c98422980a686 100644 --- a/src/coreclr/vm/interoputil.cpp +++ b/src/coreclr/vm/interoputil.cpp @@ -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) @@ -1247,8 +1241,6 @@ void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo) #endif // FEATURE_COMINTEROP #ifdef FEATURE_COMWRAPPERS - pInteropInfo->ClearManagedObjectComWrappers(&ComWrappersNative::DestroyManagedObjectComWrapper); - void* eoc; if (pInteropInfo->TryGetExternalComObjectContext(&eoc)) { diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index 2626a3237bed57..d506c038904de9 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/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index de2482d837a036..8e4f2fa82022e6 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -421,12 +421,16 @@ 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_RegisterManagedObjectWrapperForDiagnostics) + DllImportEntry(ComWrappers_RegisterNativeObjectWrapperForDiagnostics) + DllImportEntry(ComWrappers_RegisterIsRootedCallback) + DllImportEntry(TrackerObjectManager_HasReferenceTrackerManager) + DllImportEntry(TrackerObjectManager_TryRegisterReferenceTrackerManager) + DllImportEntry(TrackerObjectManager_RegisterNativeObjectWrapperCache) + DllImportEntry(TrackerObjectManager_IsGlobalPeggingEnabled) #endif #if defined(FEATURE_OBJCMARSHAL) DllImportEntry(ObjCMarshal_TrySetGlobalMessageSendCallback) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index e0ef98c3fcd153..77fa06008ef953 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -804,18 +804,6 @@ class InteropSyncBlockInfo #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) { @@ -840,55 +828,6 @@ class InteropSyncBlockInfo 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) diff --git a/src/coreclr/vm/weakreferencenative.cpp b/src/coreclr/vm/weakreferencenative.cpp index bb0ac10906ef00..495518a954ac5d 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,15 +88,13 @@ 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); @@ -127,27 +108,12 @@ extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnSt // 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,6 +131,9 @@ extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnSt END_QCALL; return pWeakReference; } +#endif // FEATURE_COMINTEROP + +#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) FCIMPL1(FC_BOOL_RET, ComAwareWeakReferenceNative::HasInteropInfo, Object* pObject) { diff --git a/src/coreclr/vm/weakreferencenative.h b/src/coreclr/vm/weakreferencenative.h index c95f84119503de..63e84c9007298f 100644 --- a/src/coreclr/vm/weakreferencenative.h +++ b/src/coreclr/vm/weakreferencenative.h @@ -21,8 +21,8 @@ class ComAwareWeakReferenceNative static FCDECL1(FC_BOOL_RET, HasInteropInfo, Object* pObject); }; -extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference * pComWeakReference, INT64 wrapperId, QCall::ObjectHandleOnStack retRcw); -extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj, INT64* wrapperId); +extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference * pComWeakReference, QCall::ObjectHandleOnStack retRcw); +extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj); #endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS 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 d6ec12abecca60..8fa5266b63ca72 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 @@ -2126,6 +2126,9 @@ Common\Interop\Windows\Ole32\Interop.CoCreateGuid.cs + + Interop\Windows\Ole32\Interop.CoGetContextToken.cs + Common\Interop\Windows\Ole32\Interop.CoGetStandardMarshal.cs @@ -2253,6 +2256,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/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.Common.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs new file mode 100644 index 00000000000000..28afaa202e8beb --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs @@ -0,0 +1,1721 @@ +// 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.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 +{ + /// + /// Class for managing wrappers of COM IUnknown types. + /// + 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 readonly ConditionalWeakTable _managedObjectWrapperTable = new ConditionalWeakTable(); + private readonly RcwCache _rcwCache = new(); + + 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; + } + + /// + /// ABI for function dispatch of a COM interface. + /// + public unsafe partial struct ComInterfaceDispatch + { + /// + /// 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(); + } + + static partial void RegisterIsRootedCallback(); + + private static partial IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder); + + 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; + } + } + } + + /// + /// Globally registered instance of the ComWrappers class for reference tracker support. + /// + private static ComWrappers? s_globalInstanceForTrackerSupport; + + internal static ComWrappers? GlobalInstanceForTrackerSupport => s_globalInstanceForTrackerSupport; + + /// + /// Globally registered instance of the ComWrappers class for marshalling. + /// + 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; + } + } + + // 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(); + RegisterManagedObjectWrapperForDiagnostics(instance, managedObjectWrapper.Wrapper); + return managedObjectWrapper.ComIp; + } + + unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* 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; + } + + /// + /// 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 = 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. + 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(); + } + + RegisterNativeObjectWrapperForDiagnostics(registeredWrapper); + + // 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); + } + + static partial void RegisterNativeObjectWrapperForDiagnostics(NativeObjectWrapper 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 = []; + + /// + /// 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 partial void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); + + 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; + } + + 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. + // TODO-ComWrappers: Pull the "IsCurrentVersion" implementation from the runtime (CoreCLR needs to access it from native code) + + private static partial IntPtr GetTaggedImplCurrentVersion(); + + 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; + } + + private static unsafe partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl(); + + // 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.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(); + } + } + } + + 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); + + // 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; + + 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.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; + } + } + } + + 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? m_next; + public GCHandle m_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.m_value; + } + } + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + + public bool MoveNext() + { + 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; + } + + 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/ComWrappers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index afc824b8025e6f..370eef6bdf90a0 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 @@ -130,6 +130,6 @@ 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); } } 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..eb34c45a054743 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs @@ -0,0 +1,209 @@ +// 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 + { + internal static readonly IntPtr s_globalHostServices = CreateHostServices(); + + // Called when an IReferenceTracker instance is found. + public static void SetReferenceTrackerHost(IntPtr trackerManager) + { + IReferenceTrackerManager.SetReferenceTrackerHost(trackerManager, s_globalHostServices); + } + + private static unsafe IntPtr CreateHostServices() + { + IntPtr* wrapperMem = (IntPtr*)NativeMemory.Alloc((nuint)sizeof(IntPtr)); + wrapperMem[0] = CreateDefaultIReferenceTrackerHostVftbl(); + return (IntPtr)wrapperMem; + } + + [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) + { + 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); + } + + } + + [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 + { + TrackerObjectManager.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, 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); + } + } + + [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); + } + } + + [UnmanagedCallersOnly] + internal static unsafe int IReferenceTrackerHost_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) + { + if (*guid == ComWrappers.IID_IReferenceTrackerHost || *guid == ComWrappers.IID_IUnknown) + { + *ppObject = pThis; + return 0; + } + else + { + return HResults.COR_E_INVALIDCAST; + } + } + + internal static unsafe IntPtr CreateDefaultIReferenceTrackerHostVftbl() + { + IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ReferenceTrackerHost), 9 * sizeof(IntPtr)); + vftbl[0] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_QueryInterface; + vftbl[1] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_AddRef; + vftbl[2] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_Release; + vftbl[3] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_DisconnectUnusedReferenceSources; + vftbl[4] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_ReleaseDisconnectedReferenceSources; + vftbl[5] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnThread; + vftbl[6] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_GetTrackerTarget; + vftbl[7] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_AddMemoryPressure; + vftbl[8] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_RemoveMemoryPressure; + return (IntPtr)vftbl; + } + } + + // 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..bb9cbf7500e76a --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs @@ -0,0 +1,113 @@ +// 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 partial bool IsGlobalPeggingEnabled { get; } + + private static partial bool HasReferenceTrackerManager { get; } + + private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager); + + static partial void RegisterGCCallbacks(); + + 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 + } + } +} From 9b32654572b206c2b72f44ad8a8220b58a43f3d5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 25 Mar 2025 17:04:06 -0700 Subject: [PATCH 02/38] Remove wrapper ID as a concept entirely --- .../Runtime/InteropServices/ComWrappers.cs | 7 ++--- src/coreclr/debug/daccess/request.cpp | 10 +++--- .../vm/interoplibinterface_comwrappers.cpp | 5 ++- src/coreclr/vm/syncblk.cpp | 2 +- src/coreclr/vm/syncblk.h | 31 ++++++++++++------- 5 files changed, 29 insertions(+), 26 deletions(-) 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 index 0df7a9ed9ddf8b..16e510a0e3fdb1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -23,16 +23,13 @@ public static unsafe partial void GetIUnknownImpl(out IntPtr fpQueryInterface, o [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIUnknownImpl")] private static partial void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); - private static long s_instanceCounter; - private readonly long id = Interlocked.Increment(ref s_instanceCounter); - unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper) { - RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref instance), wrapper, id); + RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref instance), wrapper); } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterManagedObjectWrapperForDiagnostics")] - private static unsafe partial void RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack instance, ManagedObjectWrapper* wrapper, long wrapperId); + private static unsafe partial void RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack instance, ManagedObjectWrapper* wrapper); static partial void RegisterNativeObjectWrapperForDiagnostics(NativeObjectWrapper registeredWrapper) { diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 25f2a14d44d241..f40c9b90b70bd8 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -5086,17 +5086,17 @@ HRESULT ClrDataAccess::GetObjectComWrappersData(CLRDATA_ADDRESS objAddr, CLRDATA *rcw = TO_TADDR(pInfo->m_externalComObjectContext); } - 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)); + DPTR(NewHolder) mapHolder(PTR_TO_MEMBER_TADDR(InteropSyncBlockInfo, pInfo, m_managedObjectComWrapperSet)); + DPTR(ManagedObjectComWrapperSet *)ppMap(PTR_TO_MEMBER_TADDR(NewHolder, mapHolder, m_value)); + DPTR(ManagedObjectComWrapperSet) pMap(TO_TADDR(*ppMap)); CQuickArrayList comWrappers; if (pMap != NULL) { - ManagedObjectComWrapperByIdMap::Iterator iter = pMap->Begin(); + ManagedObjectComWrapperSet::Iterator iter = pMap->Begin(); while (iter != pMap->End()) { - comWrappers.Push(TO_CDADDR(iter->Value())); + comWrappers.Push(TO_CDADDR(*iter)); ++iter; } diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index 8b0daca955a1e5..9e7f47b818496f 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -225,8 +225,7 @@ extern "C" void QCALLTYPE ComWrappers_GetTaggedImpl( extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( _In_ QCall::ObjectHandleOnStack obj, - _In_ void* wrapper, - _In_ long wrapperId) + _In_ void* wrapper) { QCALL_CONTRACT; @@ -242,7 +241,7 @@ extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); _ASSERTE(syncBlock->IsPrecious()); - interopInfo->TrySetManagedObjectComWrapper(wrapperId, wrapper); + interopInfo->AddManagedObjectComWrapper(wrapper); GCPROTECT_END(); diff --git a/src/coreclr/vm/syncblk.cpp b/src/coreclr/vm/syncblk.cpp index 073dc9c3f3f839..c58d10947927a2 100644 --- a/src/coreclr/vm/syncblk.cpp +++ b/src/coreclr/vm/syncblk.cpp @@ -71,7 +71,7 @@ InteropSyncBlockInfo::~InteropSyncBlockInfo() FreeUMEntryThunk(); #if defined(FEATURE_COMWRAPPERS) - delete m_managedObjectComWrapperMap; + delete m_managedObjectComWrapperSet; #endif // FEATURE_COMWRAPPERS } diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 77fa06008ef953..d953e2f0fdd1ca 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -611,8 +611,18 @@ typedef DPTR(class ComCallWrapper) PTR_ComCallWrapper; #include "shash.h" #endif // FEATURE_COMINTEROP +class ManagedObjectComWrapperTraits final : public NoRemoveSHashTraits> +{ +public: + typedef LPVOID key_t; + static void *Null() { LIMITED_METHOD_CONTRACT; return NULL; } + static bool IsNull(void* *e) { LIMITED_METHOD_CONTRACT; return (e == NULL); } + static const LPVOID GetKey(void *e) { LIMITED_METHOD_CONTRACT; return e; } + static count_t Hash(LPVOID key_t) { LIMITED_METHOD_CONTRACT; return (count_t)(size_t) key_t; } + static BOOL Equals(LPVOID lhs, LPVOID rhs) { LIMITED_METHOD_CONTRACT; return (lhs == rhs); } +}; -using ManagedObjectComWrapperByIdMap = MapSHash; +using ManagedObjectComWrapperSet = SHash>; class InteropSyncBlockInfo { friend class RCWHolder; @@ -636,7 +646,7 @@ class InteropSyncBlockInfo #ifdef FEATURE_COMWRAPPERS , m_externalComObjectContext{} , m_managedObjectComWrapperLock{} - , m_managedObjectComWrapperMap{} + , m_managedObjectComWrapperSet{} #endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL , m_taggedMemory{} @@ -805,27 +815,24 @@ class InteropSyncBlockInfo #if defined(FEATURE_COMWRAPPERS) public: #ifndef DACCESS_COMPILE - bool TrySetManagedObjectComWrapper(_In_ INT64 wrapperId, _In_ void* mocw, _In_ void* curr = NULL) + bool AddManagedObjectComWrapper(_In_ void* mocw) { LIMITED_METHOD_CONTRACT; - if (m_managedObjectComWrapperMap == NULL) + if (m_managedObjectComWrapperSet == NULL) { - NewHolder map = new ManagedObjectComWrapperByIdMap(); - if (InterlockedCompareExchangeT(&m_managedObjectComWrapperMap, (ManagedObjectComWrapperByIdMap *)map, NULL) == NULL) + NewHolder map = new ManagedObjectComWrapperSet(); + if (InterlockedCompareExchangeT(&m_managedObjectComWrapperSet, (ManagedObjectComWrapperSet *)map, NULL) == NULL) { map.SuppressRelease(); } - _ASSERTE(m_managedObjectComWrapperMap != NULL); + _ASSERTE(m_managedObjectComWrapperSet != NULL); } CrstHolder lock(&m_managedObjectComWrapperLock); - if (m_managedObjectComWrapperMap->LookupPtr(wrapperId) != curr) - return false; - - m_managedObjectComWrapperMap->Add(wrapperId, mocw); + m_managedObjectComWrapperSet->Add(mocw); return true; } #endif // !DACCESS_COMPILE @@ -854,7 +861,7 @@ class InteropSyncBlockInfo void* m_externalComObjectContext; CrstExplicitInit m_managedObjectComWrapperLock; - ManagedObjectComWrapperByIdMap* m_managedObjectComWrapperMap; + ManagedObjectComWrapperSet* m_managedObjectComWrapperSet; #endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL From 2abcf60c21054ff328d1dd07abf78e839931efdf Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 26 Mar 2025 13:24:03 -0700 Subject: [PATCH 03/38] Add #if around built-in COM in the weakref support. --- .../src/System/ComAwareWeakReference.CoreCLR.cs | 4 ++++ 1 file changed, 4 insertions(+) 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 4c0e872fab7360..1ee0be10c68d9e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -14,6 +14,7 @@ internal sealed partial class ComAwareWeakReference internal static object? ComWeakRefToObject(IntPtr pComWeakRef, object? context) { +#if FEATURE_COMINTEROP if (context is null) { // This wrapper was not created by ComWrappers, so we try to rehydrate using built-in COM. @@ -21,6 +22,7 @@ internal sealed partial class ComAwareWeakReference ComWeakRefToObject(pComWeakRef, ObjectHandleOnStack.Create(ref retRcw)); return retRcw; } +#endif // FEATURE_COMINTEROP return ComWeakRefToComWrappersObject(pComWeakRef, context); } @@ -56,12 +58,14 @@ internal static nint ObjectToComWeakRef(object target, out object? context) return IntPtr.Zero; } +#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 return ComWrappersObjectToComWeakRef(target, out context); } From 5b08974579b37d72638deb3fa8119f6407ce9dd0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 26 Mar 2025 13:29:20 -0700 Subject: [PATCH 04/38] Check for aggregation before getting a com weak ref for a ComWrappers-based wrapper on CoreCLR --- .../src/System/ComAwareWeakReference.CoreCLR.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 1ee0be10c68d9e..b90f6f4c7ad0eb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -67,7 +67,17 @@ internal static nint ObjectToComWeakRef(object target, out object? context) } #endif // FEATURE_COMINTEROP - return ComWrappersObjectToComWeakRef(target, out context); + if (PossiblyComWrappersObject(target)) + { + // This object is using ComWrappers, so use ComWrappers to create the weak reference. + context = target; + return ObjectToComWeakRef(ObjectHandleOnStack.Create(ref target)); + } + + // 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; } } } From 8feaa8cc5e8a18e10d0b417676e5cb87e0cb5587 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 26 Mar 2025 15:08:33 -0700 Subject: [PATCH 05/38] Make static now that we don't track an instance id --- .../src/System/Runtime/InteropServices/ComWrappers.cs | 2 +- .../src/System/Runtime/InteropServices/ComWrappers.Common.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 16e510a0e3fdb1..58dabb8f9fd7df 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -23,7 +23,7 @@ public static unsafe partial void GetIUnknownImpl(out IntPtr fpQueryInterface, o [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIUnknownImpl")] private static partial void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); - unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper) + static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper) { RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref instance), wrapper); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs index 28afaa202e8beb..ab7d6e8ba9dc66 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs @@ -766,7 +766,7 @@ public unsafe IntPtr GetOrCreateComInterfaceForObject(object instance, CreateCom return managedObjectWrapper.ComIp; } - unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper); + static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper); private static nuint AlignUp(nuint value, nuint alignment) { From d0450040a0bcb3346749c367a5075a54cd2357d8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 27 Mar 2025 12:02:06 -0700 Subject: [PATCH 06/38] Various build fixes --- src/coreclr/interop/inc/interoplibabi.h | 8 ++++---- src/coreclr/vm/interoplibinterface_comwrappers.h | 3 +-- src/coreclr/vm/syncblk.h | 6 +++--- .../System/Runtime/InteropServices/ComWrappers.Common.cs | 4 ---- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index 06e450e47cbb0e..c804408467aff2 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -35,13 +35,13 @@ namespace InteropLib { return _refCount; } - + protected: LONGLONG _refCount; - + Volatile _flags; - const int32_t _userDefinedCount; - const ComInterfaceEntry* _userDefined; + int32_t _userDefinedCount; + ComInterfaceEntry* _userDefined; ComInterfaceDispatch* _dispatches; }; } diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h index 267e8d65794e51..1e83ef1970732e 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.h +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -43,8 +43,7 @@ extern "C" void QCALLTYPE ComWrappers_GetTaggedImpl( extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( _In_ QCall::ObjectHandleOnStack obj, - _In_ void* wrapper, - _In_ long wrapperId); + _In_ void* wrapper); extern "C" void QCALLTYPE ComWrappers_RegisterNativeObjectWrapperForDiagnostics( _In_ QCall::ObjectHandleOnStack target, diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index d953e2f0fdd1ca..10809560f61b23 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -611,18 +611,18 @@ typedef DPTR(class ComCallWrapper) PTR_ComCallWrapper; #include "shash.h" #endif // FEATURE_COMINTEROP -class ManagedObjectComWrapperTraits final : public NoRemoveSHashTraits> +class ManagedObjectComWrapperTraits : public NoRemoveSHashTraits> { public: typedef LPVOID key_t; static void *Null() { LIMITED_METHOD_CONTRACT; return NULL; } - static bool IsNull(void* *e) { LIMITED_METHOD_CONTRACT; return (e == NULL); } + static bool IsNull(void *e) { LIMITED_METHOD_CONTRACT; return (e == NULL); } static const LPVOID GetKey(void *e) { LIMITED_METHOD_CONTRACT; return e; } static count_t Hash(LPVOID key_t) { LIMITED_METHOD_CONTRACT; return (count_t)(size_t) key_t; } static BOOL Equals(LPVOID lhs, LPVOID rhs) { LIMITED_METHOD_CONTRACT; return (lhs == rhs); } }; -using ManagedObjectComWrapperSet = SHash>; +using ManagedObjectComWrapperSet = SHash; class InteropSyncBlockInfo { friend class RCWHolder; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs index ab7d6e8ba9dc66..b5152edff60821 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs @@ -1405,10 +1405,6 @@ private static unsafe IntPtr CreateDefaultIUnknownVftbl() 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. - // TODO-ComWrappers: Pull the "IsCurrentVersion" implementation from the runtime (CoreCLR needs to access it from native code) - private static partial IntPtr GetTaggedImplCurrentVersion(); private static unsafe IntPtr CreateTaggedImplVftbl() From 095d9245c981fa046daadf8c9257461977345209 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 27 Mar 2025 13:07:14 -0700 Subject: [PATCH 07/38] Encapsulate the Target member --- src/coreclr/interop/comwrappers.cpp | 6 +++--- src/coreclr/interop/comwrappers.hpp | 5 +++++ src/coreclr/interop/inc/interoplibabi.h | 3 +-- src/coreclr/interop/trackerobjectmanager.cpp | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index ad7e56dc455a66..1b19206c39ee0a 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -195,7 +195,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. @@ -501,7 +501,7 @@ ULONG ManagedObjectWrapper::ReleaseFromReferenceTracker() // must destroy the wrapper. if (refCount == DestroySentinel) { - InteropLib::OBJECTHANDLE handle = InterlockedExchangePointer(&Target, nullptr); + InteropLib::OBJECTHANDLE handle = InterlockedExchangePointer(&target, nullptr); if (handle != nullptr) { InteropLibImports::DestroyHandle(handle); @@ -537,7 +537,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(target, riid, ppvObject); switch (result) { case TryInvokeICustomQueryInterfaceResult::Handled: diff --git a/src/coreclr/interop/comwrappers.hpp b/src/coreclr/interop/comwrappers.hpp index d8132c47946d3a..16b7ef40de3a51 100644 --- a/src/coreclr/interop/comwrappers.hpp +++ b/src/coreclr/interop/comwrappers.hpp @@ -59,6 +59,11 @@ class ManagedObjectWrapper final : public InteropLib::ABI::ManagedObjectWrapperL // Check if the wrapper has been marked to be destroyed. bool IsMarkedToDestroy() const; + InteropLib::OBJECTHANDLE GetTarget() const + { + return target; + } + public: // IReferenceTrackerTarget ULONG AddRefFromReferenceTracker(); ULONG ReleaseFromReferenceTracker(); diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index c804408467aff2..87335b36d1036f 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -29,14 +29,13 @@ namespace InteropLib struct ManagedObjectWrapperLayout { public: - Volatile Target; - LONGLONG GetRawRefCount() const { return _refCount; } protected: + Volatile target; LONGLONG _refCount; Volatile _flags; diff --git a/src/coreclr/interop/trackerobjectmanager.cpp b/src/coreclr/interop/trackerobjectmanager.cpp index c9f27f31b99d92..071c14326852b3 100644 --- a/src/coreclr/interop/trackerobjectmanager.cpp +++ b/src/coreclr/interop/trackerobjectmanager.cpp @@ -56,7 +56,7 @@ namespace RETURN_IF_FAILED(InteropLibImports::FoundReferencePath( _runtimeCallCxt, _sourceHandle, - mow->Target)); + mow->GetTarget())); return S_OK; } From 5c93a502c5c1ae9c7c4b81720c4878a90e9449a3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 27 Mar 2025 14:23:04 -0700 Subject: [PATCH 08/38] More fixes --- src/coreclr/interop/comwrappers.cpp | 2 +- .../vm/interoplibinterface_comwrappers.h | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index 1b19206c39ee0a..b608caa20ec169 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -331,7 +331,7 @@ void ManagedObjectWrapper::GetIUnknownImpl( void const* ManagedObjectWrapper::GetTaggedCurrentVersionImpl() noexcept { - return &ITaggedImpl_IsCurrentVersion; + return reinterpret_cast(&ITaggedImpl_IsCurrentVersion); } // The logic here should match code:ClrDataAccess::DACTryGetComWrappersObjectFromCCW in daccess/request.cpp diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h index 1e83ef1970732e..9b092631c461a1 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.h +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -75,6 +75,7 @@ class GlobalComWrappersForMarshalling // Define "manually managed" definitions of the ComWrappers types // that are used in diagnostics and during GC. class GCHandleSetObject; +class GCHandleSetEntryObject; class ManagedObjectWrapperHolderObject : public Object { @@ -123,14 +124,6 @@ class ReferenceTrackerNativeObjectWrapperObject final : public NativeObjectWrapp } }; -class GCHandleSetEntryObject final : public Object -{ - friend class CoreLibBinder; - public: - OBJECTREF m_next; - OBJECTHANDLE m_handle; -}; - #ifdef USE_CHECKED_OBJECTREFS using MOWHOLDERREF = REF; using NATIVEOBJECTWRAPPERREF = REF; @@ -145,6 +138,14 @@ using HANDLESETENTRYREF = DPTR(GCHandleSetEntryObject); using HANDLESETREF = DPTR(GCHandleSetObject); #endif +class GCHandleSetEntryObject final : public Object +{ + friend class CoreLibBinder; + public: + HANDLESETENTRYREF m_next; + OBJECTHANDLE m_handle; +}; + class GCHandleSetObject final : public Object { friend class CoreLibBinder; From 045aae9189c99a051d781a85cbdce5ca89c434bd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 27 Mar 2025 16:34:15 -0700 Subject: [PATCH 09/38] Add missing cast --- src/coreclr/vm/interoplibinterface_comwrappers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index 9e7f47b818496f..e30182c8b0156d 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -342,7 +342,7 @@ bool GCHandleSetObject::Iterator::MoveNext() // one with an entry. while (++_currentIndex != (int32_t)_buckets->GetNumComponents()) { - _currentEntry = _buckets->GetAt(_currentIndex); + _currentEntry = (HANDLESETENTRYREF)_buckets->GetAt(_currentIndex); if (_currentEntry != NULL) { return true; From dbd8533dba50792260cd4ca635dc548032382d03 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 27 Mar 2025 17:12:25 -0700 Subject: [PATCH 10/38] TADDR cast for null --- src/coreclr/debug/daccess/request.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index f40c9b90b70bd8..ad08670a0bd30f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -5263,7 +5263,7 @@ namespace OBJECTREF nativeObjectWrapper = ObjectFromHandle(nativeObjectWrapperHandle); if (nativeObjectWrapper == NULL) { - return NULL; + return (TADDR)NULL; } NATIVEOBJECTWRAPPERREF pNativeObjectWrapper = NATIVEOBJECTWRAPPERREF(nativeObjectWrapper); From 64b5e41a1deffecf0ebe62fef78a680cecc25cfd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 28 Mar 2025 21:10:34 +0000 Subject: [PATCH 11/38] Fix defines --- src/coreclr/vm/qcallentrypoints.cpp | 2 +- src/coreclr/vm/weakreferencenative.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 8e4f2fa82022e6..0ac61fd90b4a1d 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -515,7 +515,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/weakreferencenative.h b/src/coreclr/vm/weakreferencenative.h index 63e84c9007298f..ac2b5b6f23171b 100644 --- a/src/coreclr/vm/weakreferencenative.h +++ b/src/coreclr/vm/weakreferencenative.h @@ -13,7 +13,7 @@ #include "weakreference.h" -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) +#if defined(FEATURE_COMINTEROP) class ComAwareWeakReferenceNative { @@ -24,6 +24,6 @@ class ComAwareWeakReferenceNative extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference * pComWeakReference, QCall::ObjectHandleOnStack retRcw); extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj); -#endif // FEATURE_COMINTEROP || FEATURE_COMWRAPPERS +#endif // FEATURE_COMINTEROP #endif // _WEAKREFERENCENATIVE_H From fe7d8530355a845daba0958495e7cb776b802cea Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 28 Mar 2025 14:56:47 -0700 Subject: [PATCH 12/38] QI for identity is done in the managed code now, so we can skip the QI in native code. --- .../vm/interoplibinterface_comwrappers.cpp | 58 +++++++------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index e30182c8b0156d..073b27909a8263 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -114,16 +114,15 @@ bool GlobalComWrappersForMarshalling::TryGetOrCreateComInterfaceForObject( CONTRACTL_END; void* wrapper; - { - GCPROTECT_BEGIN(instance); - 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); + GCPROTECT_BEGIN(instance); - GCPROTECT_END(); - } + 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); + + GCPROTECT_END(); *wrapperRaw = wrapper; @@ -142,39 +141,24 @@ bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( } CONTRACTL_END; - // Determine the true identity of the object - SafeComHolder identity; - { - GCX_PREEMP(); - - HRESULT hr = externalComObject->QueryInterface(IID_IUnknown, &identity); - _ASSERTE(hr == S_OK); - } - - // Switch to Cooperative mode since object references - // are being manipulated. - { - GCX_COOP(); + // 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; - // 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; + OBJECTREF obj = NULL; + GCPROTECT_BEGIN(obj); - OBJECTREF obj = NULL; - GCPROTECT_BEGIN(obj); + 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); - 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(identity); - args[ARGNUM_1] = DWORD_TO_ARGHOLDER(flags); - CALL_MANAGED_METHOD_RETREF(obj, OBJECTREF, args); - - GCPROTECT_END(); + GCPROTECT_END(); - *objRef = obj; - return obj != NULL; - } + *objRef = obj; + return obj != NULL; } extern "C" void* QCALLTYPE ComWrappers_AllocateRefCountedHandle(_In_ QCall::ObjectHandleOnStack obj) From 6b8913f09f7b56ac4c5eb425aa93999e22255c17 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 28 Mar 2025 16:30:07 -0700 Subject: [PATCH 13/38] Fix ifdefs --- src/coreclr/vm/ecalllist.h | 2 ++ src/coreclr/vm/weakreferencenative.h | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 357977ba8a5a68..347cc1ac2ac004 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -359,9 +359,11 @@ FCFuncStart(gGCHandleFuncs) FCFuncElement("InternalCompareExchange", MarshalNative::GCHandleInternalCompareExchange) FCFuncEnd() +#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) FCFuncStart(gComAwareWeakReferenceFuncs) FCFuncElement("HasInteropInfo", ComAwareWeakReferenceNative::HasInteropInfo) FCFuncEnd() +#endif // defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) // // diff --git a/src/coreclr/vm/weakreferencenative.h b/src/coreclr/vm/weakreferencenative.h index ac2b5b6f23171b..d459b597a4dd62 100644 --- a/src/coreclr/vm/weakreferencenative.h +++ b/src/coreclr/vm/weakreferencenative.h @@ -13,7 +13,7 @@ #include "weakreference.h" -#if defined(FEATURE_COMINTEROP) +#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) class ComAwareWeakReferenceNative { @@ -21,6 +21,10 @@ class ComAwareWeakReferenceNative static FCDECL1(FC_BOOL_RET, HasInteropInfo, Object* pObject); }; +#endif // defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) + +#ifdef FEATURE_COMINTEROP + extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference * pComWeakReference, QCall::ObjectHandleOnStack retRcw); extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnStack obj); From 4a09807aef6d551a40904fb49aefe00882b616d1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 31 Mar 2025 17:52:55 +0000 Subject: [PATCH 14/38] Fix calling the ComWrappers API when in a ComWrappers scenario. --- .../src/System/ComAwareWeakReference.CoreCLR.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b90f6f4c7ad0eb..62ccf636f8a774 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -71,7 +71,7 @@ internal static nint ObjectToComWeakRef(object target, out object? context) { // This object is using ComWrappers, so use ComWrappers to create the weak reference. context = target; - return ObjectToComWeakRef(ObjectHandleOnStack.Create(ref target)); + return ComWrappersObjectToComWeakRef(ObjectHandleOnStack.Create(ref target)); } // This object is not produced using built-in COM or ComWrappers From a6b751378fe47d84b12a8dd63609431ae2b302e8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 2 Apr 2025 21:36:43 +0000 Subject: [PATCH 15/38] fix arguments --- .../src/System/ComAwareWeakReference.CoreCLR.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 62ccf636f8a774..915fa9dbda32e3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -69,9 +69,7 @@ internal static nint ObjectToComWeakRef(object target, out object? context) if (PossiblyComWrappersObject(target)) { - // This object is using ComWrappers, so use ComWrappers to create the weak reference. - context = target; - return ComWrappersObjectToComWeakRef(ObjectHandleOnStack.Create(ref target)); + return ComWrappersObjectToComWeakRef(target, out context); } // This object is not produced using built-in COM or ComWrappers From a4c8b8d71adab244035c065f470d25222a4f9605 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 7 Apr 2025 10:53:34 -0500 Subject: [PATCH 16/38] Handle ArgumentException for global marshalling instance. --- .../System/Runtime/InteropServices/ComWrappers.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) 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 index 58dabb8f9fd7df..c5b914ab79b320 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -53,9 +53,18 @@ internal static int CallICustomQueryInterface(ManagedObjectWrapperHolder holder, internal static IntPtr GetOrCreateComInterfaceForObjectWithGlobalMarshallingInstance(object obj) { - return s_globalInstanceForMarshalling is null - ? IntPtr.Zero - : s_globalInstanceForMarshalling.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.TrackerSupport); + 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) From cee2e71c7c67bc973f106d4f9185e01a9629689d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 9 Apr 2025 14:36:16 -0700 Subject: [PATCH 17/38] Fix fallback for RCW case as well --- .../src/System/Runtime/InteropServices/ComWrappers.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 index c5b914ab79b320..9bb3f42e063cc9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -69,7 +69,16 @@ internal static IntPtr GetOrCreateComInterfaceForObjectWithGlobalMarshallingInst internal static object? GetOrCreateObjectForComInstanceWithGlobalMarshallingInstance(IntPtr comObject, CreateObjectFlags flags) { - return s_globalInstanceForMarshalling?.GetOrCreateObjectForComInstance(comObject, 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")] From f6439f29508d12208b544d7470901bdbae85ccaf Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Apr 2025 15:50:21 -0700 Subject: [PATCH 18/38] Fix COM Activation --- .../src/System/Runtime/InteropServices/ComWrappers.Common.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs index b5152edff60821..11087b192a0d90 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs @@ -1066,7 +1066,9 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal( { // 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) + // Don't unwrap Activated wrappers, as we shouldn't peer through COM Activation. + if (unwrappedWrapperInThisContext.ComIp == identity + && !unwrappedWrapperInThisContext.IsActivated) { retValue = unwrapped; return true; From 0ec2e6bbd3398e9fcd87a92cc68ba23355f1a98a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Apr 2025 16:43:26 -0700 Subject: [PATCH 19/38] Various PR feedback --- .../Runtime/InteropServices/ComWrappers.cs | 11 ++-- .../TrackerObjectManager.CoreCLR.cs | 6 +- src/coreclr/interop/comwrappers.cpp | 5 ++ src/coreclr/interop/comwrappers.hpp | 7 +-- src/coreclr/interop/inc/interoplibimports.h | 4 +- src/coreclr/interop/trackerobjectmanager.cpp | 10 ++- .../InteropServices/ComWrappers.NativeAot.cs | 21 ++++--- src/coreclr/vm/comutilnative.cpp | 2 +- src/coreclr/vm/comutilnative.h | 2 +- src/coreclr/vm/gcenv.ee.cpp | 2 +- .../vm/interoplibinterface_comwrappers.cpp | 61 +++++++------------ .../vm/interoplibinterface_comwrappers.h | 11 ++-- src/coreclr/vm/weakreferencenative.cpp | 2 +- .../InteropServices/ComWrappers.Common.cs | 2 +- 14 files changed, 65 insertions(+), 81 deletions(-) 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 index 9bb3f42e063cc9..012bae24f50fa1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -83,22 +83,21 @@ internal static IntPtr GetOrCreateComInterfaceForObjectWithGlobalMarshallingInst [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIReferenceTrackerTargetVftbl")] [SuppressGCTransition] - private static unsafe partial IntPtr GetDefaultIReferenceTrackerTargetVftbl(); + private static partial IntPtr GetDefaultIReferenceTrackerTargetVftbl(); - private static unsafe partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl() + private static partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl() => GetDefaultIReferenceTrackerTargetVftbl(); private static partial IntPtr GetTaggedImplCurrentVersion() { - GetTaggedImpl(out IntPtr fpCurrentVersion); - return fpCurrentVersion; + return GetTaggedImpl(); } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetTaggedImpl")] [SuppressGCTransition] - private static partial void GetTaggedImpl(out IntPtr fpCurrentVersion); + private static partial IntPtr GetTaggedImpl(); - internal sealed unsafe partial class ManagedObjectWrapperHolder + internal sealed partial class ManagedObjectWrapperHolder { static partial void RegisterIsRootedCallback() { 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 index b05be951aee519..b1f83a17c2ea06 100644 --- 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 @@ -15,7 +15,7 @@ private static partial bool HasReferenceTrackerManager [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_HasReferenceTrackerManager")] [SuppressGCTransition] - [return: MarshalAs(UnmanagedType.Bool)] + [return: MarshalAs(UnmanagedType.U1)] private static partial bool HasReferenceTrackerManagerInternal(); private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager) @@ -23,7 +23,7 @@ private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceT [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_TryRegisterReferenceTrackerManager")] [SuppressGCTransition] - [return: MarshalAs(UnmanagedType.Bool)] + [return: MarshalAs(UnmanagedType.U1)] private static partial bool TryRegisterReferenceTrackerManagerInternal(IntPtr referenceTrackerManager); internal static partial bool IsGlobalPeggingEnabled @@ -31,7 +31,7 @@ internal static partial bool IsGlobalPeggingEnabled [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_IsGlobalPeggingEnabled")] [SuppressGCTransition] - [return: MarshalAs(UnmanagedType.Bool)] + [return: MarshalAs(UnmanagedType.U1)] private static partial bool IsGlobalPeggingEnabledInternal(); static partial void RegisterGCCallbacks() diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index b608caa20ec169..6ebbb5c3e4d16a 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -598,3 +598,8 @@ ULONG ManagedObjectWrapper::Release(void) return GetComCount(::InterlockedDecrement64(&_refCount)); } + +InteropLib::OBJECTHANDLE ManagedObjectWrapper::GetTarget() const +{ + return target; +} diff --git a/src/coreclr/interop/comwrappers.hpp b/src/coreclr/interop/comwrappers.hpp index 16b7ef40de3a51..d031f04ac698ed 100644 --- a/src/coreclr/interop/comwrappers.hpp +++ b/src/coreclr/interop/comwrappers.hpp @@ -59,10 +59,7 @@ class ManagedObjectWrapper final : public InteropLib::ABI::ManagedObjectWrapperL // Check if the wrapper has been marked to be destroyed. bool IsMarkedToDestroy() const; - InteropLib::OBJECTHANDLE GetTarget() const - { - return target; - } + InteropLib::OBJECTHANDLE GetTarget() const; public: // IReferenceTrackerTarget ULONG AddRefFromReferenceTracker(); @@ -96,7 +93,7 @@ class TrackerObjectManager // End the reference tracking process for external object. static HRESULT EndReferenceTracking(); - static HRESULT DetachNonPromotedObjects(InteropLibImports::RuntimeCallContext* cxt); + 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/interoplibimports.h b/src/coreclr/interop/inc/interoplibimports.h index b8e8bc9979fa00..a75252bf3019d8 100644 --- a/src/coreclr/interop/inc/interoplibimports.h +++ b/src/coreclr/interop/inc/interoplibimports.h @@ -22,9 +22,7 @@ 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** trackerTarget, _Outptr_result_maybenull_ InteropLib::OBJECTHANDLE* proxyObject) noexcept; diff --git a/src/coreclr/interop/trackerobjectmanager.cpp b/src/coreclr/interop/trackerobjectmanager.cpp index 071c14326852b3..79b34976e3dc17 100644 --- a/src/coreclr/interop/trackerobjectmanager.cpp +++ b/src/coreclr/interop/trackerobjectmanager.cpp @@ -96,11 +96,11 @@ namespace _ASSERTE(cxt != nullptr); BOOL walkFailed = FALSE; - HRESULT hr; + HRESULT hr = S_OK; IReferenceTracker* trackerTarget = nullptr; - OBJECTHANDLE proxyObject = NULL; - while (S_OK == (hr = InteropLibImports::IteratorNext(cxt, (void**)&trackerTarget, &proxyObject))) + OBJECTHANDLE proxyObject = nullptr; + while (InteropLibImports::IteratorNext(cxt, (void**)&trackerTarget, &proxyObject)) { if (trackerTarget == nullptr) continue; @@ -213,7 +213,7 @@ HRESULT TrackerObjectManager::DetachNonPromotedObjects(_In_ RuntimeCallContext* HRESULT hr; IReferenceTracker* trackerTarget = nullptr; OBJECTHANDLE proxyObject = NULL; - while (S_OK == (hr = InteropLibImports::IteratorNext(cxt, (void**)&trackerTarget, &proxyObject))) + while (InteropLibImports::IteratorNext(cxt, (void**)&trackerTarget, &proxyObject)) { if (trackerTarget == nullptr) continue; @@ -221,8 +221,6 @@ HRESULT TrackerObjectManager::DetachNonPromotedObjects(_In_ RuntimeCallContext* if (proxyObject == nullptr) continue; - RETURN_IF_FAILED(hr); - if (!InteropLibImports::IsObjectPromoted(proxyObject)) { RETURN_IF_FAILED(BeforeWrapperFinalized(trackerTarget)); 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 9cfd7ff42f1c5a..22b43815e4c4e3 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 @@ -106,16 +106,19 @@ internal static unsafe uint IReferenceTrackerTarget_Unpeg(IntPtr pThis) return wrapper->Unpeg(); } - private static unsafe partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl() + private static partial 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; + unsafe + { + 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; + } } diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 2b589995e18b17..250949d2425cdb 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, bool lowMemoryPressure) +extern "C" void QCALLTYPE GCInterface_Collect(INT32 generation, INT32 mode, CLR_BOOL lowMemoryPressure) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index cbe100615f3d83..78172b098d1478 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, bool lowMemoryPressure); +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/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 352f9277fed396..0fed5321addb11 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -388,7 +388,7 @@ bool GCToEEInterface::RefCountedHandleCallbacks(Object * pObject) #endif #ifdef FEATURE_COMWRAPPERS bool isRooted = false; - if (ComWrappersNative::IsRooted((OBJECTREF)pObject, &isRooted)) + if (ComWrappersNative::IsManagedObjectComWrapper((OBJECTREF)pObject, &isRooted)) { return isRooted; } diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index 073b27909a8263..f4fbd50ef3a0c2 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -195,16 +195,19 @@ extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl() return vftbl; } -extern "C" void QCALLTYPE ComWrappers_GetTaggedImpl( - _Out_ void const** fpTaggedIsCurrentVersion) +extern "C" void const* QCALLTYPE ComWrappers_GetTaggedImpl() { QCALL_CONTRACT_NO_GC_TRANSITION; + void const* fpTaggedIsCurrentVersion = NULL; + BEGIN_QCALL; - *fpTaggedIsCurrentVersion = InteropLib::Com::GetTaggedCurrentVersionImpl(); + fpTaggedIsCurrentVersion = InteropLib::Com::GetTaggedCurrentVersionImpl(); END_QCALL; + + return fpTaggedIsCurrentVersion; } extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( @@ -217,18 +220,13 @@ extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics GCX_COOP(); - OBJECTREF objRef = obj.Get(); - GCPROTECT_BEGIN(objRef); - // Check the object's SyncBlock for a managed object wrapper. - SyncBlock* syncBlock = objRef->GetSyncBlock(); + SyncBlock* syncBlock = obj.Get()->GetSyncBlock(); InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); _ASSERTE(syncBlock->IsPrecious()); interopInfo->AddManagedObjectComWrapper(wrapper); - GCPROTECT_END(); - END_QCALL; } @@ -242,54 +240,41 @@ extern "C" void QCALLTYPE ComWrappers_RegisterNativeObjectWrapperForDiagnostics( GCX_COOP(); - struct - { - OBJECTREF targetObj; - OBJECTREF wrapperObj; - } gc; - - gc.targetObj = target.Get(); - gc.wrapperObj = wrapper.Get(); - - GCPROTECT_BEGIN(gc); - - SyncBlock* syncBlock = gc.targetObj->GetSyncBlock(); + SyncBlock* syncBlock = target.Get()->GetSyncBlock(); InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); _ASSERTE(syncBlock->IsPrecious()); - OBJECTHANDLE wrapperHandle = GetAppDomain()->CreateLongWeakHandle(gc.wrapperObj); + OBJECTHANDLE wrapperHandle = GetAppDomain()->CreateLongWeakHandle(wrapper.Get()); interopInfo->TrySetExternalComObjectContext((PTR_VOID)wrapperHandle); - GCPROTECT_END(); - END_QCALL; } -extern "C" BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager() +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager() { QCALL_CONTRACT_NO_GC_TRANSITION; - BOOL hasManager = FALSE; + CLR_BOOL hasManager = false; BEGIN_QCALL; - hasManager = InteropLib::Com::HasReferenceTrackerManager() ? TRUE : FALSE; + hasManager = InteropLib::Com::HasReferenceTrackerManager(); END_QCALL; return hasManager; } -extern "C" BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager) +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager) { QCALL_CONTRACT_NO_GC_TRANSITION; - BOOL success = FALSE; + CLR_BOOL success = false; BEGIN_QCALL; - success = InteropLib::Com::TryRegisterReferenceTrackerManager(manager) ? TRUE : FALSE; + success = InteropLib::Com::TryRegisterReferenceTrackerManager(manager); END_QCALL; @@ -497,7 +482,7 @@ namespace InteropLibImports GCHandleSetObject::Iterator _iterator; }; - HRESULT IteratorNext( + bool IteratorNext( _In_ RuntimeCallContext* runtimeContext, _Outptr_result_maybenull_ void** referenceTracker, _Outptr_result_maybenull_ InteropLib::OBJECTHANDLE* proxyHandle) noexcept @@ -520,7 +505,7 @@ namespace InteropLibImports { *referenceTracker = NULL; *proxyHandle = NULL; - return S_FALSE; + return false; } OBJECTHANDLE nativeObjectWrapperHandle = runtimeContext->_iterator.Current(); REFTRACKEROBJECTWRAPPERREF nativeObjectWrapper = (REFTRACKEROBJECTWRAPPERREF)ObjectFromHandle(nativeObjectWrapperHandle); @@ -529,12 +514,12 @@ namespace InteropLibImports { *referenceTracker = NULL; *proxyHandle = NULL; - return S_OK; + return true; } *referenceTracker = dac_cast(nativeObjectWrapper->GetTrackerObject()); *proxyHandle = static_cast(nativeObjectWrapper->GetProxyHandle()); - return S_OK; + return true; } HRESULT FoundReferencePath( @@ -583,7 +568,7 @@ namespace MethodTable* pManagedObjectWrapperHolderMT = NULL; } -bool ComWrappersNative::IsRooted(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted) +bool ComWrappersNative::IsManagedObjectComWrapper(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted) { CONTRACTL { @@ -711,15 +696,15 @@ extern "C" void QCALLTYPE TrackerObjectManager_RegisterNativeObjectWrapperCache( END_QCALL; } -extern "C" BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled() +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled() { QCALL_CONTRACT_NO_GC_TRANSITION; - BOOL isEnabled = FALSE; + CLR_BOOL isEnabled = false; BEGIN_QCALL; - isEnabled = InteropLibImports::GetGlobalPeggingState() ? TRUE : FALSE; + isEnabled = InteropLibImports::GetGlobalPeggingState(); END_QCALL; diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h index 9b092631c461a1..764aa924c39113 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.h +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -20,7 +20,7 @@ class ComWrappersNative static void MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe); public: // Unwrapping support - static bool IsRooted(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted); + static bool IsManagedObjectComWrapper(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted); public: // GC interaction static void OnFullGCStarted(); @@ -38,8 +38,7 @@ extern "C" void* QCALLTYPE ComWrappers_AllocateRefCountedHandle(_In_ QCall::Obje extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl(); -extern "C" void QCALLTYPE ComWrappers_GetTaggedImpl( - _Out_ void const** fpTaggedIsCurrentVersion); +extern "C" void const* QCALLTYPE ComWrappers_GetTaggedImpl(); extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( _In_ QCall::ObjectHandleOnStack obj, @@ -51,13 +50,13 @@ extern "C" void QCALLTYPE ComWrappers_RegisterNativeObjectWrapperForDiagnostics( extern "C" void QCALLTYPE ComWrappers_RegisterIsRootedCallback(); -extern "C" BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager(); +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager(); -extern "C" BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager); +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager); extern "C" void QCALLTYPE TrackerObjectManager_RegisterNativeObjectWrapperCache(_In_ QCall::ObjectHandleOnStack cache); -extern "C" BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled(); +extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled(); class GlobalComWrappersForMarshalling { diff --git a/src/coreclr/vm/weakreferencenative.cpp b/src/coreclr/vm/weakreferencenative.cpp index 495518a954ac5d..caac03309ed8ef 100644 --- a/src/coreclr/vm/weakreferencenative.cpp +++ b/src/coreclr/vm/weakreferencenative.cpp @@ -96,12 +96,12 @@ extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnSt BEGIN_QCALL; 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 diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs index 11087b192a0d90..21a02fe7a0e61b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs @@ -1417,7 +1417,7 @@ private static unsafe IntPtr CreateTaggedImplVftbl() return (IntPtr)vftbl; } - private static unsafe partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl(); + private static partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl(); // Wrapper for IWeakReference private static unsafe class IWeakReference From 6da9c57fe98ba315f1dfe120f969c503d4fde4ce Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Apr 2025 16:44:45 -0700 Subject: [PATCH 20/38] Update src/coreclr/vm/interoplibinterface_shared.cpp Co-authored-by: Aaron Robinson --- src/coreclr/vm/interoplibinterface_shared.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/interoplibinterface_shared.cpp b/src/coreclr/vm/interoplibinterface_shared.cpp index 6a53c9f097e0bb..0e2ae3cdea4638 100644 --- a/src/coreclr/vm/interoplibinterface_shared.cpp +++ b/src/coreclr/vm/interoplibinterface_shared.cpp @@ -146,7 +146,7 @@ void Interop::OnAfterGCScanRoots(_In_ bool isConcurrent) #ifdef FEATURE_COMWRAPPERS ComWrappersNative::OnGCAfterMarkPhase(); -#endif +#endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL // See Interop::OnBeforeGCScanRoots for why non-concurrent. From 9dfcdff637a3f0dafd021a74caba259771361206 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 11 Apr 2025 11:05:40 -0700 Subject: [PATCH 21/38] Fix lifetime issue with the DAC (and provide a mechanism that we can use to enable the diagnostics stack to pull all wrapper information from outside the syncblock so we can get out of the syncblock entirely in the future) --- .../Runtime/InteropServices/ComWrappers.cs | 18 ++++++++++++++++-- .../InteropServices/ComWrappers.Common.cs | 17 +++++------------ 2 files changed, 21 insertions(+), 14 deletions(-) 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 index 012bae24f50fa1..a226263e035e40 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -3,6 +3,8 @@ using System.Threading; using System.Runtime.CompilerServices; +using System.Collections.Generic; +using System.Collections.Concurrent; namespace System.Runtime.InteropServices { @@ -23,9 +25,21 @@ public static unsafe partial void GetIUnknownImpl(out IntPtr fpQueryInterface, o [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetIUnknownImpl")] private static partial void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); - static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper) + private static readonly ConditionalWeakTable> s_allManagedObjectWrapperTable = []; + + static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper) { - RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref instance), wrapper); + // 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); + } + RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref instance), wrapper.Wrapper); } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterManagedObjectWrapperForDiagnostics")] diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs index 21a02fe7a0e61b..86137cb04b92a5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs @@ -733,13 +733,6 @@ public void DisconnectTracker() } } - // 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. /// @@ -755,18 +748,18 @@ public unsafe IntPtr GetOrCreateComInterfaceForObject(object instance, CreateCom { ArgumentNullException.ThrowIfNull(instance); - ManagedObjectWrapperHolder managedObjectWrapper = _managedObjectWrapperTable.GetOrAdd(instance, static (c, items) => + ManagedObjectWrapperHolder managedObjectWrapper = _managedObjectWrapperTable.GetOrAdd(instance, static (c, state) => { - ManagedObjectWrapper* value = items.This!.CreateManagedObjectWrapper(c, items.Flags); + ManagedObjectWrapper* value = state.This!.CreateManagedObjectWrapper(c, state.flags); return new ManagedObjectWrapperHolder(value, c); - }, new GetOrCreateComInterfaceForObjectParameters { This = this, Flags = flags }); + }, new { This = this, flags }); managedObjectWrapper.AddRef(); - RegisterManagedObjectWrapperForDiagnostics(instance, managedObjectWrapper.Wrapper); + RegisterManagedObjectWrapperForDiagnostics(instance, managedObjectWrapper); return managedObjectWrapper.ComIp; } - static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapper* wrapper); + static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper); private static nuint AlignUp(nuint value, nuint alignment) { From d8605e76f69295bb9d8458f81806e5a933b59847 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 11 Apr 2025 11:53:34 -0700 Subject: [PATCH 22/38] PR feedback --- src/coreclr/vm/interoplibinterface_comwrappers.h | 2 +- .../System/Runtime/InteropServices/ReferenceTrackerHost.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h index 764aa924c39113..91c0c723ae3a06 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.h +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -172,4 +172,4 @@ class GCHandleSetObject final : public Object }; }; -#endif +#endif // _INTEROPLIBINTERFACE_COMWRAPPERS_H_ 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 index eb34c45a054743..c38ebaa07226d2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs @@ -47,7 +47,6 @@ internal static unsafe int IReferenceTrackerHost_DisconnectUnusedReferenceSource { return Marshal.GetHRForException(e); } - } [UnmanagedCallersOnly] @@ -71,7 +70,6 @@ internal static unsafe int IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnT { return Marshal.GetHRForException(e); } - } // Creates a proxy object (managed object wrapper) that points to the given IUnknown. @@ -156,7 +154,7 @@ internal static unsafe int IReferenceTrackerHost_QueryInterface(IntPtr pThis, Gu if (*guid == ComWrappers.IID_IReferenceTrackerHost || *guid == ComWrappers.IID_IUnknown) { *ppObject = pThis; - return 0; + return HResults.S_OK; } else { From 1ae095fc7204ca263269be55217940d6ebf74cb1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 11 Apr 2025 16:55:54 -0700 Subject: [PATCH 23/38] Various PR feedback --- .../System.Private.CoreLib.csproj | 4 +- ...{ComWrappers.cs => ComWrappers.CoreCLR.cs} | 20 +- .../TrackerObjectManager.CoreCLR.cs | 11 +- src/coreclr/interop/comwrappers.cpp | 6 +- src/coreclr/interop/comwrappers.hpp | 4 +- src/coreclr/interop/inc/interoplibabi.h | 4 +- src/coreclr/interop/interoplib.cpp | 4 +- src/coreclr/interop/trackerobjectmanager.cpp | 27 +- .../InteropServices/ComWrappers.NativeAot.cs | 80 +- .../TrackerObjectManager.NativeAot.cs | 8 +- src/coreclr/vm/corelib.h | 4 +- .../vm/interoplibinterface_comwrappers.cpp | 58 +- .../vm/interoplibinterface_comwrappers.h | 4 +- .../System.Private.CoreLib.Shared.projitems | 5 +- .../InteropServices/ComWrappers.Common.cs | 1712 ---------------- .../ComWrappers.PlatformNotSupported.cs | 23 +- .../Runtime/InteropServices/ComWrappers.cs | 1730 ++++++++++++++++- .../CreateComInterfaceFlags.cs | 32 + .../InteropServices/CreateObjectFlags.cs | 42 + .../InteropServices/ReferenceTrackerHost.cs | 1 + .../InteropServices/TrackerObjectManager.cs | 8 - 21 files changed, 1886 insertions(+), 1901 deletions(-) rename src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/{ComWrappers.cs => ComWrappers.CoreCLR.cs} (89%) delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateComInterfaceFlags.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/CreateObjectFlags.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index de06d0d1d45fa0..1083982c37cf49 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -242,10 +242,10 @@ - - + + 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.CoreCLR.cs similarity index 89% rename from src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs rename to src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreCLR.cs index a226263e035e40..a0311be1170621 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreCLR.cs @@ -19,15 +19,16 @@ public abstract partial class ComWrappers /// Function pointer to QueryInterface. /// Function pointer to AddRef. /// Function pointer to Release. - public static unsafe partial void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) + 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); private static readonly ConditionalWeakTable> s_allManagedObjectWrapperTable = []; - static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper) + private static unsafe void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper) { // 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, @@ -45,7 +46,7 @@ static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object ins [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterManagedObjectWrapperForDiagnostics")] private static unsafe partial void RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack instance, ManagedObjectWrapper* wrapper); - static partial void RegisterNativeObjectWrapperForDiagnostics(NativeObjectWrapper registeredWrapper) + private static void RegisterNativeObjectWrapperForDiagnostics(NativeObjectWrapper registeredWrapper) { object target = registeredWrapper.ProxyHandle.Target!; RegisterNativeObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref target), ObjectHandleOnStack.Create(ref registeredWrapper)); @@ -99,10 +100,10 @@ internal static IntPtr GetOrCreateComInterfaceForObjectWithGlobalMarshallingInst [SuppressGCTransition] private static partial IntPtr GetDefaultIReferenceTrackerTargetVftbl(); - private static partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl() + private static IntPtr CreateDefaultIReferenceTrackerTargetVftbl() => GetDefaultIReferenceTrackerTargetVftbl(); - private static partial IntPtr GetTaggedImplCurrentVersion() + private static IntPtr GetTaggedImplCurrentVersion() { return GetTaggedImpl(); } @@ -113,15 +114,10 @@ private static partial IntPtr GetTaggedImplCurrentVersion() internal sealed partial class ManagedObjectWrapperHolder { - static partial void RegisterIsRootedCallback() - { - RegisterIsRootedCallbackInternal(); - } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterIsRootedCallback")] - private static partial void RegisterIsRootedCallbackInternal(); + private static partial void RegisterIsRootedCallback(); - private static partial IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) + private static IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) { return AllocateRefCountedHandle(ObjectHandleOnStack.Create(ref holder)); } 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 index b1f83a17c2ea06..ac60cd56719b64 100644 --- 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 @@ -10,7 +10,7 @@ namespace System.Runtime.InteropServices { internal static partial class TrackerObjectManager { - private static partial bool HasReferenceTrackerManager + private static bool HasReferenceTrackerManager => HasReferenceTrackerManagerInternal(); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_HasReferenceTrackerManager")] @@ -18,15 +18,12 @@ private static partial bool HasReferenceTrackerManager [return: MarshalAs(UnmanagedType.U1)] private static partial bool HasReferenceTrackerManagerInternal(); - private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager) - => TryRegisterReferenceTrackerManagerInternal(referenceTrackerManager); - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_TryRegisterReferenceTrackerManager")] [SuppressGCTransition] [return: MarshalAs(UnmanagedType.U1)] - private static partial bool TryRegisterReferenceTrackerManagerInternal(IntPtr referenceTrackerManager); + private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager); - internal static partial bool IsGlobalPeggingEnabled + internal static bool IsGlobalPeggingEnabled => IsGlobalPeggingEnabledInternal(); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TrackerObjectManager_IsGlobalPeggingEnabled")] @@ -34,7 +31,7 @@ internal static partial bool IsGlobalPeggingEnabled [return: MarshalAs(UnmanagedType.U1)] private static partial bool IsGlobalPeggingEnabledInternal(); - static partial void RegisterGCCallbacks() + 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. diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index 6ebbb5c3e4d16a..ccbffbe1956416 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -501,7 +501,7 @@ ULONG ManagedObjectWrapper::ReleaseFromReferenceTracker() // must destroy the wrapper. if (refCount == DestroySentinel) { - InteropLib::OBJECTHANDLE handle = InterlockedExchangePointer(&target, nullptr); + InteropLib::OBJECTHANDLE handle = InterlockedExchangePointer(&_target, nullptr); if (handle != nullptr) { InteropLibImports::DestroyHandle(handle); @@ -537,7 +537,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: @@ -601,5 +601,5 @@ ULONG ManagedObjectWrapper::Release(void) InteropLib::OBJECTHANDLE ManagedObjectWrapper::GetTarget() const { - return target; + return _target; } diff --git a/src/coreclr/interop/comwrappers.hpp b/src/coreclr/interop/comwrappers.hpp index d031f04ac698ed..00ebfc39194b96 100644 --- a/src/coreclr/interop/comwrappers.hpp +++ b/src/coreclr/interop/comwrappers.hpp @@ -79,9 +79,9 @@ class ManagedObjectWrapper final : public InteropLib::ABI::ManagedObjectWrapperL class TrackerObjectManager { public: - static HRESULT HasReferenceTrackerManager(); + static bool HasReferenceTrackerManager(); - static HRESULT TryRegisterReferenceTrackerManager(_In_ IReferenceTrackerManager* manager); + 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); diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index 87335b36d1036f..18e699d80da268 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -35,8 +35,8 @@ namespace InteropLib } protected: - Volatile target; - LONGLONG _refCount; + Volatile _target; + int64_t _refCount; Volatile _flags; int32_t _userDefinedCount; diff --git a/src/coreclr/interop/interoplib.cpp b/src/coreclr/interop/interoplib.cpp index 928f037646bc0b..b86842bcc3f0fe 100644 --- a/src/coreclr/interop/interoplib.cpp +++ b/src/coreclr/interop/interoplib.cpp @@ -64,12 +64,12 @@ namespace InteropLib bool HasReferenceTrackerManager() noexcept { - return TrackerObjectManager::HasReferenceTrackerManager() == S_OK; + return TrackerObjectManager::HasReferenceTrackerManager(); } bool TryRegisterReferenceTrackerManager(_In_ void* manager) noexcept { - return TrackerObjectManager::TryRegisterReferenceTrackerManager((IReferenceTrackerManager*)manager) == S_OK; + return TrackerObjectManager::TryRegisterReferenceTrackerManager((IReferenceTrackerManager*)manager); } bool IsRooted(InteropLib::ABI::ManagedObjectWrapperLayout* mow) noexcept diff --git a/src/coreclr/interop/trackerobjectmanager.cpp b/src/coreclr/interop/trackerobjectmanager.cpp index 79b34976e3dc17..cc178d4187179b 100644 --- a/src/coreclr/interop/trackerobjectmanager.cpp +++ b/src/coreclr/interop/trackerobjectmanager.cpp @@ -9,14 +9,11 @@ using RuntimeCallContext = InteropLibImports::RuntimeCallContext; namespace { - // 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} }; 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?) @@ -95,7 +92,7 @@ namespace { _ASSERTE(cxt != nullptr); - BOOL walkFailed = FALSE; + bool walkFailed = false; HRESULT hr = S_OK; IReferenceTracker* trackerTarget = nullptr; @@ -115,26 +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::HasReferenceTrackerManager() +bool TrackerObjectManager::HasReferenceTrackerManager() { - return (s_TrackerManager != nullptr) ? S_OK : S_FALSE; + return s_TrackerManager != nullptr; } -HRESULT TrackerObjectManager::TryRegisterReferenceTrackerManager(_In_ IReferenceTrackerManager* manager) +bool TrackerObjectManager::TryRegisterReferenceTrackerManager(_In_ IReferenceTrackerManager* manager) { _ASSERTE(manager != nullptr); - return (InterlockedCompareExchangePointer((void**)&s_TrackerManager, manager, nullptr) == nullptr) ? S_OK : S_FALSE; + return InterlockedCompareExchangePointer((void**)&s_TrackerManager, manager, nullptr) == nullptr; } HRESULT TrackerObjectManager::BeforeWrapperFinalized(_In_ IReferenceTracker* obj) @@ -161,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 @@ -185,7 +182,7 @@ HRESULT TrackerObjectManager::BeginReferenceTracking(_In_ RuntimeCallContext* cx HRESULT TrackerObjectManager::EndReferenceTracking() { - if (s_HasTrackingStarted != TRUE + if (s_HasTrackingStarted != true || !ShouldWalkExternalObjects()) return S_FALSE; @@ -201,7 +198,7 @@ HRESULT TrackerObjectManager::EndReferenceTracking() _ASSERTE(SUCCEEDED(hr)); InteropLibImports::SetGlobalPeggingState(true); - s_HasTrackingStarted = FALSE; + s_HasTrackingStarted = false; return hr; } 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 22b43815e4c4e3..0eea65a4c1aab4 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 @@ -20,7 +20,7 @@ public abstract partial class ComWrappers { internal sealed unsafe partial class ManagedObjectWrapperHolder { - static partial void RegisterIsRootedCallback() + private static void RegisterIsRootedCallback() { delegate* unmanaged callback = &IsRootedCallback; if (!RuntimeImports.RhRegisterRefCountedHandleCallback((nint)callback, MethodTable.Of())) @@ -37,7 +37,7 @@ private static bool IsRootedCallback(IntPtr pObj) return holder->_wrapper->IsRooted; } - private static partial IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) + private static IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder) { return RuntimeImports.RhHandleAllocRefCounted(holder); } @@ -49,7 +49,7 @@ private static partial IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolde /// Function pointer to QueryInterface. /// Function pointer to AddRef. /// Function pointer to Release. - public static unsafe partial void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) + public static unsafe void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) { fpQueryInterface = (IntPtr)(delegate* unmanaged)&ComWrappers.IUnknown_QueryInterface; fpAddRef = RuntimeImports.RhGetIUnknownAddRef(); // Implemented in C/C++ to avoid GC transitions @@ -106,23 +106,11 @@ internal static unsafe uint IReferenceTrackerTarget_Unpeg(IntPtr pThis) return wrapper->Unpeg(); } - private static partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl() + private static unsafe IntPtr CreateDefaultIReferenceTrackerTargetVftbl() { - unsafe - { - 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; - } + return (IntPtr)Unsafe.AsPointer(ref Unsafe.AsRef(in VtableImplementations.IReferenceTrackerTarget)); } - - [UnmanagedCallersOnly] internal static unsafe int ITaggedImpl_IsCurrentVersion(IntPtr pThis, IntPtr version) { @@ -131,12 +119,68 @@ internal static unsafe int ITaggedImpl_IsCurrentVersion(IntPtr pThis, IntPtr ver : HResults.E_FAIL; } - private static partial IntPtr GetTaggedImplCurrentVersion() + private static IntPtr GetTaggedImplCurrentVersion() { unsafe { return (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion; } } + + private static class VtableImplementations + { + public unsafe struct IUnknownVftbl + { + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; + } + + public unsafe struct IReferenceTrackerTargetVftbl + { + public IUnknownVftbl IUnknownVftbl; + public delegate* unmanaged AddRefFromReferenceTracker; + public delegate* unmanaged ReleaseFromReferenceTracker; + public delegate* unmanaged Peg; + public delegate* unmanaged Unpeg; + } + + public unsafe struct ITaggedImplVftbl + { + public IUnknownVftbl IUnknownVftbl; + public delegate* unmanaged IsCurrentVersion; + } + + [FixedAddressValueType] + public static readonly IUnknownVftbl IUnknown; + + [FixedAddressValueType] + public static readonly IReferenceTrackerTargetVftbl IReferenceTrackerTarget; + + [FixedAddressValueType] + public static readonly ITaggedImplVftbl ITaggedImpl; + + 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.IUnknownVftbl.QueryInterface = (delegate* unmanaged)&IReferenceTrackerTarget_QueryInterface; + GetIUnknownImpl( + fpQueryInterface: out _, + fpAddRef: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget.IUnknownVftbl))->AddRef, + fpRelease: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget.IUnknownVftbl))->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; + + *(IUnknownVftbl*)Unsafe.AsPointer(ref ITaggedImpl) = IUnknown; + 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 6ed9677b6df062..74bf88f13ae40f 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 @@ -95,17 +95,17 @@ public static bool AddReferencePath(object target, object foundReference) return s_referenceCache.AddDependentHandle(target, foundReference); } - private static partial bool HasReferenceTrackerManager + private static bool HasReferenceTrackerManager => s_trackerManager != IntPtr.Zero; - private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager) + private static bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager) { return Interlocked.CompareExchange(ref s_trackerManager, referenceTrackerManager, IntPtr.Zero) == IntPtr.Zero; } - internal static partial bool IsGlobalPeggingEnabled => s_isGlobalPeggingOn; + internal static bool IsGlobalPeggingEnabled => s_isGlobalPeggingOn; - static partial void RegisterGCCallbacks() + private static void RegisterGCCallbacks() { unsafe { diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 780e04dd95e911..78e97f6889a47e 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -443,8 +443,8 @@ DEFINE_FIELD_U(_trackerObject, ReferenceTrackerNativeObjectWrapperObject, _track DEFINE_FIELD_U(_contextToken, ReferenceTrackerNativeObjectWrapperObject, _contextToken) DEFINE_FIELD_U(_trackerObjectDisconnected, ReferenceTrackerNativeObjectWrapperObject, _trackerObjectDisconnected) DEFINE_CLASS_U(Interop, GCHandleSet+Entry, GCHandleSetEntryObject) -DEFINE_FIELD_U(m_next, GCHandleSetEntryObject, m_next) -DEFINE_FIELD_U(m_value, GCHandleSetEntryObject, m_handle) +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 diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index f4fbd50ef3a0c2..7b59a47017cfef 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -56,17 +56,13 @@ extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( _Out_ void** fpAddRef, _Out_ void** fpRelease) { - QCALL_CONTRACT; + QCALL_CONTRACT_NO_GC_TRANSITION; _ASSERTE(fpQueryInterface != NULL); _ASSERTE(fpAddRef != NULL); _ASSERTE(fpRelease != NULL); - BEGIN_QCALL; - InteropLib::Com::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); - - END_QCALL; } void ComWrappersNative::DestroyExternalComObjectContext(_In_ void* contextRaw) @@ -186,28 +182,14 @@ extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl() void const* vftbl = NULL; - BEGIN_QCALL; - - vftbl = InteropLib::Com::GetIReferenceTrackerTargetVftbl(); - - END_QCALL; - - return vftbl; + return InteropLib::Com::GetIReferenceTrackerTargetVftbl(); } extern "C" void const* QCALLTYPE ComWrappers_GetTaggedImpl() { QCALL_CONTRACT_NO_GC_TRANSITION; - void const* fpTaggedIsCurrentVersion = NULL; - - BEGIN_QCALL; - - fpTaggedIsCurrentVersion = InteropLib::Com::GetTaggedCurrentVersionImpl(); - - END_QCALL; - - return fpTaggedIsCurrentVersion; + return InteropLib::Com::GetTaggedCurrentVersionImpl(); } extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( @@ -255,36 +237,20 @@ extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager() { QCALL_CONTRACT_NO_GC_TRANSITION; - CLR_BOOL hasManager = false; - - BEGIN_QCALL; - - hasManager = InteropLib::Com::HasReferenceTrackerManager(); - - END_QCALL; - - return hasManager; + return InteropLib::Com::HasReferenceTrackerManager(); } extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_TryRegisterReferenceTrackerManager(_In_ void* manager) { QCALL_CONTRACT_NO_GC_TRANSITION; - CLR_BOOL success = false; - - BEGIN_QCALL; - - success = InteropLib::Com::TryRegisterReferenceTrackerManager(manager); - - END_QCALL; - - return success; + return InteropLib::Com::TryRegisterReferenceTrackerManager(manager); } OBJECTHANDLE GCHandleSetObject::Iterator::Current() const { LIMITED_METHOD_CONTRACT; - return _currentEntry->m_handle; + return _currentEntry->_value; } bool GCHandleSetObject::Iterator::MoveNext() @@ -302,7 +268,7 @@ bool GCHandleSetObject::Iterator::MoveNext() if (_currentEntry != NULL) { - _currentEntry = _currentEntry->m_next; + _currentEntry = _currentEntry->_next; } if (_currentEntry == NULL) @@ -700,15 +666,7 @@ extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled() { QCALL_CONTRACT_NO_GC_TRANSITION; - CLR_BOOL isEnabled = false; - - BEGIN_QCALL; - - isEnabled = InteropLibImports::GetGlobalPeggingState(); - - END_QCALL; - - return isEnabled; + return InteropLibImports::GetGlobalPeggingState(); } #endif // FEATURE_COMWRAPPERS diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h index 91c0c723ae3a06..838f4c739634fb 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.h +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -141,8 +141,8 @@ class GCHandleSetEntryObject final : public Object { friend class CoreLibBinder; public: - HANDLESETENTRYREF m_next; - OBJECTHANDLE m_handle; + HANDLESETENTRYREF _next; + OBJECTHANDLE _value; }; class GCHandleSetObject final : public Object 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 8fa5266b63ca72..d6f4a50fd3aa67 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 @@ -958,7 +958,8 @@ - + + @@ -2257,7 +2258,7 @@ - + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs deleted file mode 100644 index 86137cb04b92a5..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.Common.cs +++ /dev/null @@ -1,1712 +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.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 -{ - /// - /// Class for managing wrappers of COM IUnknown types. - /// - 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 readonly ConditionalWeakTable _managedObjectWrapperTable = new ConditionalWeakTable(); - private readonly RcwCache _rcwCache = new(); - - 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; - } - - /// - /// ABI for function dispatch of a COM interface. - /// - public unsafe partial struct ComInterfaceDispatch - { - /// - /// 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(); - } - - static partial void RegisterIsRootedCallback(); - - private static partial IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder); - - 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; - } - } - } - - /// - /// Globally registered instance of the ComWrappers class for reference tracker support. - /// - private static ComWrappers? s_globalInstanceForTrackerSupport; - - internal static ComWrappers? GlobalInstanceForTrackerSupport => s_globalInstanceForTrackerSupport; - - /// - /// Globally registered instance of the ComWrappers class for marshalling. - /// - 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; - } - } - - /// - /// 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, 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; - } - - static unsafe partial void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder 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; - } - - /// - /// 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 = 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(); - } - - RegisterNativeObjectWrapperForDiagnostics(registeredWrapper); - - // 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); - } - - static partial void RegisterNativeObjectWrapperForDiagnostics(NativeObjectWrapper 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 = []; - - /// - /// 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 partial void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); - - 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; - } - - 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 partial IntPtr GetTaggedImplCurrentVersion(); - - 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; - } - - private static partial IntPtr CreateDefaultIReferenceTrackerTargetVftbl(); - - // 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.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(); - } - } - } - - 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); - - // 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; - - 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.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; - } - } - } - - 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? m_next; - public GCHandle m_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.m_value; - } - } - - object IEnumerator.Current => Current; - - public void Dispose() - { - } - - public bool MoveNext() - { - 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; - } - - 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/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 370eef6bdf90a0..2c401aa174d3ab 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,1346 @@ // 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 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 readonly ConditionalWeakTable _managedObjectWrapperTable = new ConditionalWeakTable(); + private readonly RcwCache _rcwCache = new(); + + 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; + } /// - /// The caller will provide an IUnknown Vtable. + /// ABI for function dispatch of a COM interface. /// - /// - /// 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, + 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; + } + } + } /// - /// 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. + /// Globally registered instance of the ComWrappers class for reference tracker support. /// - TrackerSupport = 2, - } + private static ComWrappers? s_globalInstanceForTrackerSupport; - /// - /// Enumeration of flags for . - /// - [Flags] - public enum CreateObjectFlags - { - None = 0, + internal static ComWrappers? GlobalInstanceForTrackerSupport => s_globalInstanceForTrackerSupport; + + /// + /// Globally registered instance of the ComWrappers class for marshalling. + /// + 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; + } + } /// - /// Indicate if the supplied external COM object implements the IReferenceTracker. + /// Create a COM representation of the supplied object that can be passed to a non-managed environment. /// - TrackerObject = 1, + /// 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, state) => + { + ManagedObjectWrapper* value = state.This!.CreateManagedObjectWrapper(c, state.flags); + return new ManagedObjectWrapperHolder(value, c); + }, new { This = this, flags }); + + managedObjectWrapper.AddRef(); +#if CORECLR + RegisterManagedObjectWrapperForDiagnostics(instance, managedObjectWrapper); +#endif + 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; + } /// - /// Ignore any internal caching and always create a unique instance. + /// Get the currently registered managed object or creates a new managed object and registers it. /// - UniqueInstance = 2, + /// 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; + } /// - /// Defined when COM aggregation is involved (that is an inner instance supplied). + /// Get the currently registered managed object or uses the supplied managed object and registers it. /// - Aggregation = 4, + /// 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); + } /// - /// Check if the supplied instance is actually a wrapper and if so return the underlying - /// managed object rather than creating a new wrapper. + /// 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 matches the built-in RCW semantics for COM interop. + /// 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. /// - Unwrap = 8, - } + 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; + } + } - /// - /// 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. + /// 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) { - /// - /// Interface IID. - /// - public Guid IID; + 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(); + } + +#if CORECLR + RegisterNativeObjectWrapperForDiagnostics(registeredWrapper); +#endif + + // 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); + } } /// @@ -131,5 +1376,378 @@ public partial struct ComInterfaceDispatch /// /// Collection of objects to release. 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; + } + + 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; + } + + // 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/ReferenceTrackerHost.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs index c38ebaa07226d2..7d179d8c16b6a6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs @@ -154,6 +154,7 @@ internal static unsafe int IReferenceTrackerHost_QueryInterface(IntPtr pThis, Gu if (*guid == ComWrappers.IID_IReferenceTrackerHost || *guid == ComWrappers.IID_IUnknown) { *ppObject = pThis; + Marshal.AddRef(pThis); return HResults.S_OK; } else 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 index bb9cbf7500e76a..df0053629c9ca6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.cs @@ -37,14 +37,6 @@ internal static void OnIReferenceTrackerFound(IntPtr referenceTracker) } } - internal static partial bool IsGlobalPeggingEnabled { get; } - - private static partial bool HasReferenceTrackerManager { get; } - - private static partial bool TryRegisterReferenceTrackerManager(IntPtr referenceTrackerManager); - - static partial void RegisterGCCallbacks(); - internal static void AfterWrapperCreated(IntPtr referenceTracker) { Debug.Assert(referenceTracker != IntPtr.Zero); From c8d027774a0bfd27363bc9e37f904366a8d925b3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 11 Apr 2025 17:25:17 -0700 Subject: [PATCH 24/38] Use the pre-init pattern for all vtables on NativeAOT. --- .../InteropServices/ComWrappers.CoreCLR.cs | 15 +++ .../InteropServices/ComWrappers.NativeAot.cs | 97 +++++++++++-------- .../TrackerObjectManager.NativeAot.cs | 26 ++++- .../Runtime/InteropServices/ComWrappers.cs | 15 --- .../InteropServices/ReferenceTrackerHost.cs | 86 ++++++++-------- 5 files changed, 134 insertions(+), 105 deletions(-) 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 index a0311be1170621..53dc15d8dcd048 100644 --- 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 @@ -26,6 +26,21 @@ public static unsafe void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPt [SuppressGCTransition] private static partial void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease); + 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; + } + private static readonly ConditionalWeakTable> s_allManagedObjectWrapperTable = []; private static unsafe void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper) 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 1218534c90d7e2..9e279d305f34ed 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 @@ -71,61 +71,29 @@ 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) + private static IntPtr GetTaggedImplCurrentVersion() { - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); - return wrapper->Peg(); + unsafe + { + return (IntPtr)(delegate* unmanaged)&VtableImplementations.ITaggedImpl_IsCurrentVersion; + } } - [UnmanagedCallersOnly] - internal static unsafe uint IReferenceTrackerTarget_Unpeg(IntPtr pThis) + private static unsafe IntPtr CreateDefaultIUnknownVftbl() { - ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis); - return wrapper->Unpeg(); + return (IntPtr)Unsafe.AsPointer(in VtableImplementations.IUnknown); } private static unsafe IntPtr CreateDefaultIReferenceTrackerTargetVftbl() { - return (IntPtr)Unsafe.AsPointer(ref Unsafe.AsRef(in VtableImplementations.IReferenceTrackerTarget)); + return (IntPtr)Unsafe.AsPointer(in VtableImplementations.IReferenceTrackerTarget); } - [UnmanagedCallersOnly] - internal static unsafe int ITaggedImpl_IsCurrentVersion(IntPtr pThis, IntPtr version) + private static unsafe IntPtr CreateTaggedImplVftbl() { - return version == (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion - ? HResults.S_OK - : HResults.E_FAIL; + return (IntPtr)Unsafe.AsPointer(in VtableImplementations.ITaggedImpl); } - private static IntPtr GetTaggedImplCurrentVersion() - { - unsafe - { - return (IntPtr)(delegate* unmanaged)&ITaggedImpl_IsCurrentVersion; - } - } private static class VtableImplementations { @@ -160,6 +128,51 @@ public unsafe struct ITaggedImplVftbl [FixedAddressValueType] public static readonly ITaggedImplVftbl ITaggedImpl; + + + [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; + } + static unsafe VtableImplementations() { // Use the "pre-inited vtable" pattern to ensure that ILC can pre-compile these vtables. 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 74bf88f13ae40f..d381b04ff0da8f 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 @@ -12,7 +12,8 @@ namespace System.Runtime.InteropServices { internal static partial class TrackerObjectManager { - internal static readonly IntPtr s_findReferencesTargetCallback = FindReferenceTargetsCallback.CreateFindReferenceTargetsCallback(); + [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; @@ -244,11 +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; + } + + [FixedAddressValueType] + internal static readonly ReferenceTargetsVftbl Vftbl; + +#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 + { + Vftbl.AddRef = &Untracked_AddRef; + Vftbl.Release = &Untracked_Release; + Vftbl.QueryInterface = &IFindReferenceTargetsCallback_QueryInterface; + Vftbl.FoundTrackerTarget = &IFindReferenceTargetsCallback_FoundTrackerTarget; } } 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 2c401aa174d3ab..9b2f6adef04bc5 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 @@ -1438,21 +1438,6 @@ internal static unsafe uint Untracked_Release(IntPtr _) return 1; } - 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; - } - // Wrapper for IWeakReference private static unsafe class IWeakReference { 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 index 7d179d8c16b6a6..80a52ca2e42377 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs @@ -10,7 +10,8 @@ namespace System.Runtime.InteropServices { internal static class ReferenceTrackerHost { - internal static readonly IntPtr s_globalHostServices = CreateHostServices(); + [FixedAddressValueType] + internal static readonly unsafe IntPtr s_globalHostServices = (IntPtr)Unsafe.AsPointer(in HostServices.Vftbl); // Called when an IReferenceTracker instance is found. public static void SetReferenceTrackerHost(IntPtr trackerManager) @@ -18,15 +19,10 @@ public static void SetReferenceTrackerHost(IntPtr trackerManager) IReferenceTrackerManager.SetReferenceTrackerHost(trackerManager, s_globalHostServices); } - private static unsafe IntPtr CreateHostServices() - { - IntPtr* wrapperMem = (IntPtr*)NativeMemory.Alloc((nuint)sizeof(IntPtr)); - wrapperMem[0] = CreateDefaultIReferenceTrackerHostVftbl(); - return (IntPtr)wrapperMem; - } - +#pragma warning disable IDE0060 [UnmanagedCallersOnly] internal static unsafe int IReferenceTrackerHost_DisconnectUnusedReferenceSources(IntPtr pThis, uint flags) +#pragma warning restore IDE0060 { try { @@ -49,8 +45,10 @@ internal static unsafe int IReferenceTrackerHost_DisconnectUnusedReferenceSource } } +#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 @@ -58,8 +56,10 @@ internal static unsafe int IReferenceTrackerHost_ReleaseDisconnectedReferenceSou return HResults.S_OK; } +#pragma warning disable IDE0060 [UnmanagedCallersOnly] internal static unsafe int IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnThread(IntPtr pThis) +#pragma warning restore IDE0060 { try { @@ -72,31 +72,10 @@ internal static unsafe int IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnT } } - // 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. - // +#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) { @@ -120,8 +99,10 @@ internal static unsafe int IReferenceTrackerHost_GetTrackerTarget(IntPtr pThis, } } +#pragma warning disable IDE0060 [UnmanagedCallersOnly] internal static unsafe int IReferenceTrackerHost_AddMemoryPressure(IntPtr pThis, long bytesAllocated) +#pragma warning restore IDE0060 { try { @@ -134,8 +115,10 @@ internal static unsafe int IReferenceTrackerHost_AddMemoryPressure(IntPtr pThis, } } +#pragma warning disable IDE0060 [UnmanagedCallersOnly] internal static unsafe int IReferenceTrackerHost_RemoveMemoryPressure(IntPtr pThis, long bytesAllocated) +#pragma warning restore IDE0060 { try { @@ -163,19 +146,36 @@ internal static unsafe int IReferenceTrackerHost_QueryInterface(IntPtr pThis, Gu } } - internal static unsafe IntPtr CreateDefaultIReferenceTrackerHostVftbl() + private unsafe struct IReferenceTrackerHostVftbl { - IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ReferenceTrackerHost), 9 * sizeof(IntPtr)); - vftbl[0] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_QueryInterface; - vftbl[1] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_AddRef; - vftbl[2] = (IntPtr)(delegate* unmanaged)&ComWrappers.Untracked_Release; - vftbl[3] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_DisconnectUnusedReferenceSources; - vftbl[4] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_ReleaseDisconnectedReferenceSources; - vftbl[5] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_NotifyEndOfReferenceTrackingOnThread; - vftbl[6] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_GetTrackerTarget; - vftbl[7] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_AddMemoryPressure; - vftbl[8] = (IntPtr)(delegate* unmanaged)&IReferenceTrackerHost_RemoveMemoryPressure; - return (IntPtr)vftbl; + 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; + } } } From b123e15eb19a98a7db70d6b332ae0115f3877987 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Apr 2025 10:11:39 -0700 Subject: [PATCH 25/38] PR feedback --- .../InteropServices/ComWrappers.CoreCLR.cs | 4 ++++ .../InteropServices/ComWrappers.NativeAot.cs | 18 +++--------------- .../TrackerObjectManager.NativeAot.cs | 2 +- .../Runtime/InteropServices/ComWrappers.cs | 4 ---- .../InteropServices/ReferenceTrackerHost.cs | 6 +++--- 5 files changed, 11 insertions(+), 23 deletions(-) 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 index 53dc15d8dcd048..d02abd8191758e 100644 --- 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 @@ -26,6 +26,10 @@ public static unsafe void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPt [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)); 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 9e279d305f34ed..279d1e89a594fb 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 @@ -79,21 +79,9 @@ private static IntPtr GetTaggedImplCurrentVersion() } } - private static unsafe IntPtr CreateDefaultIUnknownVftbl() - { - return (IntPtr)Unsafe.AsPointer(in VtableImplementations.IUnknown); - } - - private static unsafe IntPtr CreateDefaultIReferenceTrackerTargetVftbl() - { - return (IntPtr)Unsafe.AsPointer(in VtableImplementations.IReferenceTrackerTarget); - } - - private static unsafe IntPtr CreateTaggedImplVftbl() - { - return (IntPtr)Unsafe.AsPointer(in VtableImplementations.ITaggedImpl); - } - + 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 class VtableImplementations { 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 d381b04ff0da8f..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 @@ -164,7 +164,7 @@ internal static unsafe void WalkExternalTrackerObjects() nativeObjectWrapper.TrackerObject != IntPtr.Zero) { FindReferenceTargetsCallback.s_currentRootObjectHandle = nativeObjectWrapper.ProxyHandle; - if (IReferenceTracker.FindTrackerTargets(nativeObjectWrapper.TrackerObject, s_findReferencesTargetCallback) != HResults.S_OK) + if (IReferenceTracker.FindTrackerTargets(nativeObjectWrapper.TrackerObject, (IntPtr)Unsafe.AsPointer(in s_findReferencesTargetCallback)) != HResults.S_OK) { walkFailed = true; FindReferenceTargetsCallback.s_currentRootObjectHandle = default; 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 9b2f6adef04bc5..682a8ba691ae54 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 @@ -47,10 +47,6 @@ public struct ComInterfaceEntry 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); 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 index 80a52ca2e42377..a715c4e772d0a9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ReferenceTrackerHost.cs @@ -11,12 +11,12 @@ namespace System.Runtime.InteropServices internal static class ReferenceTrackerHost { [FixedAddressValueType] - internal static readonly unsafe IntPtr s_globalHostServices = (IntPtr)Unsafe.AsPointer(in HostServices.Vftbl); + private static readonly unsafe IntPtr s_globalHostServices = (IntPtr)Unsafe.AsPointer(in HostServices.Vftbl); // Called when an IReferenceTracker instance is found. - public static void SetReferenceTrackerHost(IntPtr trackerManager) + public static unsafe void SetReferenceTrackerHost(IntPtr trackerManager) { - IReferenceTrackerManager.SetReferenceTrackerHost(trackerManager, s_globalHostServices); + IReferenceTrackerManager.SetReferenceTrackerHost(trackerManager, (IntPtr)Unsafe.AsPointer(in s_globalHostServices)); } #pragma warning disable IDE0060 From 8816f51887435e874b8176e0f17a6385f14a47d6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 May 2025 15:43:03 -0700 Subject: [PATCH 26/38] Reimplement ComWrappers diagnostics on top of the ConditionalWeakTables on the ComWrappers class instead of using the syncblock --- .../InteropServices/ComWrappers.CoreCLR.cs | 29 ---- .../classlibnative/bcltype/objectnative.cpp | 19 +-- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 158 ++++++++++-------- src/coreclr/interop/comwrappers.cpp | 78 +-------- src/coreclr/interop/inc/interoplibabi.h | 46 ++++- src/coreclr/vm/CMakeLists.txt | 2 + src/coreclr/vm/binder.cpp | 3 +- src/coreclr/vm/conditionalweaktable.cpp | 38 +++++ src/coreclr/vm/conditionalweaktable.h | 76 +++++++++ src/coreclr/vm/corelib.cpp | 1 + src/coreclr/vm/corelib.h | 20 +++ .../vm/interoplibinterface_comwrappers.cpp | 55 ------ .../vm/interoplibinterface_comwrappers.h | 12 +- src/coreclr/vm/interoputil.cpp | 13 +- src/coreclr/vm/object.h | 1 + src/coreclr/vm/object.inl | 25 +++ src/coreclr/vm/qcallentrypoints.cpp | 2 - src/coreclr/vm/syncblk.cpp | 4 - src/coreclr/vm/syncblk.h | 76 --------- .../CompilerServices/ConditionalWeakTable.cs | 2 + .../Runtime/InteropServices/ComWrappers.cs | 33 +++- 22 files changed, 323 insertions(+), 371 deletions(-) create mode 100644 src/coreclr/vm/conditionalweaktable.cpp create mode 100644 src/coreclr/vm/conditionalweaktable.h 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 index d02abd8191758e..5f89431729f0aa 100644 --- 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 @@ -45,35 +45,6 @@ private static unsafe IntPtr CreateTaggedImplVftbl() return (IntPtr)vftbl; } - private static readonly ConditionalWeakTable> s_allManagedObjectWrapperTable = []; - - private static unsafe void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper) - { - // 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); - } - RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref instance), wrapper.Wrapper); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterManagedObjectWrapperForDiagnostics")] - private static unsafe partial void RegisterManagedObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack instance, ManagedObjectWrapper* wrapper); - - private static void RegisterNativeObjectWrapperForDiagnostics(NativeObjectWrapper registeredWrapper) - { - object target = registeredWrapper.ProxyHandle.Target!; - RegisterNativeObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack.Create(ref target), ObjectHandleOnStack.Create(ref registeredWrapper)); - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_RegisterNativeObjectWrapperForDiagnostics")] - private static unsafe partial void RegisterNativeObjectWrapperForDiagnosticsInternal(ObjectHandleOnStack target, ObjectHandleOnStack wrapper); - internal static int CallICustomQueryInterface(ManagedObjectWrapperHolder holder, ref Guid iid, out IntPtr ppObject) { if (holder.WrappedObject is ICustomQueryInterface customQueryInterface) 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 c8947048ec92ef..7c89896c5449af 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 ad08670a0bd30f..cddf5baf70ba45 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 @@ -24,6 +25,7 @@ #include typedef DPTR(InteropLib::ABI::ManagedObjectWrapperLayout) PTR_ManagedObjectWrapper; +typedef DPTR(InteropLib::ABI::ComInterfaceEntry) PTR_ComInterfaceEntry; #endif // FEATURE_COMWRAPPERS #ifndef TARGET_UNIX @@ -4436,6 +4438,7 @@ ErrExit: return hr; HRESULT ClrDataAccess::DACTryGetComWrappersObjectFromCCW(CLRDATA_ADDRESS ccwPtr, OBJECTREF* objRef) { HRESULT hr = E_FAIL; + MOWHOLDERREF holder = NULL; if (ccwPtr == 0 || objRef == NULL) { @@ -4450,7 +4453,9 @@ HRESULT ClrDataAccess::DACTryGetComWrappersObjectFromCCW(CLRDATA_ADDRESS ccwPtr, goto ErrExit; } - *objRef = ObjectFromHandle(handle); + holder = (MOWHOLDERREF)ObjectFromHandle(handle); + + *objRef = holder->_wrappedObject; return S_OK; @@ -5051,6 +5056,33 @@ HRESULT ClrDataAccess::GetBreakingChangeVersion(int* pVersion) return S_OK; } +TADDR ClrDataAccess::GetIdentityForManagedObjectWrapper(TADDR mow) +{ + PTR_ManagedObjectWrapper pMOW = dac_cast(mow); + // Replicate the logic for ManagedObjectWrapper.As(IID_IUnknown) + if ((pMOW->_flags & 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 (TADDR)InteropLib::ABI::IndexIntoDispatchSection(pMOW->_userDefinedCount, pMOW->_dispatches); + } + + // We need to find the IUnknown interface pointer in the MOW. + for (int32_t i = 0; i < pMOW->_userDefinedCount; i++) + { + // _userDefined is a pointer into the target address space. + PTR_ComInterfaceEntry pEntry = dac_cast((TADDR)(pMOW->_userDefined + i)); + if (pEntry->IID == IID_IUnknown) + { + // We found the IUnknown interface pointer. + // The index returned from IndexIntoDispatchSection is in the target address space. + return (TADDR)InteropLib::ABI::IndexIntoDispatchSection(i, pMOW->_dispatches); + } + } + + return (TADDR)NULL; +} + HRESULT ClrDataAccess::GetObjectComWrappersData(CLRDATA_ADDRESS objAddr, CLRDATA_ADDRESS *rcw, unsigned int count, CLRDATA_ADDRESS *mowList, unsigned int *pNeeded) { #ifdef FEATURE_COMWRAPPERS @@ -5065,6 +5097,10 @@ HRESULT ClrDataAccess::GetObjectComWrappersData(CLRDATA_ADDRESS objAddr, CLRDATA } SOSDacEnter(); + + // Default to having found no information. + HRESULT hr = S_FALSE; + if (pNeeded != NULL) { *pNeeded = 0; @@ -5075,59 +5111,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_managedObjectComWrapperSet)); - DPTR(ManagedObjectComWrapperSet *)ppMap(PTR_TO_MEMBER_TADDR(NewHolder, mapHolder, m_value)); - DPTR(ManagedObjectComWrapperSet) 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) { - ManagedObjectComWrapperSet::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)); - ++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 @@ -5210,40 +5250,13 @@ namespace { BOOL IsComWrappersRCW(CLRDATA_ADDRESS rcw) { - NewArrayHolder region; - unsigned int count = 0; - - { - DacHandleTableMemoryEnumerator handleEnumerator; - handleEnumerator.Init(); - if (!handleEnumerator.GetCount(&count) || count == 0) - { - return FALSE; - } - region = new(nothrow) SOSMemoryRegion[count]; - if (handleEnumerator.Next(count, region, &count) != S_OK) - { - return FALSE; - } - } - - BOOL isGCHandle = FALSE; - for (unsigned int i = 0; i < count; i++) - { - if (region[i].Start <= rcw && rcw < region[i].Start + region[i].Size) - { - isGCHandle = TRUE; - break; - } - } - - if (!isGCHandle) + if ((rcw & 1) == 0) { + // We use the low bit of the RCW address to indicate that it is a ComWrappers RCW. return FALSE; } - OBJECTHANDLE nativeObjectWrapperHandle = (OBJECTHANDLE)TO_TADDR(rcw); - OBJECTREF nativeObjectWrapper = ObjectFromHandle(nativeObjectWrapperHandle); + OBJECTREF nativeObjectWrapper = OBJECTREF(TO_TADDR(rcw & ~1)); if (nativeObjectWrapper == NULL) { return FALSE; @@ -5259,14 +5272,13 @@ namespace TADDR GetComWrappersRCWIdentity(CLRDATA_ADDRESS rcw) { - OBJECTHANDLE nativeObjectWrapperHandle = (OBJECTHANDLE)TO_TADDR(rcw); - OBJECTREF nativeObjectWrapper = ObjectFromHandle(nativeObjectWrapperHandle); - if (nativeObjectWrapper == NULL) + if ((rcw & 1) == 0) { - return (TADDR)NULL; + // We use the low bit of the RCW address to indicate that it is a ComWrappers RCW. + return NULL; } - NATIVEOBJECTWRAPPERREF pNativeObjectWrapper = NATIVEOBJECTWRAPPERREF(nativeObjectWrapper); + NATIVEOBJECTWRAPPERREF pNativeObjectWrapper = NATIVEOBJECTWRAPPERREF(TO_TADDR(rcw & ~1)); return pNativeObjectWrapper->GetExternalComObject(); } } diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index ccbffbe1956416..05fb629cf2282f 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -13,24 +13,6 @@ using OBJECTHANDLE = InteropLib::OBJECTHANDLE; using TryInvokeICustomQueryInterfaceResult = InteropLibImports::TryInvokeICustomQueryInterfaceResult; -namespace InteropLib -{ - namespace ABI - { - struct ComInterfaceDispatch - { - const void* vtable; - }; - ABI_ASSERT(sizeof(ComInterfaceDispatch) == sizeof(void*)); - - struct ComInterfaceEntry - { - GUID IID; - const void* Vtable; - }; - } -} - namespace ABI { //--------------------------------------------------------------------------------- @@ -65,65 +47,7 @@ namespace ABI 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; - } - - // 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) diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index 18e699d80da268..a3daee9d59b6fa 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -13,21 +13,47 @@ namespace InteropLib { // Updating this also requires updating ComInterfaceDispatch::GetInstance. #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); - struct ComInterfaceDispatch; + static_assert(sizeof(void*) < DispatchAlignmentThisPtr); - struct ComInterfaceEntry; + 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 { +#ifdef DACCESS_COMPILE + friend class ClrDataAccess; +#endif // DACCESS_COMPILE public: LONGLONG GetRawRefCount() const { @@ -41,8 +67,16 @@ namespace InteropLib Volatile _flags; int32_t _userDefinedCount; ComInterfaceEntry* _userDefined; - ComInterfaceDispatch* _dispatches; + 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/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 436fc7ae5db107..3930504e2c8843 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 4947f1d7771db1..97ad0838fe6524 100644 --- a/src/coreclr/vm/binder.cpp +++ b/src/coreclr/vm/binder.cpp @@ -21,6 +21,7 @@ #include "sigbuilder.h" #include "olevariant.h" #include "configuration.h" +#include "conditionalweaktable.h" #include "interoplibinterface_comwrappers.h" // @@ -560,7 +561,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/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 af2736edc2faeb..dafdc95ff5c090 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" diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index f42402be5cc7c6..e322e258afd9f9 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -436,6 +436,8 @@ DEFINE_CLASS(NATIVE_OBJECT_WRAPPER, Interop, ComWrappers+NativeObj 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) @@ -760,6 +762,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) @@ -1143,6 +1159,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/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index 7b59a47017cfef..84e03be530cb3e 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -65,20 +65,6 @@ extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl( InteropLib::Com::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease); } -void ComWrappersNative::DestroyExternalComObjectContext(_In_ void* contextRaw) -{ - CONTRACTL - { - NOTHROW; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(contextRaw != NULL); - } - CONTRACTL_END; - - DestroyLongWeakHandle((OBJECTHANDLE)contextRaw); -} - void ComWrappersNative::MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe) { CONTRACTL @@ -192,47 +178,6 @@ extern "C" void const* QCALLTYPE ComWrappers_GetTaggedImpl() return InteropLib::Com::GetTaggedCurrentVersionImpl(); } -extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( - _In_ QCall::ObjectHandleOnStack obj, - _In_ void* wrapper) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - - GCX_COOP(); - - // Check the object's SyncBlock for a managed object wrapper. - SyncBlock* syncBlock = obj.Get()->GetSyncBlock(); - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); - _ASSERTE(syncBlock->IsPrecious()); - - interopInfo->AddManagedObjectComWrapper(wrapper); - - END_QCALL; -} - -extern "C" void QCALLTYPE ComWrappers_RegisterNativeObjectWrapperForDiagnostics( - _In_ QCall::ObjectHandleOnStack target, - _In_ QCall::ObjectHandleOnStack wrapper) -{ - QCALL_CONTRACT; - - BEGIN_QCALL; - - GCX_COOP(); - - SyncBlock* syncBlock = target.Get()->GetSyncBlock(); - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); - _ASSERTE(syncBlock->IsPrecious()); - - OBJECTHANDLE wrapperHandle = GetAppDomain()->CreateLongWeakHandle(wrapper.Get()); - - interopInfo->TrySetExternalComObjectContext((PTR_VOID)wrapperHandle); - - END_QCALL; -} - extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager() { QCALL_CONTRACT_NO_GC_TRANSITION; diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h index 838f4c739634fb..1085775d594c24 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.h +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -13,9 +13,6 @@ // Native calls for the managed ComWrappers API class ComWrappersNative { -public: // Lifetime management for COM Wrappers - static void DestroyExternalComObjectContext(_In_ void* context); - public: // COM activation static void MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe); @@ -40,14 +37,6 @@ extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl(); extern "C" void const* QCALLTYPE ComWrappers_GetTaggedImpl(); -extern "C" void QCALLTYPE ComWrappers_RegisterManagedObjectWrapperForDiagnostics( - _In_ QCall::ObjectHandleOnStack obj, - _In_ void* wrapper); - -extern "C" void QCALLTYPE ComWrappers_RegisterNativeObjectWrapperForDiagnostics( - _In_ QCall::ObjectHandleOnStack target, - _In_ QCall::ObjectHandleOnStack wrapper); - extern "C" void QCALLTYPE ComWrappers_RegisterIsRootedCallback(); extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_HasReferenceTrackerManager(); @@ -79,6 +68,7 @@ class GCHandleSetEntryObject; class ManagedObjectWrapperHolderObject : public Object { friend class CoreLibBinder; + friend class ClrDataAccess; private: OBJECTREF _releaser; OBJECTREF _wrappedObject; diff --git a/src/coreclr/vm/interoputil.cpp b/src/coreclr/vm/interoputil.cpp index 7c98422980a686..0b61f9f04850e1 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 @@ -1239,18 +1239,9 @@ void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo) pCCW->Cleanup(); } #endif // FEATURE_COMINTEROP - -#ifdef FEATURE_COMWRAPPERS - 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 diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 1c4384e7771ef1..d2c3067d1be0c4 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 7202e72273fcab..a45e3d69a249d7 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -427,8 +427,6 @@ static const Entry s_QCall[] = DllImportEntry(ComWrappers_AllocateRefCountedHandle) DllImportEntry(ComWrappers_GetIReferenceTrackerTargetVftbl) DllImportEntry(ComWrappers_GetTaggedImpl) - DllImportEntry(ComWrappers_RegisterManagedObjectWrapperForDiagnostics) - DllImportEntry(ComWrappers_RegisterNativeObjectWrapperForDiagnostics) DllImportEntry(ComWrappers_RegisterIsRootedCallback) DllImportEntry(TrackerObjectManager_HasReferenceTrackerManager) DllImportEntry(TrackerObjectManager_TryRegisterReferenceTrackerManager) diff --git a/src/coreclr/vm/syncblk.cpp b/src/coreclr/vm/syncblk.cpp index c58d10947927a2..ac605dfb9bee14 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_managedObjectComWrapperSet; -#endif // FEATURE_COMWRAPPERS } #ifndef TARGET_UNIX diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 10809560f61b23..403428fe266f71 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -608,21 +608,7 @@ class ComClassFactory; struct RCW; class RCWHolder; typedef DPTR(class ComCallWrapper) PTR_ComCallWrapper; - -#include "shash.h" #endif // FEATURE_COMINTEROP -class ManagedObjectComWrapperTraits : public NoRemoveSHashTraits> -{ -public: - typedef LPVOID key_t; - static void *Null() { LIMITED_METHOD_CONTRACT; return NULL; } - static bool IsNull(void *e) { LIMITED_METHOD_CONTRACT; return (e == NULL); } - static const LPVOID GetKey(void *e) { LIMITED_METHOD_CONTRACT; return e; } - static count_t Hash(LPVOID key_t) { LIMITED_METHOD_CONTRACT; return (count_t)(size_t) key_t; } - static BOOL Equals(LPVOID lhs, LPVOID rhs) { LIMITED_METHOD_CONTRACT; return (lhs == rhs); } -}; - -using ManagedObjectComWrapperSet = SHash; class InteropSyncBlockInfo { friend class RCWHolder; @@ -643,22 +629,12 @@ class InteropSyncBlockInfo #endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION , m_pRCW{} #endif // FEATURE_COMINTEROP -#ifdef FEATURE_COMWRAPPERS - , m_externalComObjectContext{} - , m_managedObjectComWrapperLock{} - , m_managedObjectComWrapperSet{} -#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(); @@ -812,58 +788,6 @@ class InteropSyncBlockInfo #endif // FEATURE_COMINTEROP -#if defined(FEATURE_COMWRAPPERS) -public: -#ifndef DACCESS_COMPILE - bool AddManagedObjectComWrapper(_In_ void* mocw) - { - LIMITED_METHOD_CONTRACT; - - if (m_managedObjectComWrapperSet == NULL) - { - NewHolder map = new ManagedObjectComWrapperSet(); - if (InterlockedCompareExchangeT(&m_managedObjectComWrapperSet, (ManagedObjectComWrapperSet *)map, NULL) == NULL) - { - map.SuppressRelease(); - } - - _ASSERTE(m_managedObjectComWrapperSet != NULL); - } - - CrstHolder lock(&m_managedObjectComWrapperLock); - - m_managedObjectComWrapperSet->Add(mocw); - return true; - } -#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; - ManagedObjectComWrapperSet* m_managedObjectComWrapperSet; -#endif // FEATURE_COMWRAPPERS - #ifdef FEATURE_OBJCMARSHAL public: #ifndef DACCESS_COMPILE 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/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index 682a8ba691ae54..c236d092a860e7 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 @@ -58,9 +58,17 @@ public struct ComInterfaceEntry 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 ConditionalWeakTable s_nativeObjectWrapperTable = []; - private readonly ConditionalWeakTable _managedObjectWrapperTable = new ConditionalWeakTable(); + /// + /// Associates an object with all the s that were created for it. + /// + private static readonly ConditionalWeakTable> s_allManagedObjectWrapperTable = []; + + /// + /// Associates a managed object with the that was created for it by this instance. + /// + private readonly ConditionalWeakTable _managedObjectWrapperTable = []; private readonly RcwCache _rcwCache = new(); internal static bool TryGetComInstanceForIID(object obj, Guid iid, out IntPtr unknown, out ComWrappers? comWrappers) @@ -771,12 +779,25 @@ public unsafe IntPtr GetOrCreateComInterfaceForObject(object instance, CreateCom }, new { This = this, flags }); managedObjectWrapper.AddRef(); -#if CORECLR RegisterManagedObjectWrapperForDiagnostics(instance, managedObjectWrapper); -#endif + return managedObjectWrapper.ComIp; } + private static void RegisterManagedObjectWrapperForDiagnostics(object instance, ManagedObjectWrapperHolder wrapper) + { + // 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; @@ -1183,10 +1204,6 @@ private void RegisterWrapperForObject(NativeObjectWrapper wrapper, object comPro throw new NotSupportedException(); } -#if CORECLR - RegisterNativeObjectWrapperForDiagnostics(registeredWrapper); -#endif - // 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 From fb8bf7daed9f330bded6ee6cbab460e74a644d8d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 May 2025 15:57:01 -0700 Subject: [PATCH 27/38] PR feedback --- src/coreclr/interop/inc/interoplibabi.h | 6 +++++- src/coreclr/vm/interoplibinterface_comwrappers.cpp | 8 ++++---- src/coreclr/vm/interoplibinterface_comwrappers.h | 2 +- src/coreclr/vm/interoplibinterface_shared.cpp | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index a3daee9d59b6fa..d6cfa4d14038fc 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -11,7 +11,11 @@ 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 constexpr size_t DispatchAlignmentThisPtr = 64; // Should be a power of 2. #else diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.cpp b/src/coreclr/vm/interoplibinterface_comwrappers.cpp index 84e03be530cb3e..833e842bf264d9 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.cpp +++ b/src/coreclr/vm/interoplibinterface_comwrappers.cpp @@ -476,7 +476,7 @@ namespace InteropLibImports namespace { - MethodTable* pManagedObjectWrapperHolderMT = NULL; + MethodTable* s_pManagedObjectWrapperHolderMT = NULL; } bool ComWrappersNative::IsManagedObjectComWrapper(_In_ OBJECTREF managedObjectWrapperHolderRef, _Out_ bool* pIsRooted) @@ -489,7 +489,7 @@ bool ComWrappersNative::IsManagedObjectComWrapper(_In_ OBJECTREF managedObjectWr } CONTRACTL_END; - if (managedObjectWrapperHolderRef->GetGCSafeMethodTable() != pManagedObjectWrapperHolderMT) + if (managedObjectWrapperHolderRef->GetGCSafeMethodTable() != s_pManagedObjectWrapperHolderMT ) { return false; } @@ -556,7 +556,7 @@ void ComWrappersNative::OnFullGCFinished() STRESS_LOG0(LF_INTEROP, LL_INFO10000, "End Reference Tracking\n"); } -void ComWrappersNative::OnGCAfterMarkPhase() +void ComWrappersNative::OnAfterGCScanRoots() { CONTRACTL { @@ -588,7 +588,7 @@ extern "C" void QCALLTYPE ComWrappers_RegisterIsRootedCallback() BEGIN_QCALL; // Grab the method table for the objects we inspect in our ref-counted handle callback. - pManagedObjectWrapperHolderMT = CoreLibBinder::GetClass(CLASS__MANAGED_OBJECT_WRAPPER_HOLDER); + s_pManagedObjectWrapperHolderMT = CoreLibBinder::GetClass(CLASS__MANAGED_OBJECT_WRAPPER_HOLDER); END_QCALL; } diff --git a/src/coreclr/vm/interoplibinterface_comwrappers.h b/src/coreclr/vm/interoplibinterface_comwrappers.h index 1085775d594c24..4d534b33a247b5 100644 --- a/src/coreclr/vm/interoplibinterface_comwrappers.h +++ b/src/coreclr/vm/interoplibinterface_comwrappers.h @@ -22,7 +22,7 @@ class ComWrappersNative public: // GC interaction static void OnFullGCStarted(); static void OnFullGCFinished(); - static void OnGCAfterMarkPhase(); + static void OnAfterGCScanRoots(); }; // Native QCalls for the abstract ComWrappers managed type. diff --git a/src/coreclr/vm/interoplibinterface_shared.cpp b/src/coreclr/vm/interoplibinterface_shared.cpp index 0e2ae3cdea4638..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::OnGCAfterMarkPhase(); + ComWrappersNative::OnAfterGCScanRoots(); #endif // FEATURE_COMWRAPPERS #ifdef FEATURE_OBJCMARSHAL From ccac105678ce9dbc65715f5440ae4ed115283a9d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 May 2025 15:57:57 -0700 Subject: [PATCH 28/38] Add message for static-assert --- src/coreclr/interop/inc/interoplibabi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index d6cfa4d14038fc..77ce49b0d8619c 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -24,7 +24,7 @@ namespace InteropLib constexpr intptr_t DispatchThisPtrMask = ~(DispatchAlignmentThisPtr - 1); - static_assert(sizeof(void*) < DispatchAlignmentThisPtr); + static_assert(sizeof(void*) < DispatchAlignmentThisPtr, "DispatchAlignmentThisPtr must be larger than sizeof(void*)."); constexpr size_t EntriesPerThisPtr = (DispatchAlignmentThisPtr / sizeof(void*)) - 1; From 0437a737279d8fa74521874e40d36f0e6251a8a7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 2 May 2025 14:10:08 -0700 Subject: [PATCH 29/38] Refactor reading into a ManagedObjectWrapper to avoid friend classes --- src/coreclr/debug/daccess/request.cpp | 48 +++++++++++++++++++------ src/coreclr/interop/inc/interoplibabi.h | 3 -- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index ed346689569f92..05e6996eee9ab6 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -23,9 +23,6 @@ #ifdef FEATURE_COMWRAPPERS #include #include - -typedef DPTR(InteropLib::ABI::ManagedObjectWrapperLayout) PTR_ManagedObjectWrapper; -typedef DPTR(InteropLib::ABI::ComInterfaceEntry) PTR_ComInterfaceEntry; #endif // FEATURE_COMWRAPPERS #ifndef TARGET_UNIX @@ -5052,27 +5049,58 @@ HRESULT ClrDataAccess::GetBreakingChangeVersion(int* pVersion) return S_OK; } +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->_flags & InteropLib::Com::CreateComInterfaceFlagsEx::CallerDefinedIUnknown) == InteropLib::Com::CreateComInterfaceFlagsEx::None) + 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 (TADDR)InteropLib::ABI::IndexIntoDispatchSection(pMOW->_userDefinedCount, pMOW->_dispatches); + return pMOW->GetRuntimeDefinedIUnknown(); } // We need to find the IUnknown interface pointer in the MOW. - for (int32_t i = 0; i < pMOW->_userDefinedCount; i++) + int32_t userDefinedCount; + PTR_ComInterfaceEntry pUserDefined = pMOW->GetUserDefined(&userDefinedCount); + for (int32_t i = 0; i < userDefinedCount; i++) { - // _userDefined is a pointer into the target address space. - PTR_ComInterfaceEntry pEntry = dac_cast((TADDR)(pMOW->_userDefined + i)); - if (pEntry->IID == IID_IUnknown) + if (pUserDefined[i].IID == IID_IUnknown) { // We found the IUnknown interface pointer. // The index returned from IndexIntoDispatchSection is in the target address space. - return (TADDR)InteropLib::ABI::IndexIntoDispatchSection(i, pMOW->_dispatches); + return pMOW->IndexIntoDispatchSection(i); } } diff --git a/src/coreclr/interop/inc/interoplibabi.h b/src/coreclr/interop/inc/interoplibabi.h index 77ce49b0d8619c..217ecda8b73e28 100644 --- a/src/coreclr/interop/inc/interoplibabi.h +++ b/src/coreclr/interop/inc/interoplibabi.h @@ -55,9 +55,6 @@ namespace InteropLib // This is designed to codify the binary layout. struct ManagedObjectWrapperLayout { -#ifdef DACCESS_COMPILE - friend class ClrDataAccess; -#endif // DACCESS_COMPILE public: LONGLONG GetRawRefCount() const { From 36c2e9a0d66acd38300e797cfc6c247dc2c7884d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 2 May 2025 15:36:17 -0700 Subject: [PATCH 30/38] Wrap in ifdef for correctness --- src/coreclr/debug/daccess/request.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 05e6996eee9ab6..5bc5c4cbd6d2d0 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -5049,6 +5049,7 @@ HRESULT ClrDataAccess::GetBreakingChangeVersion(int* pVersion) return S_OK; } +#ifdef FEATURE_COMWRAPPERS namespace { typedef DPTR(InteropLib::ABI::ComInterfaceEntry) PTR_ComInterfaceEntry; @@ -5106,6 +5107,7 @@ TADDR ClrDataAccess::GetIdentityForManagedObjectWrapper(TADDR mow) return (TADDR)NULL; } +#endif // FEATURE_COMWRAPPERS HRESULT ClrDataAccess::GetObjectComWrappersData(CLRDATA_ADDRESS objAddr, CLRDATA_ADDRESS *rcw, unsigned int count, CLRDATA_ADDRESS *mowList, unsigned int *pNeeded) { From 7f080f7253f35a0c5a545da0b7f43f21834f448c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 6 May 2025 14:33:28 -0700 Subject: [PATCH 31/38] Add validation that the vtable layouts are preinitialized and guard the diagnostics-only table with the debugger support feature switch that already exists. --- .../InteropServices/ComWrappers.NativeAot.cs | 29 ++++++++++++++----- .../src/System/Diagnostics/Debugger.cs | 11 +++++++ .../Runtime/InteropServices/ComWrappers.cs | 7 +++++ .../Preinitialization/Preinitialization.cs | 11 +++++++ 4 files changed, 50 insertions(+), 8 deletions(-) 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 279d1e89a594fb..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 @@ -83,6 +83,14 @@ private static IntPtr GetTaggedImplCurrentVersion() internal static unsafe IntPtr TaggedImplVftblPtr => (IntPtr)Unsafe.AsPointer(in VtableImplementations.ITaggedImpl); internal static unsafe IntPtr DefaultIReferenceTrackerTargetVftblPtr => (IntPtr)Unsafe.AsPointer(in VtableImplementations.IReferenceTrackerTarget); + /// + /// 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 { public unsafe struct IUnknownVftbl @@ -94,7 +102,9 @@ public unsafe struct IUnknownVftbl public unsafe struct IReferenceTrackerTargetVftbl { - public IUnknownVftbl IUnknownVftbl; + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; public delegate* unmanaged AddRefFromReferenceTracker; public delegate* unmanaged ReleaseFromReferenceTracker; public delegate* unmanaged Peg; @@ -103,7 +113,9 @@ public unsafe struct IReferenceTrackerTargetVftbl public unsafe struct ITaggedImplVftbl { - public IUnknownVftbl IUnknownVftbl; + public delegate* unmanaged QueryInterface; + public delegate* unmanaged AddRef; + public delegate* unmanaged Release; public delegate* unmanaged IsCurrentVersion; } @@ -116,8 +128,6 @@ public unsafe struct ITaggedImplVftbl [FixedAddressValueType] public static readonly ITaggedImplVftbl ITaggedImpl; - - [UnmanagedCallersOnly] internal static unsafe int IReferenceTrackerTarget_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject) { @@ -169,17 +179,20 @@ static unsafe VtableImplementations() fpAddRef: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IUnknown))->AddRef, fpRelease: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IUnknown))->Release); - IReferenceTrackerTarget.IUnknownVftbl.QueryInterface = (delegate* unmanaged)&IReferenceTrackerTarget_QueryInterface; + IReferenceTrackerTarget.QueryInterface = (delegate* unmanaged)&IReferenceTrackerTarget_QueryInterface; GetIUnknownImpl( fpQueryInterface: out _, - fpAddRef: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget.IUnknownVftbl))->AddRef, - fpRelease: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget.IUnknownVftbl))->Release); + 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; - *(IUnknownVftbl*)Unsafe.AsPointer(ref ITaggedImpl) = IUnknown; + 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/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/InteropServices/ComWrappers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index c236d092a860e7..d2e08fda93d0e8 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 @@ -786,6 +786,13 @@ public unsafe IntPtr GetOrCreateComInterfaceForObject(object instance, CreateCom 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. 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", From 62c3141ac9e9754fab418e865923639f319d0054 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 7 May 2025 13:07:14 -0700 Subject: [PATCH 32/38] PR feedback and fix ifdefs for syncblk cleanup --- src/coreclr/vm/interoputil.cpp | 2 +- src/coreclr/vm/interoputil.h | 8 -------- src/coreclr/vm/syncblk.cpp | 2 +- .../src/System/Runtime/InteropServices/ComWrappers.cs | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/coreclr/vm/interoputil.cpp b/src/coreclr/vm/interoputil.cpp index ba9038c695502a..d2ef66159a2425 100644 --- a/src/coreclr/vm/interoputil.cpp +++ b/src/coreclr/vm/interoputil.cpp @@ -2480,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/syncblk.cpp b/src/coreclr/vm/syncblk.cpp index ac605dfb9bee14..ab2e2118d33461 100644 --- a/src/coreclr/vm/syncblk.cpp +++ b/src/coreclr/vm/syncblk.cpp @@ -710,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 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 d2e08fda93d0e8..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 @@ -1230,7 +1230,7 @@ private static void AddWrapperToReferenceTrackerHandleCache(NativeObjectWrapper private sealed class RcwCache { private readonly Lock _lock = new Lock(useTrivialWaits: true); - private readonly Dictionary _cache = []; + private readonly Dictionary _cache = []; /// /// Gets the current RCW proxy object for if it exists in the cache or inserts a new entry with . From 3d94abb9307be005b1cfeb84dbdc865672f5978a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 7 May 2025 16:20:56 -0700 Subject: [PATCH 33/38] Fix ifdef --- src/coreclr/vm/syncblk.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/syncblk.cpp b/src/coreclr/vm/syncblk.cpp index ab2e2118d33461..10d88882e71776 100644 --- a/src/coreclr/vm/syncblk.cpp +++ b/src/coreclr/vm/syncblk.cpp @@ -972,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) From 03c9120f6b07a8d0e0ef4da27f8e80fd48b3ad12 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 8 May 2025 16:03:11 -0700 Subject: [PATCH 34/38] Fix null return --- src/coreclr/debug/daccess/request.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 5bc5c4cbd6d2d0..99c157007ff20f 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -5301,7 +5301,7 @@ namespace if ((rcw & 1) == 0) { // We use the low bit of the RCW address to indicate that it is a ComWrappers RCW. - return NULL; + return (TADDR)NULL; } NATIVEOBJECTWRAPPERREF pNativeObjectWrapper = NATIVEOBJECTWRAPPERREF(TO_TADDR(rcw & ~1)); From 72fea93c9fba0659c6211d203f1a00a3faacec8b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 8 May 2025 17:02:19 -0700 Subject: [PATCH 35/38] Fix weak reference detection of comwrappers --- .../src/System/ComAwareWeakReference.CoreCLR.cs | 15 ++++++--------- src/coreclr/vm/ecalllist.h | 6 ------ src/coreclr/vm/weakreferencenative.cpp | 14 -------------- src/coreclr/vm/weakreferencenative.h | 10 ---------- 4 files changed, 6 insertions(+), 39 deletions(-) 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 915fa9dbda32e3..5d9070f0e8d73d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -29,6 +29,12 @@ internal sealed partial class ComAwareWeakReference [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe bool PossiblyComObject(object target) + { + return HasRealSyncBlock(target) || PossiblyComWrappersObject(target); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe bool HasRealSyncBlock(object target) { // see: syncblk.h const int IS_HASHCODE_BIT_NUMBER = 26; @@ -44,20 +50,11 @@ internal static unsafe bool PossiblyComObject(object target) } } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool HasInteropInfo(object target); - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjectToComWeakRef")] private static partial IntPtr ObjectToComWeakRef(ObjectHandleOnStack retRcw); internal static nint ObjectToComWeakRef(object target, out object? context) { - if (!HasInteropInfo(target)) - { - context = null; - return IntPtr.Zero; - } - #if FEATURE_COMINTEROP if (target is __ComObject) { diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 23ec9094d83f32..d10d215be8debd 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -370,12 +370,6 @@ FCFuncStart(gGCHandleFuncs) FCFuncElement("InternalCompareExchange", MarshalNative::GCHandleInternalCompareExchange) FCFuncEnd() -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) -FCFuncStart(gComAwareWeakReferenceFuncs) - FCFuncElement("HasInteropInfo", ComAwareWeakReferenceNative::HasInteropInfo) -FCFuncEnd() -#endif // defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) - // // // Class definitions diff --git a/src/coreclr/vm/weakreferencenative.cpp b/src/coreclr/vm/weakreferencenative.cpp index caac03309ed8ef..3f1724ef211b6b 100644 --- a/src/coreclr/vm/weakreferencenative.cpp +++ b/src/coreclr/vm/weakreferencenative.cpp @@ -132,17 +132,3 @@ extern "C" IWeakReference * QCALLTYPE ObjectToComWeakRef(QCall::ObjectHandleOnSt return pWeakReference; } #endif // FEATURE_COMINTEROP - -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) - -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 diff --git a/src/coreclr/vm/weakreferencenative.h b/src/coreclr/vm/weakreferencenative.h index d459b597a4dd62..df2ba1dc23e4dd 100644 --- a/src/coreclr/vm/weakreferencenative.h +++ b/src/coreclr/vm/weakreferencenative.h @@ -13,16 +13,6 @@ #include "weakreference.h" -#if defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) - -class ComAwareWeakReferenceNative -{ -public: - static FCDECL1(FC_BOOL_RET, HasInteropInfo, Object* pObject); -}; - -#endif // defined(FEATURE_COMINTEROP) || defined(FEATURE_COMWRAPPERS) - #ifdef FEATURE_COMINTEROP extern "C" void QCALLTYPE ComWeakRefToObject(IWeakReference * pComWeakReference, QCall::ObjectHandleOnStack retRcw); From 566b5c904cf657beb09c7a09e76bb4fe06e8496c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 9 May 2025 11:33:33 -0700 Subject: [PATCH 36/38] Don't check the sync-block, just do a type check --- .../System/ComAwareWeakReference.CoreCLR.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) 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 5d9070f0e8d73d..626274808353fb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -30,24 +30,7 @@ internal sealed partial class ComAwareWeakReference [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe bool PossiblyComObject(object target) { - return HasRealSyncBlock(target) || PossiblyComWrappersObject(target); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static unsafe bool HasRealSyncBlock(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; - } + return target is __ComObject || PossiblyComWrappersObject(target); } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjectToComWeakRef")] From 1dd8f713bf88f83ac8ff80d29337d36c086d1531 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 9 May 2025 13:30:26 -0700 Subject: [PATCH 37/38] Add ifdef --- .../src/System/ComAwareWeakReference.CoreCLR.cs | 5 +++++ 1 file changed, 5 insertions(+) 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 626274808353fb..de9908401cba8b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs @@ -30,7 +30,12 @@ internal sealed partial class ComAwareWeakReference [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe bool PossiblyComObject(object target) { +#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 } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjectToComWeakRef")] From 1f225f3928cc73dde5db0742f5f0ba138d4b7497 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 9 May 2025 14:24:40 -0700 Subject: [PATCH 38/38] Remove FCall class def --- src/coreclr/vm/ecalllist.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index d10d215be8debd..e0df408c04e4bb 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -383,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)