// 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; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading; namespace System.Runtime.InteropServices { /// <summary> /// Represents an opaque, GC handle to a managed object. A GC handle is used when an /// object reference must be reachable from unmanaged memory. /// </summary> /// <remarks> /// There are 4 kinds of roots: /// Normal: Keeps the object from being collected. /// Weak: Allows object to be collected and handle contents will be zeroed. /// Weak references are zeroed before the finalizer runs, so if the /// object is resurrected in the finalizer the weak reference is still zeroed. /// 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. /// </remarks> /// <seealso cref="GCHandle{T}"/> /// <seealso cref="PinnedGCHandle{T}"/> /// <seealso cref="WeakGCHandle{T}"/> [StructLayout(LayoutKind.Sequential)] public partial struct GCHandle : IEquatable<GCHandle> { // The actual integer handle value that the EE uses internally. private IntPtr _handle; // Allocate a handle storing the object and the type. private GCHandle(object? value, GCHandleType type) { // Make sure the type parameter is within the valid range for the enum. if ((uint)type > (uint)GCHandleType.Pinned) // IMPORTANT: This must be kept in sync with the GCHandleType enum. { throw new ArgumentOutOfRangeException(nameof(type), SR.ArgumentOutOfRange_Enum); } if (type == GCHandleType.Pinned && !Marshal.IsPinnable(value)) { throw new ArgumentException(SR.ArgumentException_NotIsomorphic, nameof(value)); } IntPtr handle = InternalAlloc(value, type); if (type == GCHandleType.Pinned) { // Record if the handle is pinned. handle |= 1; } _handle = handle; } // Used in the conversion functions below. private GCHandle(IntPtr handle) => _handle = handle; /// <summary>Creates a new GC handle for an object.</summary> /// <param name="value">The object that the GC handle is created for.</param> /// <returns>A new GC handle that protects the object.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static GCHandle Alloc(object? value) => new GCHandle(value, GCHandleType.Normal); /// <summary>Creates a new GC handle for an object.</summary> /// <param name="value">The object that the GC handle is created for.</param> /// <param name="type">The type of GC handle to create.</param> /// <returns>A new GC handle that protects the object.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static GCHandle Alloc(object? value, GCHandleType type) => new GCHandle(value, type); /// <summary>Frees a GC handle.</summary> public void Free() { // Free the handle if it hasn't already been freed. IntPtr handle = Interlocked.Exchange(ref _handle, IntPtr.Zero); ThrowIfInvalid(handle); InternalFree(GetHandleValue(handle)); } // Target property - allows getting / updating of the handle's referent. public object? Target { readonly get { IntPtr handle = _handle; ThrowIfInvalid(handle); return InternalGet(GetHandleValue(handle)); } set { IntPtr handle = _handle; ThrowIfInvalid(handle); if (IsPinned(handle) && !Marshal.IsPinnable(value)) { throw new ArgumentException(SR.ArgumentException_NotIsomorphic, nameof(value)); } InternalSet(GetHandleValue(handle), value); } } /// <summary> /// Retrieve the address of an object in a Pinned handle. This throws /// an exception if the handle is any type other than Pinned. /// </summary> public readonly IntPtr AddrOfPinnedObject() { // Check if the handle was not a pinned handle. // You can only get the address of pinned handles. IntPtr handle = _handle; ThrowIfInvalid(handle); if (!IsPinned(handle)) { ThrowHelper.ThrowInvalidOperationException_HandleIsNotPinned(); } // Get the address. object? target = InternalGet(GetHandleValue(handle)); if (target is null) { return default; } unsafe { // Unsafe.AsPointer calls are safe since object is pinned. if (RuntimeHelpers.ObjectHasComponentSize(target)) { if (target.GetType() == typeof(string)) { return (IntPtr)Unsafe.AsPointer(ref Unsafe.As<string>(target).GetRawStringData()); } Debug.Assert(target is Array); return (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(Unsafe.As<Array>(target))); } return (IntPtr)Unsafe.AsPointer(ref target.GetRawData()); } } /// <summary>Determine whether this handle has been allocated or not.</summary> public readonly bool IsAllocated => _handle != 0; /// <summary> /// Used to create a GCHandle from an int. This is intended to /// be used with the reverse conversion. /// </summary> public static explicit operator GCHandle(IntPtr value) => FromIntPtr(value); public static GCHandle FromIntPtr(IntPtr value) { ThrowIfInvalid(value); return new GCHandle(value); } /// <summary>Used to get the internal integer representation of the handle out.</summary> public static explicit operator IntPtr(GCHandle value) => ToIntPtr(value); public static IntPtr ToIntPtr(GCHandle value) => value._handle; public override readonly int GetHashCode() => _handle.GetHashCode(); public override readonly bool Equals([NotNullWhen(true)] object? o) => o is GCHandle other && Equals(other); /// <summary>Indicates whether the current instance is equal to another instance of the same type.</summary> /// <param name="other">An instance to compare with this instance.</param> /// <returns>true if the current instance is equal to the other instance; otherwise, false.</returns> public readonly bool Equals(GCHandle other) => _handle == other._handle; public static bool operator ==(GCHandle a, GCHandle b) => a._handle == b._handle; public static bool operator !=(GCHandle a, GCHandle b) => a._handle != b._handle; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static IntPtr GetHandleValue(IntPtr handle) => new IntPtr(handle & ~1); // Remove Pin flag [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsPinned(IntPtr handle) => (handle & 1) != 0; // Check Pin flag [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ThrowIfInvalid(IntPtr handle) { // Check if the handle was never initialized or was freed. if (handle == 0) { 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 unmanaged 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 } } }