Skip to content

Commit 2fa1dda

Browse files
AaronRobinsonMSFTCopilotCopilot
authored
Add ObjectiveCMarshal.GetOrCreateReferenceTrackingMemory API (#128508)
Fixes #128476 Adds `ObjectiveCMarshal.GetOrCreateReferenceTrackingMemory(object)` which returns the tagged memory span for an object without creating a `GCHandle`. This is a more efficient alternative to `CreateReferenceTrackingHandle` when only the tagged memory is needed and no reference tracking handle is required at that point in time. ## Changes - Added public API `ObjectiveCMarshal. GetOrCreateReferenceTrackingMemory(object)` with full XML documentation - Mono: stub throwing `NotImplementedException` - Ref assembly updated - Tests added to `ObjectiveCMarshalAPI` --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 123a5e9 commit 2fa1dda

11 files changed

Lines changed: 240 additions & 41 deletions

File tree

src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ private static partial IntPtr CreateReferenceTrackingHandleInternal(
3939
out int memInSizeT,
4040
out IntPtr mem);
4141

42+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjCMarshal_GetOrCreateReferenceTrackingMemory")]
43+
private static partial void GetOrCreateReferenceTrackingMemoryInternal(
44+
ObjectHandleOnStack obj,
45+
out int memInSizeT,
46+
out IntPtr mem);
47+
4248
[UnmanagedCallersOnly]
4349
internal static unsafe void* InvokeUnhandledExceptionPropagation(Exception* pExceptionArg, IntPtr methodDesc, IntPtr* pContext, Exception* pException)
4450
{

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ private static IntPtr CreateReferenceTrackingHandleInternal(
125125
object obj,
126126
out int memInSizeT,
127127
out IntPtr mem)
128+
{
129+
// Rely on GetOrCreateReferenceTrackingMemoryInternal for state checking.
130+
GetOrCreateReferenceTrackingMemoryInternal(obj, out memInSizeT, out mem);
131+
return RuntimeImports.RhHandleAllocRefCounted(obj);
132+
}
133+
134+
private static void GetOrCreateReferenceTrackingMemoryInternal(
135+
object obj,
136+
out int memInSizeT,
137+
out IntPtr mem)
128138
{
129139
if (!s_initialized)
130140
{
@@ -139,7 +149,6 @@ private static IntPtr CreateReferenceTrackingHandleInternal(
139149
var trackerInfo = s_objects.GetOrAdd(obj, static o => new ObjcTrackingInformation());
140150
trackerInfo.EnsureInitialized(obj);
141151
trackerInfo.GetTaggedMemory(out memInSizeT, out mem);
142-
return RuntimeImports.RhHandleAllocRefCounted(obj);
143152
}
144153

145154
internal class ObjcTrackingInformation

src/coreclr/vm/interoplibinterface.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle(
6363
_Out_ int* memInSizeT,
6464
_Outptr_ void** mem);
6565

66+
extern "C" void QCALLTYPE ObjCMarshal_GetOrCreateReferenceTrackingMemory(
67+
_In_ QCall::ObjectHandleOnStack obj,
68+
_Out_ int* memInSizeT,
69+
_Outptr_ void** mem);
70+
6671
extern "C" BOOL QCALLTYPE ObjCMarshal_TrySetGlobalMessageSendCallback(
6772
_In_ ObjCMarshalNative::MessageSendFunction msgSendFunction,
6873
_In_ void* fptr);

src/coreclr/vm/interoplibinterface_objc.cpp

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,39 @@ extern "C" BOOL QCALLTYPE ObjCMarshal_TryInitializeReferenceTracker(
5757
return success;
5858
}
5959

60+
namespace
61+
{
62+
void* GetTaggedMemoryForObject(
63+
_In_ QCall::ObjectHandleOnStack obj,
64+
_Out_ size_t* memInSizeT)
65+
{
66+
CONTRACTL
67+
{
68+
THROWS;
69+
GC_TRIGGERS;
70+
MODE_COOPERATIVE;
71+
PRECONDITION(CheckPointer(memInSizeT));
72+
}
73+
CONTRACTL_END;
74+
75+
// The reference tracking system must be initialized.
76+
if (!g_ReferenceTrackerInitialized)
77+
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCMarshalNotInitialized"));
78+
79+
// The object's type must be marked appropriately and with a finalizer.
80+
if (!obj.Get()->GetMethodTable()->IsTrackedReferenceWithFinalizer())
81+
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCTypeNoFinalizer"));
82+
83+
// Initialize the syncblock for this instance.
84+
SyncBlock* syncBlock = obj.Get()->GetSyncBlock();
85+
InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo();
86+
void* taggedMemoryLocal = interopInfo->EnsureTaggedMemoryAllocated(memInSizeT);
87+
_ASSERTE(taggedMemoryLocal != NULL);
88+
89+
return taggedMemoryLocal;
90+
}
91+
}
92+
6093
extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle(
6194
_In_ QCall::ObjectHandleOnStack obj,
6295
_Out_ int* memInSizeT,
@@ -72,44 +105,42 @@ extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle(
72105

73106
BEGIN_QCALL;
74107

75-
// The reference tracking system must be initialized.
76-
if (!g_ReferenceTrackerInitialized)
77-
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCMarshalNotInitialized"));
78-
79108
// Switch to Cooperative mode since object references
80109
// are being manipulated.
81-
{
82-
GCX_COOP();
110+
GCX_COOP();
111+
taggedMemoryLocal = GetTaggedMemoryForObject(obj, &memInSizeTLocal);
112+
instHandle = GetAppDomain()->CreateTypedHandle(obj.Get(), HNDTYPE_REFCOUNTED);
83113

84-
struct
85-
{
86-
OBJECTREF objRef;
87-
} gc;
88-
gc.objRef = NULL;
89-
GCPROTECT_BEGIN(gc);
114+
END_QCALL;
90115

91-
gc.objRef = obj.Get();
116+
*memInSizeT = (int)memInSizeTLocal;
117+
*mem = taggedMemoryLocal;
118+
return (void*)instHandle;
119+
}
92120

93-
// The object's type must be marked appropriately and with a finalizer.
94-
if (!gc.objRef->GetMethodTable()->IsTrackedReferenceWithFinalizer())
95-
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCTypeNoFinalizer"));
121+
extern "C" void QCALLTYPE ObjCMarshal_GetOrCreateReferenceTrackingMemory(
122+
_In_ QCall::ObjectHandleOnStack obj,
123+
_Out_ int* memInSizeT,
124+
_Outptr_ void** mem)
125+
{
126+
QCALL_CONTRACT;
127+
_ASSERTE(memInSizeT != NULL);
128+
_ASSERTE(mem != NULL);
96129

97-
// Initialize the syncblock for this instance.
98-
SyncBlock* syncBlock = gc.objRef->GetSyncBlock();
99-
InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo();
100-
taggedMemoryLocal = interopInfo->AllocTaggedMemory(&memInSizeTLocal);
101-
_ASSERTE(taggedMemoryLocal != NULL);
130+
size_t memInSizeTLocal;
131+
void* taggedMemoryLocal;
102132

103-
instHandle = GetAppDomain()->CreateTypedHandle(gc.objRef, HNDTYPE_REFCOUNTED);
133+
BEGIN_QCALL;
104134

105-
GCPROTECT_END();
106-
}
135+
// Switch to Cooperative mode since object references
136+
// are being manipulated.
137+
GCX_COOP();
138+
taggedMemoryLocal = GetTaggedMemoryForObject(obj, &memInSizeTLocal);
107139

108140
END_QCALL;
109141

110142
*memInSizeT = (int)memInSizeTLocal;
111143
*mem = taggedMemoryLocal;
112-
return (void*)instHandle;
113144
}
114145

115146
namespace

src/coreclr/vm/qcallentrypoints.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ static const Entry s_QCall[] =
441441
DllImportEntry(ObjCMarshal_TrySetGlobalMessageSendCallback)
442442
DllImportEntry(ObjCMarshal_TryInitializeReferenceTracker)
443443
DllImportEntry(ObjCMarshal_CreateReferenceTrackingHandle)
444+
DllImportEntry(ObjCMarshal_GetOrCreateReferenceTrackingMemory)
444445
#endif
445446
#if defined(FEATURE_JAVAMARSHAL)
446447
DllImportEntry(JavaMarshal_Initialize)

src/coreclr/vm/syncblk.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ class InteropSyncBlockInfo
331331
#ifdef FEATURE_OBJCMARSHAL
332332
public:
333333
#ifndef DACCESS_COMPILE
334-
PTR_VOID AllocTaggedMemory(_Out_ size_t* memoryInSizeT)
334+
PTR_VOID EnsureTaggedMemoryAllocated(_Out_ size_t* memoryInSizeT)
335335
{
336336
LIMITED_METHOD_CONTRACT;
337337
_ASSERTE(memoryInSizeT != NULL);

src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.PlatformNotSupported.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,34 @@ public static GCHandle CreateReferenceTrackingHandle(
9494
out Span<IntPtr> taggedMemory)
9595
=> throw new PlatformNotSupportedException();
9696

97+
/// <summary>
98+
/// Gets reference tracking memory for the supplied object.
99+
/// </summary>
100+
/// <param name="obj">The object whose tracking memory to return.</param>
101+
/// <returns>A span of tracking memory associated with <paramref name="obj"/>.</returns>
102+
/// <exception cref="InvalidOperationException">Thrown if the ObjectiveCMarshal API has not been initialized.</exception>
103+
/// <remarks>
104+
/// The Initialize() must be called prior to calling this function.
105+
///
106+
/// The <paramref name="obj"/> must have a type in its hierarchy marked with
107+
/// <see cref="ObjectiveCTrackedTypeAttribute"/>.
108+
///
109+
/// The "Is Referenced" callback passed to <see cref="Initialize" />
110+
/// will be passed the memory returned from this function.
111+
/// The memory it points at is defined by the length in the <see cref="Span{IntPtr}"/> and
112+
/// will be zeroed out. It will be available until <paramref name="obj"/> is collected by the GC.
113+
/// The returned memory can be used for any purpose by the caller of this function and is usable
114+
/// during the "Is Referenced" callback.
115+
///
116+
/// Calling this function multiple times with the same <paramref name="obj"/> will
117+
/// return the same tracking memory. It is only guaranteed to be zero initialized on
118+
/// the first call of this or <see cref="CreateReferenceTrackingHandle" />.
119+
///
120+
/// The return value is the same as the tracking memory returned from <see cref="CreateReferenceTrackingHandle" />.
121+
/// </remarks>
122+
public static Span<IntPtr> GetOrCreateReferenceTrackingMemory(object obj)
123+
=> throw new PlatformNotSupportedException();
124+
97125
/// <summary>
98126
/// Objective-C msgSend function override options.
99127
/// </summary>

src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ public static unsafe void Initialize(
101101
/// return a new handle each time but the same tagged memory will be returned. The
102102
/// tagged memory is only guaranteed to be zero initialized on the first call.
103103
///
104+
/// The tagged memory returned is the same as the memory returned from <see cref="GetOrCreateReferenceTrackingMemory" />.
105+
///
104106
/// The caller is responsible for freeing the returned <see cref="GCHandle"/>.
105107
/// </remarks>
106108
public static GCHandle CreateReferenceTrackingHandle(
@@ -126,6 +128,50 @@ public static GCHandle CreateReferenceTrackingHandle(
126128
return GCHandle.FromIntPtr(refCountHandle);
127129
}
128130

131+
/// <summary>
132+
/// Gets reference tracking memory for the supplied object.
133+
/// </summary>
134+
/// <param name="obj">The object whose tracking memory to return.</param>
135+
/// <returns>A span of tracking memory associated with <paramref name="obj"/>.</returns>
136+
/// <exception cref="InvalidOperationException">Thrown if the ObjectiveCMarshal API has not been initialized.</exception>
137+
/// <remarks>
138+
/// The Initialize() must be called prior to calling this function.
139+
///
140+
/// The <paramref name="obj"/> must have a type in its hierarchy marked with
141+
/// <see cref="ObjectiveCTrackedTypeAttribute"/>.
142+
///
143+
/// The "Is Referenced" callback passed to <see cref="Initialize" />
144+
/// will be passed the memory returned from this function.
145+
/// The memory it points at is defined by the length in the <see cref="Span{IntPtr}"/> and
146+
/// will be zeroed out. It will be available until <paramref name="obj"/> is collected by the GC.
147+
/// The returned memory can be used for any purpose by the caller of this function and usable
148+
/// during the "Is Referenced" callback.
149+
///
150+
/// Calling this function multiple times with the same <paramref name="obj"/> will
151+
/// return the same tracking memory. It is only guaranteed to be zero initialized on
152+
/// the first call of this or <see cref="CreateReferenceTrackingHandle" />.
153+
///
154+
/// The return value is the same as the tracking memory returned from <see cref="CreateReferenceTrackingHandle" />.
155+
/// </remarks>
156+
public static Span<IntPtr> GetOrCreateReferenceTrackingMemory(object obj)
157+
{
158+
ArgumentNullException.ThrowIfNull(obj);
159+
160+
GetOrCreateReferenceTrackingMemoryInternal(
161+
#if NATIVEAOT
162+
obj,
163+
#else
164+
ObjectHandleOnStack.Create(ref obj),
165+
#endif
166+
out int memInSizeT,
167+
out IntPtr mem);
168+
169+
unsafe
170+
{
171+
return new Span<IntPtr>(mem.ToPointer(), memInSizeT);
172+
}
173+
}
174+
129175
/// <summary>
130176
/// Objective-C msgSend function override options.
131177
/// </summary>

src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2448,6 +2448,7 @@ public static unsafe void Initialize(
24482448
public static GCHandle CreateReferenceTrackingHandle(
24492449
object obj,
24502450
out System.Span<System.IntPtr> taggedMemory) => throw null;
2451+
public static System.Span<System.IntPtr> GetOrCreateReferenceTrackingMemory(object obj) => throw null;
24512452
public enum MessageSendFunction
24522453
{
24532454
MsgSend,

src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,10 @@ private static IntPtr CreateReferenceTrackingHandleInternal(
3333
ObjectHandleOnStack obj,
3434
out int memInSizeT,
3535
out IntPtr mem) => throw new NotImplementedException();
36+
37+
private static void GetOrCreateReferenceTrackingMemoryInternal(
38+
ObjectHandleOnStack obj,
39+
out int memInSizeT,
40+
out IntPtr mem) => throw new NotImplementedException();
3641
}
3742
}

0 commit comments

Comments
 (0)