Skip to content

Move Untracked_Release to native code to avoid problems during shutdown #115489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ 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 unsafe void GetUntrackedIUnknownImpl(out delegate* unmanaged[MemberFunction]<IntPtr, uint> fpAddRef, out delegate* unmanaged[MemberFunction]<IntPtr, uint> fpRelease)
{
fpAddRef = fpRelease = GetUntrackedAddRefRelease();
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ComWrappers_GetUntrackedAddRefRelease")]
[SuppressGCTransition]
private static unsafe partial delegate* unmanaged[MemberFunction]<IntPtr, uint> GetUntrackedAddRefRelease();

internal static IntPtr DefaultIUnknownVftblPtr { get; } = CreateDefaultIUnknownVftbl();
internal static IntPtr TaggedImplVftblPtr { get; } = CreateTaggedImplVftbl();
internal static IntPtr DefaultIReferenceTrackerTargetVftblPtr { get; } = CreateDefaultIReferenceTrackerTargetVftbl();
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/nativeaot/Runtime/HandleTableHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,12 @@ EXTERN_C uint32_t __stdcall RhIUnknown_AddRef(void* pComThis)
ManagedObjectWrapper* wrapper = ToManagedObjectWrapper(pComThis);
return wrapper->AddRef();
}

//
// Release is implemented in native code so that it does not need to synchronize with the GC. This is important because Xaml
// can invoke this Release during shutdown, and we don't want to synchronize with the GC at that time.
//
EXTERN_C uint32_t __stdcall RhUntracked_AddRefRelease(void*)
{
return 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,26 @@ private static IntPtr AllocateRefCountedHandle(ManagedObjectWrapperHolder holder
/// <param name="fpRelease">Function pointer to Release.</param>
public static unsafe void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease)
{
fpQueryInterface = (IntPtr)(delegate* unmanaged<IntPtr, Guid*, IntPtr*, int>)&ComWrappers.IUnknown_QueryInterface;
fpQueryInterface = (IntPtr)(delegate* unmanaged[MemberFunction]<IntPtr, Guid*, IntPtr*, int>)&ComWrappers.IUnknown_QueryInterface;
fpAddRef = (IntPtr)(delegate*<IntPtr, uint>)&RuntimeImports.RhIUnknown_AddRef; // Implemented in C/C++ to avoid GC transitions
fpRelease = (IntPtr)(delegate* unmanaged<IntPtr, uint>)&ComWrappers.IUnknown_Release;
fpRelease = (IntPtr)(delegate* unmanaged[MemberFunction]<IntPtr, uint>)&ComWrappers.IUnknown_Release;
}

[UnmanagedCallersOnly]
internal static unsafe void GetUntrackedIUnknownImpl(out delegate* unmanaged[MemberFunction]<IntPtr, uint> fpAddRef, out delegate* unmanaged[MemberFunction]<IntPtr, uint> fpRelease)
{
// Implemented in C/C++ to avoid GC transitions during shutdown
fpAddRef = (delegate* unmanaged[MemberFunction]<IntPtr, uint>)(void*)(delegate*<IntPtr, uint>)&RuntimeImports.RhUntracked_AddRefRelease;
fpRelease = (delegate* unmanaged[MemberFunction]<IntPtr, uint>)(void*)(delegate*<IntPtr, uint>)&RuntimeImports.RhUntracked_AddRefRelease;
}

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
internal static unsafe int IUnknown_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject)
{
ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis);
return wrapper->QueryInterface(in *guid, out *ppObject);
}

[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
internal static unsafe uint IUnknown_Release(IntPtr pThis)
{
ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis);
Expand All @@ -75,7 +82,7 @@ private static IntPtr GetTaggedImplCurrentVersion()
{
unsafe
{
return (IntPtr)(delegate* unmanaged<IntPtr, IntPtr, int>)&VtableImplementations.ITaggedImpl_IsCurrentVersion;
return (IntPtr)(delegate* unmanaged[MemberFunction]<IntPtr, IntPtr, int>)&VtableImplementations.ITaggedImpl_IsCurrentVersion;
}
}

Expand All @@ -95,28 +102,28 @@ private static class VtableImplementations
{
public unsafe struct IUnknownVftbl
{
public delegate* unmanaged<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged<IntPtr, int> AddRef;
public delegate* unmanaged<IntPtr, uint> Release;
public delegate* unmanaged[MemberFunction]<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged[MemberFunction]<IntPtr, int> AddRef;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> Release;
}

public unsafe struct IReferenceTrackerTargetVftbl
{
public delegate* unmanaged<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged<IntPtr, int> AddRef;
public delegate* unmanaged<IntPtr, uint> Release;
public delegate* unmanaged<IntPtr, uint> AddRefFromReferenceTracker;
public delegate* unmanaged<IntPtr, uint> ReleaseFromReferenceTracker;
public delegate* unmanaged<IntPtr, uint> Peg;
public delegate* unmanaged<IntPtr, uint> Unpeg;
public delegate* unmanaged[MemberFunction]<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged[MemberFunction]<IntPtr, int> AddRef;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> Release;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> AddRefFromReferenceTracker;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> ReleaseFromReferenceTracker;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> Peg;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> Unpeg;
}

public unsafe struct ITaggedImplVftbl
{
public delegate* unmanaged<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged<IntPtr, int> AddRef;
public delegate* unmanaged<IntPtr, uint> Release;
public delegate* unmanaged<IntPtr, IntPtr, int> IsCurrentVersion;
public delegate* unmanaged[MemberFunction]<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged[MemberFunction]<IntPtr, int> AddRef;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> Release;
public delegate* unmanaged[MemberFunction]<IntPtr, IntPtr, int> IsCurrentVersion;
}

[FixedAddressValueType]
Expand All @@ -128,45 +135,45 @@ public unsafe struct ITaggedImplVftbl
[FixedAddressValueType]
public static readonly ITaggedImplVftbl ITaggedImpl;

[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
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]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
internal static unsafe uint IReferenceTrackerTarget_AddRefFromReferenceTracker(IntPtr pThis)
{
ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis);
return wrapper->AddRefFromReferenceTracker();
}

[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
internal static unsafe uint IReferenceTrackerTarget_ReleaseFromReferenceTracker(IntPtr pThis)
{
ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis);
return wrapper->ReleaseFromReferenceTracker();
}

[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
internal static unsafe uint IReferenceTrackerTarget_Peg(IntPtr pThis)
{
ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis);
return wrapper->Peg();
}

[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
internal static unsafe uint IReferenceTrackerTarget_Unpeg(IntPtr pThis)
{
ManagedObjectWrapper* wrapper = ComInterfaceDispatch.ToManagedObjectWrapper((ComInterfaceDispatch*)pThis);
return wrapper->Unpeg();
}

[UnmanagedCallersOnly]
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
internal static unsafe int ITaggedImpl_IsCurrentVersion(IntPtr pThis, IntPtr version)
{
return version == (IntPtr)(delegate* unmanaged<IntPtr, IntPtr, int>)&ITaggedImpl_IsCurrentVersion
return version == (IntPtr)(delegate* unmanaged[MemberFunction]<IntPtr, IntPtr, int>)&ITaggedImpl_IsCurrentVersion
? HResults.S_OK
: HResults.E_FAIL;
}
Expand All @@ -179,21 +186,21 @@ static unsafe VtableImplementations()
fpAddRef: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IUnknown))->AddRef,
fpRelease: out *(nint*)&((IUnknownVftbl*)Unsafe.AsPointer(ref IUnknown))->Release);

IReferenceTrackerTarget.QueryInterface = (delegate* unmanaged<IntPtr, Guid*, IntPtr*, int>)&IReferenceTrackerTarget_QueryInterface;
IReferenceTrackerTarget.QueryInterface = (delegate* unmanaged[MemberFunction]<IntPtr, Guid*, IntPtr*, int>)&IReferenceTrackerTarget_QueryInterface;
GetIUnknownImpl(
fpQueryInterface: out _,
fpAddRef: out *(nint*)&((IReferenceTrackerTargetVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget))->AddRef,
fpRelease: out *(nint*)&((IReferenceTrackerTargetVftbl*)Unsafe.AsPointer(ref IReferenceTrackerTarget))->Release);
IReferenceTrackerTarget.AddRefFromReferenceTracker = (delegate* unmanaged<IntPtr, uint>)&IReferenceTrackerTarget_AddRefFromReferenceTracker;
IReferenceTrackerTarget.ReleaseFromReferenceTracker = (delegate* unmanaged<IntPtr, uint>)&IReferenceTrackerTarget_ReleaseFromReferenceTracker;
IReferenceTrackerTarget.Peg = (delegate* unmanaged<IntPtr, uint>)&IReferenceTrackerTarget_Peg;
IReferenceTrackerTarget.Unpeg = (delegate* unmanaged<IntPtr, uint>)&IReferenceTrackerTarget_Unpeg;
IReferenceTrackerTarget.AddRefFromReferenceTracker = (delegate* unmanaged[MemberFunction]<IntPtr, uint>)&IReferenceTrackerTarget_AddRefFromReferenceTracker;
IReferenceTrackerTarget.ReleaseFromReferenceTracker = (delegate* unmanaged[MemberFunction]<IntPtr, uint>)&IReferenceTrackerTarget_ReleaseFromReferenceTracker;
IReferenceTrackerTarget.Peg = (delegate* unmanaged[MemberFunction]<IntPtr, uint>)&IReferenceTrackerTarget_Peg;
IReferenceTrackerTarget.Unpeg = (delegate* unmanaged[MemberFunction]<IntPtr, uint>)&IReferenceTrackerTarget_Unpeg;

GetIUnknownImpl(
fpQueryInterface: out *(nint*)&((ITaggedImplVftbl*)Unsafe.AsPointer(ref ITaggedImpl))->QueryInterface,
fpAddRef: out *(nint*)&((ITaggedImplVftbl*)Unsafe.AsPointer(ref ITaggedImpl))->AddRef,
fpRelease: out *(nint*)&((ITaggedImplVftbl*)Unsafe.AsPointer(ref ITaggedImpl))->Release);
ITaggedImpl.IsCurrentVersion = (delegate* unmanaged<IntPtr, IntPtr, int>)&ITaggedImpl_IsCurrentVersion;
ITaggedImpl.IsCurrentVersion = (delegate* unmanaged[MemberFunction]<IntPtr, IntPtr, int>)&ITaggedImpl_IsCurrentVersion;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ internal static unsafe class FindReferenceTargetsCallback
{
internal static GCHandle s_currentRootObjectHandle;

[UnmanagedCallersOnly]
#pragma warning disable CS3016
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
#pragma warning restore CS3016
private static unsafe int IFindReferenceTargetsCallback_QueryInterface(IntPtr pThis, Guid* guid, IntPtr* ppObject)
{
if (*guid == IID_IFindReferenceTargetsCallback || *guid == IID_IUnknown)
Expand All @@ -218,7 +220,9 @@ private static unsafe int IFindReferenceTargetsCallback_QueryInterface(IntPtr pT
}
}

[UnmanagedCallersOnly]
#pragma warning disable CS3016
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])]
#pragma warning restore CS3016
private static unsafe int IFindReferenceTargetsCallback_FoundTrackerTarget(IntPtr pThis, IntPtr referenceTrackerTarget)
{
if (referenceTrackerTarget == IntPtr.Zero)
Expand All @@ -235,22 +239,12 @@ private static unsafe int IFindReferenceTargetsCallback_FoundTrackerTarget(IntPt
return HResults.S_OK;
}

private static unsafe IntPtr CreateDefaultIFindReferenceTargetsCallbackVftbl()
{
IntPtr* vftbl = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(FindReferenceTargetsCallback), 4 * sizeof(IntPtr));
vftbl[0] = (IntPtr)(delegate* unmanaged<IntPtr, Guid*, IntPtr*, int>)&IFindReferenceTargetsCallback_QueryInterface;
vftbl[1] = (IntPtr)(delegate* unmanaged<IntPtr, uint>)&ComWrappers.Untracked_AddRef;
vftbl[2] = (IntPtr)(delegate* unmanaged<IntPtr, uint>)&ComWrappers.Untracked_Release;
vftbl[3] = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr, int>)&IFindReferenceTargetsCallback_FoundTrackerTarget;
return (IntPtr)vftbl;
}

internal struct ReferenceTargetsVftbl
{
public delegate* unmanaged<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged<IntPtr, uint> AddRef;
public delegate* unmanaged<IntPtr, uint> Release;
public delegate* unmanaged<IntPtr, IntPtr, int> FoundTrackerTarget;
public delegate* unmanaged[MemberFunction]<IntPtr, Guid*, IntPtr*, int> QueryInterface;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> AddRef;
public delegate* unmanaged[MemberFunction]<IntPtr, uint> Release;
public delegate* unmanaged[MemberFunction]<IntPtr, IntPtr, int> FoundTrackerTarget;
}

[FixedAddressValueType]
Expand All @@ -261,8 +255,7 @@ internal struct ReferenceTargetsVftbl
static FindReferenceTargetsCallback()
#pragma warning restore CA1810 // Initialize reference type static fields inline
{
Vftbl.AddRef = &Untracked_AddRef;
Vftbl.Release = &Untracked_Release;
ComWrappers.GetUntrackedIUnknownImpl(out Vftbl.AddRef, out Vftbl.Release);
Vftbl.QueryInterface = &IFindReferenceTargetsCallback_QueryInterface;
Vftbl.FoundTrackerTarget = &IFindReferenceTargetsCallback_FoundTrackerTarget;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,16 @@ internal enum GcRestrictedCalloutKind
)]
internal static extern uint RhIUnknown_AddRef(nint pThis);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary,
#if TARGET_WINDOWS && TARGET_X86
"_RhUntracked_AddRefRelease@4"
#else
"RhUntracked_AddRefRelease"
#endif
)]
internal static extern uint RhUntracked_AddRefRelease(nint pThis);

#if FEATURE_OBJCMARSHAL
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhRegisterObjectiveCMarshalBeginEndCallback")]
Expand Down
19 changes: 19 additions & 0 deletions src/coreclr/vm/interoplibinterface_comwrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -614,4 +614,23 @@ extern "C" CLR_BOOL QCALLTYPE TrackerObjectManager_IsGlobalPeggingEnabled()
return InteropLibImports::GetGlobalPeggingState();
}

// Some of our "Untracked" COM objects may be owned by static globals
// in client code. We need to ensure that we don't try executing a managed
// method for the first time when the process is shutting down.
// Therefore, we need to provide unmanaged implementations of AddRef and Release.
namespace
{
int STDMETHODCALLTYPE Untracked_AddRefRelease(void*)
{
return 1;
}
}

extern "C" void* QCALLTYPE ComWrappers_GetUntrackedAddRefRelease()
{
QCALL_CONTRACT_NO_GC_TRANSITION;

return (void*)Untracked_AddRefRelease;
}

#endif // FEATURE_COMWRAPPERS
2 changes: 2 additions & 0 deletions src/coreclr/vm/interoplibinterface_comwrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ extern "C" void QCALLTYPE ComWrappers_GetIUnknownImpl(
_Out_ void** fpAddRef,
_Out_ void** fpRelease);

extern "C" void* QCALLTYPE ComWrappers_GetUntrackedAddRefRelease();

extern "C" void* QCALLTYPE ComWrappers_AllocateRefCountedHandle(_In_ QCall::ObjectHandleOnStack obj);

extern "C" void const* QCALLTYPE ComWrappers_GetIReferenceTrackerTargetVftbl();
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ static const Entry s_QCall[] =
DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo)
#if defined(FEATURE_COMWRAPPERS)
DllImportEntry(ComWrappers_GetIUnknownImpl)
DllImportEntry(ComWrappers_GetUntrackedAddRefRelease)
DllImportEntry(ComWrappers_AllocateRefCountedHandle)
DllImportEntry(ComWrappers_GetIReferenceTrackerTargetVftbl)
DllImportEntry(ComWrappers_GetTaggedImpl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, Create
}

IntPtr currentVersion = GetTaggedImplCurrentVersion();
int hr = ((delegate* unmanaged<IntPtr, IntPtr, int>)(*(*(void***)implMaybe + 3 /* ITaggedImpl.IsCurrentVersion slot */)))(implMaybe, currentVersion);
int hr = ((delegate* unmanaged[MemberFunction]<IntPtr, IntPtr, int>)(*(*(void***)implMaybe + 3 /* ITaggedImpl.IsCurrentVersion slot */)))(implMaybe, currentVersion);
Marshal.Release(implMaybe);
if (hr != 0)
{
Expand Down Expand Up @@ -1445,26 +1445,13 @@ internal static IntPtr GetOrCreateTrackerTarget(IntPtr externalComObject)
return s_globalInstanceForTrackerSupport.GetOrCreateComInterfaceForObject(obj, CreateComInterfaceFlags.TrackerSupport);
}

// Lifetime maintained by stack - we don't care about ref counts
[UnmanagedCallersOnly]
internal static unsafe uint Untracked_AddRef(IntPtr _)
{
return 1;
}

[UnmanagedCallersOnly]
internal static unsafe uint Untracked_Release(IntPtr _)
{
return 1;
}

// Wrapper for IWeakReference
private static unsafe class IWeakReference
{
public static int Resolve(IntPtr pThis, Guid guid, out IntPtr inspectable)
{
fixed (IntPtr* inspectablePtr = &inspectable)
return (*(delegate* unmanaged<IntPtr, Guid*, IntPtr*, int>**)pThis)[3](pThis, &guid, inspectablePtr);
return (*(delegate* unmanaged[MemberFunction]<IntPtr, Guid*, IntPtr*, int>**)pThis)[3](pThis, &guid, inspectablePtr);
}
}

Expand All @@ -1474,7 +1461,7 @@ private static unsafe class IWeakReferenceSource
public static int GetWeakReference(IntPtr pThis, out IntPtr weakReference)
{
fixed (IntPtr* weakReferencePtr = &weakReference)
return (*(delegate* unmanaged<IntPtr, IntPtr*, int>**)pThis)[3](pThis, weakReferencePtr);
return (*(delegate* unmanaged[MemberFunction]<IntPtr, IntPtr*, int>**)pThis)[3](pThis, weakReferencePtr);
}
}

Expand Down
Loading
Loading