diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs index 101978434bd612..bf40d1b5e12a6d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs @@ -39,6 +39,12 @@ private static partial IntPtr CreateReferenceTrackingHandleInternal( out int memInSizeT, out IntPtr mem); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjCMarshal_GetOrCreateTaggedMemory")] + private static partial void GetOrCreateTaggedMemoryInternal( + ObjectHandleOnStack obj, + out int memInSizeT, + out IntPtr mem); + [UnmanagedCallersOnly] internal static unsafe void* InvokeUnhandledExceptionPropagation(Exception* pExceptionArg, IntPtr methodDesc, IntPtr* pContext, Exception* pException) { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs index 621c62d87db7d9..4a194b76175727 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs @@ -125,6 +125,16 @@ private static IntPtr CreateReferenceTrackingHandleInternal( object obj, out int memInSizeT, out IntPtr mem) + { + // Rely on GetOrCreateTaggedMemoryInternal for state checking. + GetOrCreateTaggedMemoryInternal(obj, out memInSizeT, out mem); + return RuntimeImports.RhHandleAllocRefCounted(obj); + } + + private static void GetOrCreateTaggedMemoryInternal( + object obj, + out int memInSizeT, + out IntPtr mem) { if (!s_initialized) { @@ -139,7 +149,6 @@ private static IntPtr CreateReferenceTrackingHandleInternal( var trackerInfo = s_objects.GetOrAdd(obj, static o => new ObjcTrackingInformation()); trackerInfo.EnsureInitialized(obj); trackerInfo.GetTaggedMemory(out memInSizeT, out mem); - return RuntimeImports.RhHandleAllocRefCounted(obj); } internal class ObjcTrackingInformation diff --git a/src/coreclr/vm/interoplibinterface.h b/src/coreclr/vm/interoplibinterface.h index e7d8ea3e209fbb..41fba14b64b4d2 100644 --- a/src/coreclr/vm/interoplibinterface.h +++ b/src/coreclr/vm/interoplibinterface.h @@ -63,6 +63,11 @@ extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle( _Out_ int* memInSizeT, _Outptr_ void** mem); +extern "C" void QCALLTYPE ObjCMarshal_GetOrCreateTaggedMemory( + _In_ QCall::ObjectHandleOnStack obj, + _Out_ int* memInSizeT, + _Outptr_ void** mem); + extern "C" BOOL QCALLTYPE ObjCMarshal_TrySetGlobalMessageSendCallback( _In_ ObjCMarshalNative::MessageSendFunction msgSendFunction, _In_ void* fptr); diff --git a/src/coreclr/vm/interoplibinterface_objc.cpp b/src/coreclr/vm/interoplibinterface_objc.cpp index 9fb23c76451ad1..f322990ac4b349 100644 --- a/src/coreclr/vm/interoplibinterface_objc.cpp +++ b/src/coreclr/vm/interoplibinterface_objc.cpp @@ -57,6 +57,62 @@ extern "C" BOOL QCALLTYPE ObjCMarshal_TryInitializeReferenceTracker( return success; } +namespace +{ + void* TaggedMemoryForObjectHelper( + _In_ QCall::ObjectHandleOnStack obj, + _Out_ size_t* memInSizeT, + _Out_opt_ OBJECTHANDLE* instHandle) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(CheckPointer(memInSizeT)); + } + CONTRACTL_END; + + // The reference tracking system must be initialized. + if (!g_ReferenceTrackerInitialized) + COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCMarshalNotInitialized")); + + void* taggedMemoryLocal; + + // Switch to Cooperative mode since object references + // are being manipulated. + { + GCX_COOP(); + + struct + { + OBJECTREF objRef; + } gc; + gc.objRef = NULL; + GCPROTECT_BEGIN(gc); + + gc.objRef = obj.Get(); + + // The object's type must be marked appropriately and with a finalizer. + if (!gc.objRef->GetMethodTable()->IsTrackedReferenceWithFinalizer()) + COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCTypeNoFinalizer")); + + // Initialize the syncblock for this instance. + SyncBlock* syncBlock = gc.objRef->GetSyncBlock(); + InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); + taggedMemoryLocal = interopInfo->AllocTaggedMemory(memInSizeT); + _ASSERTE(taggedMemoryLocal != NULL); + + if (instHandle != NULL) + *instHandle = GetAppDomain()->CreateTypedHandle(gc.objRef, HNDTYPE_REFCOUNTED); + + GCPROTECT_END(); + } + + return taggedMemoryLocal; + } +} + extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle( _In_ QCall::ObjectHandleOnStack obj, _Out_ int* memInSizeT, @@ -72,44 +128,33 @@ extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle( BEGIN_QCALL; - // The reference tracking system must be initialized. - if (!g_ReferenceTrackerInitialized) - COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCMarshalNotInitialized")); - - // Switch to Cooperative mode since object references - // are being manipulated. - { - GCX_COOP(); - - struct - { - OBJECTREF objRef; - } gc; - gc.objRef = NULL; - GCPROTECT_BEGIN(gc); - - gc.objRef = obj.Get(); + taggedMemoryLocal = TaggedMemoryForObjectHelper(obj, &memInSizeTLocal, &instHandle); + END_QCALL; - // The object's type must be marked appropriately and with a finalizer. - if (!gc.objRef->GetMethodTable()->IsTrackedReferenceWithFinalizer()) - COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCTypeNoFinalizer")); + *memInSizeT = (int)memInSizeTLocal; + *mem = taggedMemoryLocal; + return (void*)instHandle; +} - // Initialize the syncblock for this instance. - SyncBlock* syncBlock = gc.objRef->GetSyncBlock(); - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); - taggedMemoryLocal = interopInfo->AllocTaggedMemory(&memInSizeTLocal); - _ASSERTE(taggedMemoryLocal != NULL); +extern "C" void QCALLTYPE ObjCMarshal_GetOrCreateTaggedMemory( + _In_ QCall::ObjectHandleOnStack obj, + _Out_ int* memInSizeT, + _Outptr_ void** mem) +{ + QCALL_CONTRACT; + _ASSERTE(memInSizeT != NULL); + _ASSERTE(mem != NULL); - instHandle = GetAppDomain()->CreateTypedHandle(gc.objRef, HNDTYPE_REFCOUNTED); + size_t memInSizeTLocal; + void* taggedMemoryLocal; - GCPROTECT_END(); - } + BEGIN_QCALL; + taggedMemoryLocal = TaggedMemoryForObjectHelper(obj, &memInSizeTLocal, NULL); END_QCALL; *memInSizeT = (int)memInSizeTLocal; *mem = taggedMemoryLocal; - return (void*)instHandle; } namespace diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 1692140748aee8..a4f96915c06666 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -441,6 +441,7 @@ static const Entry s_QCall[] = DllImportEntry(ObjCMarshal_TrySetGlobalMessageSendCallback) DllImportEntry(ObjCMarshal_TryInitializeReferenceTracker) DllImportEntry(ObjCMarshal_CreateReferenceTrackingHandle) + DllImportEntry(ObjCMarshal_GetOrCreateTaggedMemory) #endif #if defined(FEATURE_JAVAMARSHAL) DllImportEntry(JavaMarshal_Initialize) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.PlatformNotSupported.cs index 133b2e7fe887eb..f9e46f3d291360 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.PlatformNotSupported.cs @@ -94,6 +94,9 @@ public static GCHandle CreateReferenceTrackingHandle( out Span taggedMemory) => throw new PlatformNotSupportedException(); + public static Span GetOrCreateTaggedMemory(object obj) + => throw new PlatformNotSupportedException(); + /// /// Objective-C msgSend function override options. /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs index 812de71191cc93..1d38df07e87f5f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs @@ -101,6 +101,8 @@ public static unsafe void Initialize( /// return a new handle each time but the same tagged memory will be returned. The /// tagged memory is only guaranteed to be zero initialized on the first call. /// + /// The tagged memory returned is the same as the memory returned from . + /// /// The caller is responsible for freeing the returned . /// public static GCHandle CreateReferenceTrackingHandle( @@ -126,6 +128,50 @@ public static GCHandle CreateReferenceTrackingHandle( return GCHandle.FromIntPtr(refCountHandle); } + /// + /// Request a pointer to memory tagged for the supplied object. + /// + /// The object whose tagged memory to return. + /// A pointer to memory tagged to the object. + /// Thrown if the ObjectiveCMarshal API has not been initialized. + /// + /// The function must be called prior to calling this function. + /// + /// The parameter must have a type in its hierarchy marked with + /// . + /// + /// The "Is Referenced" callback passed to + /// will be passed the memory returned from this function. + /// The memory it points at is defined by the length in the and + /// will be zeroed out. It will be available until is collected by the GC. + /// The returned memory can be used for any purpose by the caller of this function and usable + /// during the "Is Referenced" callback. + /// + /// Calling this function multiple times with the same will + /// return the same tagged memory. It is only guaranteed to be zero initialized on + /// the first call. + /// + /// The return value is the same as the tagged memory returned from . + /// + public static Span GetOrCreateTaggedMemory(object obj) + { + ArgumentNullException.ThrowIfNull(obj); + + GetOrCreateTaggedMemoryInternal( +#if NATIVEAOT + obj, +#else + ObjectHandleOnStack.Create(ref obj), +#endif + out int memInSizeT, + out IntPtr mem); + + unsafe + { + return new Span(mem.ToPointer(), memInSizeT); + } + } + /// /// Objective-C msgSend function override options. /// diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs index 07ef66f0e6a62d..c3645cc1c5bd7d 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -2448,6 +2448,7 @@ public static unsafe void Initialize( public static GCHandle CreateReferenceTrackingHandle( object obj, out System.Span taggedMemory) => throw null; + public static System.Span GetOrCreateTaggedMemory(object obj) => throw null; public enum MessageSendFunction { MsgSend, diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs index 0f4945b8e138c8..261f7b8ed4ad0f 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs @@ -33,5 +33,10 @@ private static IntPtr CreateReferenceTrackingHandleInternal( ObjectHandleOnStack obj, out int memInSizeT, out IntPtr mem) => throw new NotImplementedException(); + + private static void GetOrCreateTaggedMemoryInternal( + ObjectHandleOnStack obj, + out int memInSizeT, + out IntPtr mem) => throw new NotImplementedException(); } } diff --git a/src/tests/Interop/ObjectiveC/ObjectiveCMarshalAPI/Program.cs b/src/tests/Interop/ObjectiveC/ObjectiveCMarshalAPI/Program.cs index 55015c45cd16c9..9cee3bee913e45 100644 --- a/src/tests/Interop/ObjectiveC/ObjectiveCMarshalAPI/Program.cs +++ b/src/tests/Interop/ObjectiveC/ObjectiveCMarshalAPI/Program.cs @@ -39,9 +39,9 @@ public static extern unsafe void SetImports( public unsafe class Program { - static void Validate_ReferenceTrackingAPIs_InvalidArgs() + static void Validate_InvalidArgs() { - Console.WriteLine($"Running {nameof(Validate_ReferenceTrackingAPIs_InvalidArgs)}..."); + Console.WriteLine($"Running {nameof(Validate_InvalidArgs)}..."); delegate* unmanaged beginEndCallback; delegate* unmanaged isReferencedCallback; @@ -73,6 +73,11 @@ static void Validate_ReferenceTrackingAPIs_InvalidArgs() { ObjectiveCMarshal.CreateReferenceTrackingHandle(null , out _); }); + Assert.Throws( + () => + { + ObjectiveCMarshal.GetOrCreateTaggedMemory(null); + }); } // The expectation here is during reference tracking handle creation @@ -184,6 +189,25 @@ static void InitializeObjectiveCMarshal() ObjectiveCMarshal.Initialize(beginEndCallback, isReferencedCallback, trackedObjectEnteredFinalization, OnUnhandledExceptionPropagationHandler); } + static void Validate_PreInitialize_Scenario() + { + Console.WriteLine($"Running {nameof(Validate_PreInitialize_Scenario)}..."); + + // Attempting to create handle prior to initialization. + Assert.Throws( + () => + { + ObjectiveCMarshal.CreateReferenceTrackingHandle(new Base(), out _); + }); + + // Not initialized yet - should throw. + Assert.Throws( + () => + { + ObjectiveCMarshal.GetOrCreateTaggedMemory(new Base()); + }); + } + [MethodImpl(MethodImplOptions.NoInlining)] static GCHandle AllocAndTrackObject(uint count) where T : Base, new() { @@ -223,17 +247,6 @@ static unsafe void Validate_ReferenceTracking_Scenario() { Console.WriteLine($"Running {nameof(Validate_ReferenceTracking_Scenario)}..."); - var handles = new List(); - - // Attempting to create handle prior to initialization. - Assert.Throws( - () => - { - ObjectiveCMarshal.CreateReferenceTrackingHandle(new Base(), out _); - }); - - InitializeObjectiveCMarshal(); - // Type attributed but no finalizer. Assert.Throws( () => @@ -249,6 +262,8 @@ static unsafe void Validate_ReferenceTracking_Scenario() AllocUntrackedObject(); AllocUntrackedObject(); + var handles = new List(); + // Provide the minimum number of times the reference callback should run. // See IsRefCb() in NativeObjCMarshalTests.cpp for usage logic. const uint callbackCount = 3; @@ -426,6 +441,53 @@ static void _Validate_ExceptionPropagation() GC.KeepAlive(delThrowException); } + static unsafe void Validate_GetOrCreateTaggedMemory_Scenario() + { + Console.WriteLine($"Running {nameof(Validate_GetOrCreateTaggedMemory_Scenario)}..."); + + // Type attributed but no finalizer. + Assert.Throws( + () => + { + ObjectiveCMarshal.GetOrCreateTaggedMemory(new AttributedNoFinalizer()); + }); + + var obj = new Base(); + + // Validate length matches CreateReferenceTrackingHandle contract. + Span memFromGet = ObjectiveCMarshal.GetOrCreateTaggedMemory(obj); + Assert.Equal(2, memFromGet.Length); + + // Memory should be zero-initialized on first call. + Assert.Equal(IntPtr.Zero, memFromGet[0]); + Assert.Equal(IntPtr.Zero, memFromGet[1]); + + // Multiple calls return the same memory. + Span memFromGet2 = ObjectiveCMarshal.GetOrCreateTaggedMemory(obj); + fixed (void* p1 = memFromGet) + fixed (void* p2 = memFromGet2) + Assert.Equal((IntPtr)p1, (IntPtr)p2); + + // CreateReferenceTrackingHandle on the same object returns the same memory. + GCHandle h = ObjectiveCMarshal.CreateReferenceTrackingHandle(obj, out Span memFromCreate); + fixed (void* p1 = memFromGet) + fixed (void* p2 = memFromCreate) + Assert.Equal((IntPtr)p1, (IntPtr)p2); + h.Free(); + + // GetOrCreateTaggedMemory after CreateReferenceTrackingHandle also returns the same memory. + var obj2 = new Base(); + GCHandle h2 = ObjectiveCMarshal.CreateReferenceTrackingHandle(obj2, out Span memFromCreate2); + Span memFromGetAfter = ObjectiveCMarshal.GetOrCreateTaggedMemory(obj2); + fixed (void* p1 = memFromCreate2) + fixed (void* p2 = memFromGetAfter) + Assert.Equal((IntPtr)p1, (IntPtr)p2); + h2.Free(); + + GC.KeepAlive(obj); + GC.KeepAlive(obj2); + } + static void Validate_Initialize_FailsOnSecondAttempt() { Console.WriteLine($"Running {nameof(Validate_Initialize_FailsOnSecondAttempt)}..."); @@ -444,8 +506,14 @@ public static int TestEntryPoint() { try { - Validate_ReferenceTrackingAPIs_InvalidArgs(); + Validate_InvalidArgs(); + Validate_PreInitialize_Scenario(); + + InitializeObjectiveCMarshal(); + + Validate_GetOrCreateTaggedMemory_Scenario(); Validate_ReferenceTracking_Scenario(); + Validate_Initialize_FailsOnSecondAttempt(); } catch (Exception e)