// 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 fromPointer; /// /// 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); fromPointer = false; } /// /// Recreates an based on the passed . /// Disposing this object will not free the handle, the original object must be disposed instead. /// /// Handle. public ObjectHandle(IntPtr handle) { this.handle = GCHandle.FromIntPtr(handle); fromPointer = true; } /// /// 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 (!fromPointer && handle.IsAllocated) handle.Free(); } #endregion } }