// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System.Runtime.InteropServices; namespace SDL.Tests { /// /// Wrapper on that supports the pattern. /// public struct ObjectHandle : IDisposable where T : class { /// /// The pointer from the , if it is allocated. Otherwise . /// public IntPtr Handle => handle.IsAllocated ? GCHandle.ToIntPtr(handle) : IntPtr.Zero; /// /// The address of target object, if it is allocated and pinned. Otherwise . /// Note: This is not the same as the . /// public IntPtr Address => handle.IsAllocated ? handle.AddrOfPinnedObject() : IntPtr.Zero; public bool IsAllocated => handle.IsAllocated; private GCHandle handle; private readonly bool canFree; /// /// Wraps the provided object with a , using the given . /// /// The target object to wrap. /// The handle type to use. public ObjectHandle(T target, GCHandleType handleType) { handle = GCHandle.Alloc(target, handleType); canFree = true; } /// /// Recreates an based on the passed . /// If is true, disposing this object will free the handle. /// /// from a previously constructed . /// Whether this instance owns the underlying . public ObjectHandle(IntPtr handle, bool ownsHandle = false) { this.handle = GCHandle.FromIntPtr(handle); canFree = ownsHandle; } /// /// Gets the object being referenced. /// Returns true if successful and populates with the referenced object. /// Returns false If the handle is not allocated or the target is not of type . /// /// Populates this parameter with the targeted object. public bool GetTarget(out T target) { if (!handle.IsAllocated) { target = default; return false; } try { if (handle.Target is T value) { target = value; return true; } } catch (InvalidOperationException) { } target = default; return false; } #region IDisposable Support public void Dispose() { if (canFree && handle.IsAllocated) handle.Free(); } #endregion } }