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 2fdf95932689b..b9a1643d054b2 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 @@ -974,6 +974,8 @@ + + @@ -1014,6 +1016,7 @@ + @@ -1038,6 +1041,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs new file mode 100644 index 0000000000000..7ef18f2e89812 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs @@ -0,0 +1,132 @@ +// 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.Runtime.InteropServices +{ + /// + /// Represents an strongly-typed opaque, GC handle to a managed object. + /// A GC handle is used when an object reference must be reachable from + /// unmanaged memory. + /// + /// + /// corresponds to Normal roots. + /// For Weak and WeakTrackResurrection, see . + /// For Pinned, see . + /// + /// + /// The type of the object this tracks to. + public struct GCHandle : IEquatable>, IDisposable + where T : class? + { + // The actual integer handle value that the EE uses internally. + private IntPtr _handle; + + /// + /// Allocates a handle for the specified object. + /// + /// The object that uses the . + public GCHandle(T target) + { + _handle = GCHandle.InternalAlloc(target, GCHandleType.Normal); + } + + private GCHandle(IntPtr handle) => _handle = handle; + + /// Determine whether this handle has been allocated or not. + public readonly bool IsAllocated => _handle != IntPtr.Zero; + + /// Gets or sets the object this handle represents. + public readonly T Target + { + get + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Skip the type check to provide lowest overhead. + return Unsafe.As(GCHandle.InternalGet(handle)); + } + set + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + GCHandle.InternalSet(handle, value); + } + } + + /// + /// Returns a new object created from a handle to a managed object. + /// + /// An handle to a managed object to create a object from. + /// A new object that corresponds to the value parameter. + /// + /// The representation of is not + /// interchangable with . + /// + public static GCHandle FromIntPtr(IntPtr value) => new GCHandle(value); + + /// + /// Returns the internal integer representation of a object. + /// + /// A object to retrieve an internal integer representation from. + /// An object that represents a object. + /// + /// The representation of is not + /// interchangable with . + /// + public static IntPtr ToIntPtr(GCHandle value) => value._handle; + + /// + /// Releases this . + /// + public void Dispose() + { + // Free the handle if it hasn't already been freed. + // Unlike GCHandle.Free, no thread safety is provided. + IntPtr handle = _handle; + if (handle != IntPtr.Zero) + { + _handle = IntPtr.Zero; + GCHandle.InternalFree(handle); + } + } + + /// Indicates whether the current instance is equal to another instance of the same type. + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is GCHandle handle && Equals(handle); + + /// Indicates whether the current instance is equal to another instance of the same type. + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + public readonly bool Equals(GCHandle other) => _handle == other._handle; + + /// + /// Returns an identifier for the current object. + /// + /// An identifier for the current object. + public override readonly int GetHashCode() => _handle.GetHashCode(); + + /// + /// Returns a value indicating whether two objects are equal. + /// + /// A object to compare with the parameter. + /// A object to compare with the parameter. + /// + /// if the and parameters are equal; otherwise, . + /// + public static bool operator ==(GCHandle left, GCHandle right) => left._handle == right._handle; + + /// + /// Returns a value indicating whether two objects are not equal. + /// + /// A object to compare with the parameter. + /// A object to compare with the parameter. + /// + /// if the and parameters are not equal; otherwise, . + /// + public static bool operator !=(GCHandle left, GCHandle right) => left._handle != right._handle; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs index 14151c1c02705..4f3178e1655b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs @@ -21,6 +21,7 @@ namespace System.Runtime.InteropServices /// WeakTrackResurrection: Same as Weak, but stays until after object is really gone. /// Pinned - same as Normal, but allows the address of the actual object to be taken. /// + /// [StructLayout(LayoutKind.Sequential)] public partial struct GCHandle : IEquatable { @@ -118,11 +119,18 @@ public readonly IntPtr AddrOfPinnedObject() } // Get the address. + unsafe + { + return (IntPtr)AddrOfPinnedObjectFromHandle(GetHandleValue(handle)); + } + } - object? target = InternalGet(GetHandleValue(handle)); + internal static unsafe void* AddrOfPinnedObjectFromHandle(IntPtr handle) + { + object? target = InternalGet(handle); if (target is null) { - return default; + return null; } unsafe @@ -132,14 +140,14 @@ public readonly IntPtr AddrOfPinnedObject() { if (target.GetType() == typeof(string)) { - return (IntPtr)Unsafe.AsPointer(ref Unsafe.As(target).GetRawStringData()); + return Unsafe.AsPointer(ref Unsafe.As(target).GetRawStringData()); } Debug.Assert(target is Array); - return (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(target))); + return Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(Unsafe.As(target))); } - return (IntPtr)Unsafe.AsPointer(ref target.GetRawData()); + return Unsafe.AsPointer(ref target.GetRawData()); } } @@ -183,7 +191,7 @@ public static GCHandle FromIntPtr(IntPtr value) private static bool IsPinned(IntPtr handle) => (handle & 1) != 0; // Check Pin flag [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ThrowIfInvalid(IntPtr handle) + internal static void ThrowIfInvalid(IntPtr handle) { // Check if the handle was never initialized or was freed. if (handle == 0) @@ -191,5 +199,24 @@ private static void ThrowIfInvalid(IntPtr handle) ThrowHelper.ThrowInvalidOperationException_HandleIsNotInitialized(); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void CheckUninitialized(IntPtr handle) + { + // Check if the handle was never initialized or was freed. + // Throws NRE with minimal overhead, to avoid access violation from managed code. + // Invalid handle is unsupported and will cause AV as expected. +#if MONO + // Mono doesn't handle reading null pointer as NRE. + // Throw a NRE manually. + if (handle == 0) + { + throw new NullReferenceException(); + } +#else + // The read will be combined with the read in InternalGet under Release. + _ = *(object*)handle; +#endif + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandleExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandleExtensions.cs new file mode 100644 index 0000000000000..f32b412433067 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandleExtensions.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Provides extension methods to operate with GC handles. + /// + public static class GCHandleExtensions + { + // The following methods are strongly typed generic specifications only on + // PinnedGCHandles with correct type. + + /// + /// Retrieves the address of string data in a of array. + /// + /// The address of the pinned array. + [CLSCompliant(false)] + public static unsafe T* GetAddressOfArrayData( +#nullable disable // Nullable oblivious because no covariance between PinnedGCHandle and PinnedGCHandle + this PinnedGCHandle handle) +#nullable restore + { + T[]? array = handle.Target; + if (array is null) + return null; + + // Unsafe.AsPointer call is safe since object is pinned. + return (T*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(array)); + } + + /// + /// Retrieves the address of string data in a of . + /// + /// The address of the pinned . + [CLSCompliant(false)] + public static unsafe char* GetAddressOfStringData( +#nullable disable // Nullable oblivious because no covariance between PinnedGCHandle and PinnedGCHandle + this PinnedGCHandle handle) +#nullable restore + { + string? str = handle.Target; + if (str is null) + return null; + + // Unsafe.AsPointer call is safe since object is pinned. + return (char*)Unsafe.AsPointer(ref str.GetRawStringData()); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs new file mode 100644 index 0000000000000..197447d976d4d --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs @@ -0,0 +1,148 @@ +// 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.Runtime.InteropServices +{ + /// + /// Represents an strongly-typed opaque, GC handle to a managed object. + /// A GC handle is used when an object reference must be reachable from + /// unmanaged memory. + /// The object is pinned at fixed location in GC heap and allows its + /// address to be taken. + /// + /// + /// corresponds to Pinned roots. + /// For Normal, see . + /// For Weak and WeakTrackResurrection, see . + /// + /// + /// The type of the object this tracks to. + public struct PinnedGCHandle : IEquatable>, IDisposable + where T : class? + { + // The actual integer handle value that the EE uses internally. + private IntPtr _handle; + + /// + /// Allocates a handle for the specified object. + /// + /// The object that uses the . + public PinnedGCHandle(T target) + { + // Unlike GCHandle, pinning any object is allowed + _handle = GCHandle.InternalAlloc(target, GCHandleType.Pinned); + } + + private PinnedGCHandle(IntPtr handle) => _handle = handle; + + /// Determine whether this handle has been allocated or not. + public readonly bool IsAllocated => _handle != IntPtr.Zero; + + /// Gets or sets the object this handle represents. + public readonly T Target + { + get + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Skip the type check to provide lowest overhead. + return Unsafe.As(GCHandle.InternalGet(handle)); + } + set + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Unlike GCHandle, pinning any object is allowed + GCHandle.InternalSet(handle, value); + } + } + + /// + /// Retrieves the address of object data in a . + /// + /// The address of the pinned data object. + [CLSCompliant(false)] + public readonly unsafe void* GetAddressOfObjectData() + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + return GCHandle.AddrOfPinnedObjectFromHandle(handle); + } + + /// + /// Returns a new object created from a handle to a managed object. + /// + /// An handle to a managed object to create a object from. + /// A new object that corresponds to the value parameter. + /// + /// The representation of is not + /// interchangable with . + /// + public static PinnedGCHandle FromIntPtr(IntPtr value) => new PinnedGCHandle(value); + + /// + /// Returns the internal integer representation of a object. + /// + /// A object to retrieve an internal integer representation from. + /// An object that represents a object. + /// + /// The representation of is not + /// interchangable with . + /// + public static IntPtr ToIntPtr(PinnedGCHandle value) => value._handle; + + /// + /// Releases this . + /// + public void Dispose() + { + // Free the handle if it hasn't already been freed. + // Unlike GCHandle.Free, no thread safety is provided. + IntPtr handle = _handle; + if (handle != IntPtr.Zero) + { + _handle = IntPtr.Zero; + GCHandle.InternalFree(handle); + } + } + + /// Indicates whether the current instance is equal to another instance of the same type. + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is PinnedGCHandle handle && Equals(handle); + + /// Indicates whether the current instance is equal to another instance of the same type. + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + public readonly bool Equals(PinnedGCHandle other) => _handle == other._handle; + + /// + /// Returns an identifier for the current object. + /// + /// An identifier for the current object. + public override readonly int GetHashCode() => _handle.GetHashCode(); + + /// + /// Returns a value indicating whether two objects are equal. + /// + /// A object to compare with the parameter. + /// A object to compare with the parameter. + /// + /// if the and parameters are equal; otherwise, . + /// + public static bool operator ==(PinnedGCHandle left, PinnedGCHandle right) => left._handle == right._handle; + + /// + /// Returns a value indicating whether two objects are not equal. + /// + /// A object to compare with the parameter. + /// A object to compare with the parameter. + /// + /// if the and parameters are not equal; otherwise, . + /// + public static bool operator !=(PinnedGCHandle left, PinnedGCHandle right) => left._handle != right._handle; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs new file mode 100644 index 0000000000000..3b741f6f44d4c --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs @@ -0,0 +1,140 @@ +// 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.Runtime.InteropServices +{ + /// + /// Represents an strongly-typed opaque, GC handle to a managed object. + /// A GC handle is used when an object reference must be reachable from + /// unmanaged memory. + /// The object is allowed to be collected and handle contents will be zeroed. + /// + /// + /// corresponds to Weak or WeakTrackResurrection roots. + /// For Normal, see . + /// For Pinned, see . + /// + /// + /// The type of the object this tracks to. + public struct WeakGCHandle : IEquatable>, IDisposable + where T : class? + { + // The actual integer handle value that the EE uses internally. + private IntPtr _handle; + + /// + /// Allocates a handle for the specified object. + /// + /// The object that uses the . + /// Whether track the object when it's resurrected in the finalizer. + public WeakGCHandle(T target, bool trackResurrection = false) + { + _handle = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak); + } + + private WeakGCHandle(IntPtr handle) => _handle = handle; + + /// Determine whether this handle has been allocated or not. + public readonly bool IsAllocated => _handle != IntPtr.Zero; + + /// + /// Tries to retrieve the target object that is referenced by the current object. + /// + /// When this method returns, contains the target object, if it is available. + /// if the target was retrieved; otherwise, . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryGetTarget([NotNullWhen(true)] out T? target) + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Skip the type check to provide lowest overhead. + T? obj = Unsafe.As(GCHandle.InternalGet(handle)); + target = obj; + return obj != null; + } + + /// Sets the object this handle represents. + public readonly void SetTarget(T target) + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + GCHandle.InternalSet(handle, target); + } + + /// + /// Returns a new object created from a handle to a managed object. + /// + /// An handle to a managed object to create a object from. + /// A new object that corresponds to the value parameter. + /// + /// The representation of is not + /// interchangable with . + /// + public static WeakGCHandle FromIntPtr(IntPtr value) => new WeakGCHandle(value); + + /// + /// Returns the internal integer representation of a object. + /// + /// A object to retrieve an internal integer representation from. + /// An object that represents a object. + /// + /// The representation of is not + /// interchangable with . + /// + public static IntPtr ToIntPtr(WeakGCHandle value) => value._handle; + + /// + /// Releases this . + /// + public void Dispose() + { + // Free the handle if it hasn't already been freed. + // Unlike GCHandle.Free, no thread safety is provided. + IntPtr handle = _handle; + if (handle != IntPtr.Zero) + { + _handle = IntPtr.Zero; + GCHandle.InternalFree(handle); + } + } + + /// Indicates whether the current instance is equal to another instance of the same type. + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is WeakGCHandle handle && Equals(handle); + + /// Indicates whether the current instance is equal to another instance of the same type. + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + public readonly bool Equals(WeakGCHandle other) => _handle == other._handle; + + /// + /// Returns an identifier for the current object. + /// + /// An identifier for the current object. + public override readonly int GetHashCode() => _handle.GetHashCode(); + + /// + /// Returns a value indicating whether two objects are equal. + /// + /// A object to compare with the parameter. + /// A object to compare with the parameter. + /// + /// if the and parameters are equal; otherwise, . + /// + public static bool operator ==(WeakGCHandle left, WeakGCHandle right) => left._handle == right._handle; + + /// + /// Returns a value indicating whether two objects are not equal. + /// + /// A object to compare with the parameter. + /// A object to compare with the parameter. + /// + /// if the and parameters are not equal; otherwise, . + /// + public static bool operator !=(WeakGCHandle left, WeakGCHandle right) => left._handle != right._handle; + } +} diff --git a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs index ace27b84d44e3..bd6650c461576 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs @@ -19,6 +19,36 @@ public void Ctor_Default() Assert.Equal(IntPtr.Zero, (IntPtr)handle); } + [Fact] + public void Ctor_Default_Generic() + { + var handle = new GCHandle(); + Assert.Throws(() => handle.Target); + Assert.False(handle.IsAllocated); + + Assert.Equal(IntPtr.Zero, GCHandle.ToIntPtr(handle)); + } + + [Fact] + public void Ctor_Default_Weak() + { + var handle = new WeakGCHandle(); + Assert.Throws(() => handle.TryGetTarget(out _)); + Assert.False(handle.IsAllocated); + + Assert.Equal(IntPtr.Zero, WeakGCHandle.ToIntPtr(handle)); + } + + [Fact] + public void Ctor_Default_Pinned() + { + var handle = new PinnedGCHandle(); + Assert.Throws(() => handle.Target); + Assert.False(handle.IsAllocated); + + Assert.Equal(IntPtr.Zero, PinnedGCHandle.ToIntPtr(handle)); + } + public static IEnumerable Alloc_TestData() { yield return new object[] { null }; @@ -38,6 +68,29 @@ public void Alloc_Value_ReturnsExpected(object value) ValidateGCHandle(handle, GCHandleType.Normal, value); } + [Fact] + public void Alloc_Value_ReturnsExpected_Genenic() + { + RunTest(null); + RunTest("String"); + RunTest(new int[1]); + RunTest(new object[1]); + RunTest(new NonBlittable[1]); + RunTest(new Blittable()); + RunTest(new NonBlittable()); + RunTest(new UnmanagedNonBlittable()); + RunTest(new ClassWithReferences()); + RunTest(new ClassWithoutReferences()); + + void RunTest(T value) where T : class + { + ValidateGCHandle(new GCHandle(value), value); + ValidateWeakGCHandle(new WeakGCHandle(value), value); + ValidateWeakGCHandle(new WeakGCHandle(value, trackResurrection: true), value); + ValidatePinnedGCHandle(new PinnedGCHandle(value), value); + } + } + public static IEnumerable Alloc_Type_TestData() { foreach (object[] data in Alloc_TestData()) @@ -98,10 +151,69 @@ public void FromIntPtr_Zero_ThrowsInvalidOperationException() } [Fact] - public void AddrOfPinnedObject_NotInitialized_ThrowsInvalidOperationException() + public void FromIntPtr_Generic_Zero_NoCheck() + { + var handle = GCHandle.FromIntPtr(IntPtr.Zero); + Assert.False(handle.IsAllocated); + var weakHandle = WeakGCHandle.FromIntPtr(IntPtr.Zero); + Assert.False(weakHandle.IsAllocated); + var pinnedHandle = PinnedGCHandle.FromIntPtr(IntPtr.Zero); + Assert.False(pinnedHandle.IsAllocated); + } + + [Fact] + public unsafe void AddrOfPinnedObject_NotInitialized_ThrowsException() { var handle = new GCHandle(); Assert.Throws(() => handle.AddrOfPinnedObject()); + var handleOfObject = new PinnedGCHandle(); + Assert.Throws(() => handleOfObject.GetAddressOfObjectData()); + var handleOfString = new PinnedGCHandle(); + Assert.Throws(() => handleOfString.GetAddressOfStringData()); + var handleOfArray = new PinnedGCHandle(); + Assert.Throws(() => handleOfArray.GetAddressOfArrayData()); + } + + [Fact] + public unsafe void AddrOfPinnedObject_ReturnsStringData() + { + string str = "String"; + fixed (char* ptr = str) + { + var handle = GCHandle.Alloc(str, GCHandleType.Pinned); + try + { + Assert.Equal((IntPtr)ptr, handle.AddrOfPinnedObject()); + using var handleOfString = new PinnedGCHandle(str); + Assert.Equal((IntPtr)ptr, (IntPtr)handleOfString.GetAddressOfObjectData()); + Assert.Equal((IntPtr)ptr, (IntPtr)handleOfString.GetAddressOfStringData()); + } + finally + { + handle.Free(); + } + } + } + + [Fact] + public unsafe void AddrOfPinnedObject_ReturnsArrayData() + { + int[] array = new int[1]; + fixed (int* ptr = array) + { + var handle = GCHandle.Alloc(array, GCHandleType.Pinned); + try + { + Assert.Equal((IntPtr)ptr, handle.AddrOfPinnedObject()); + using var handleOfArray = new PinnedGCHandle(array); + Assert.Equal((IntPtr)ptr, (IntPtr)handleOfArray.GetAddressOfObjectData()); + Assert.Equal((IntPtr)ptr, (IntPtr)handleOfArray.GetAddressOfArrayData()); + } + finally + { + handle.Free(); + } + } } [Fact] @@ -125,6 +237,17 @@ public void Free_NotInitialized_ThrowsInvalidOperationException() Assert.Throws(() => handle.Free()); } + [Fact] + public void Dispose_NotInitialized_NoThrow() + { + var handleOfObject = new GCHandle(); + handleOfObject.Dispose(); + var weakHandle = new WeakGCHandle(); + weakHandle.Dispose(); + var pinnedHandle = new PinnedGCHandle(); + pinnedHandle.Dispose(); + } + public static IEnumerable Equals_TestData() { GCHandle handle = GCHandle.Alloc(new object()); @@ -190,6 +313,73 @@ private static void ValidateGCHandle(GCHandle handle, GCHandleType type, object } } + private static void ValidateGCHandle(GCHandle handle, T target) + where T : class + { + try + { + Assert.Equal(target, handle.Target); + Assert.True(handle.IsAllocated); + + Assert.NotEqual(IntPtr.Zero, GCHandle.ToIntPtr(handle)); + } + finally + { + handle.Dispose(); + Assert.False(handle.IsAllocated); + } + } + + private static void ValidateWeakGCHandle(WeakGCHandle handle, T target) where T : class + { + try + { + if (target != null) + { + Assert.True(handle.TryGetTarget(out T outTarget)); + Assert.Equal(target, outTarget); + } + else + { + Assert.False(handle.TryGetTarget(out T outTarget)); + Assert.Null(outTarget); + } + Assert.True(handle.IsAllocated); + + Assert.NotEqual(IntPtr.Zero, WeakGCHandle.ToIntPtr(handle)); + } + finally + { + handle.Dispose(); + Assert.False(handle.IsAllocated); + } + } + + private static unsafe void ValidatePinnedGCHandle(PinnedGCHandle handle, T target) where T : class + { + try + { + Assert.Equal(target, handle.Target); + Assert.True(handle.IsAllocated); + + Assert.NotEqual(IntPtr.Zero, PinnedGCHandle.ToIntPtr(handle)); + + if (target == null) + { + Assert.Equal(IntPtr.Zero, (IntPtr)handle.GetAddressOfObjectData()); + } + else + { + Assert.NotEqual(IntPtr.Zero, (IntPtr)handle.GetAddressOfObjectData()); + } + } + finally + { + handle.Dispose(); + Assert.False(handle.IsAllocated); + } + } + public struct Blittable { public int _field; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index af640b0339a44..7f76529222441 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -14078,6 +14078,37 @@ public void Free() { } public static bool operator !=(System.Runtime.InteropServices.GCHandle a, System.Runtime.InteropServices.GCHandle b) { throw null; } public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.GCHandle value) { throw null; } } + public partial struct GCHandle : System.IDisposable, System.IEquatable> where T : class? + { + private int _dummyPrimitive; + public void Dispose() { } + public override readonly bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public readonly bool Equals(System.Runtime.InteropServices.GCHandle other) { throw null; } + public static System.Runtime.InteropServices.GCHandle FromIntPtr(System.IntPtr value) { throw null; } + public override readonly int GetHashCode() { throw null; } + public GCHandle(T target) { } + public readonly bool IsAllocated { get { throw null; } } + public static bool operator ==(System.Runtime.InteropServices.GCHandle left, System.Runtime.InteropServices.GCHandle right) { throw null; } + public static bool operator !=(System.Runtime.InteropServices.GCHandle left, System.Runtime.InteropServices.GCHandle right) { throw null; } + public readonly T Target { get { throw null; } set { } } + public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.GCHandle value) { throw null; } + } + public static class GCHandleExtensions + { + [System.CLSCompliantAttribute(false)] + public static unsafe T* GetAddressOfArrayData( +#nullable disable + this System.Runtime.InteropServices.PinnedGCHandle handle) +#nullable restore + { throw null; } + [System.CLSCompliantAttribute(false)] +#nullable disable + public static unsafe char* GetAddressOfStringData( +#nullable disable + this System.Runtime.InteropServices.PinnedGCHandle handle) +#nullable restore + { throw null; } + } public enum GCHandleType { Weak = 0, @@ -14147,6 +14178,23 @@ public sealed partial class OutAttribute : System.Attribute { public OutAttribute() { } } + public partial struct PinnedGCHandle : System.IDisposable, System.IEquatable> where T : class? + { + private int _dummyPrimitive; + public void Dispose() { } + public override readonly bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public readonly bool Equals(System.Runtime.InteropServices.PinnedGCHandle other) { throw null; } + public static System.Runtime.InteropServices.PinnedGCHandle FromIntPtr(System.IntPtr value) { throw null; } + [System.CLSCompliantAttribute(false)] + public readonly unsafe void* GetAddressOfObjectData() { throw null; } + public override readonly int GetHashCode() { throw null; } + public readonly bool IsAllocated { get { throw null; } } + public static bool operator ==(System.Runtime.InteropServices.PinnedGCHandle left, System.Runtime.InteropServices.PinnedGCHandle right) { throw null; } + public static bool operator !=(System.Runtime.InteropServices.PinnedGCHandle left, System.Runtime.InteropServices.PinnedGCHandle right) { throw null; } + public PinnedGCHandle(T target) { } + public T Target { readonly get { throw null; } set { } } + public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.PinnedGCHandle value) { throw null; } + } public static partial class RuntimeInformation { public static string FrameworkDescription { get { throw null; } } @@ -14270,6 +14318,22 @@ public enum UnmanagedType HString = 47, LPUTF8Str = 48, } + public partial struct WeakGCHandle : System.IDisposable, System.IEquatable> where T : class? + { + private int _dummyPrimitive; + public void Dispose() { } + public override readonly bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public readonly bool Equals(System.Runtime.InteropServices.WeakGCHandle other) { throw null; } + public static System.Runtime.InteropServices.WeakGCHandle FromIntPtr(System.IntPtr value) { throw null; } + public override readonly int GetHashCode() { throw null; } + public readonly bool IsAllocated { get { throw null; } } + public static bool operator ==(System.Runtime.InteropServices.WeakGCHandle left, System.Runtime.InteropServices.WeakGCHandle right) { throw null; } + public static bool operator !=(System.Runtime.InteropServices.WeakGCHandle left, System.Runtime.InteropServices.WeakGCHandle right) { throw null; } + public readonly void SetTarget(T target) { } + public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.WeakGCHandle value) { throw null; } + public readonly bool TryGetTarget([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out T? target) { throw null; } + public WeakGCHandle(T target, bool trackResurrection = false) { } + } } namespace System.Runtime.InteropServices.Marshalling {