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