// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace CommunityToolkit.HighPerformance.Helpers; /// /// Helpers for working with instances. /// public static class ObjectMarshal { /// /// Calculates the byte offset to a specific field within a given . /// /// The type of field being referenced. /// The input hosting the target field. /// A reference to a target field of type within . /// /// The value representing the offset to the target field from the start of the object data /// for the parameter . The offset is in relation to the first usable byte after the method table. /// /// The input parameters are not validated, and it's responsibility of the caller to ensure that /// the reference is actually pointing to a memory location within . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IntPtr DangerousGetObjectDataByteOffset(object obj, ref T data) { RawObjectData? rawObj = Unsafe.As(obj)!; ref byte r0 = ref rawObj.Data; ref byte r1 = ref Unsafe.As(ref data); return Unsafe.ByteOffset(ref r0, ref r1); } /// /// Gets a reference to data within a given at a specified offset. /// /// The type of reference to retrieve. /// The input hosting the target field. /// The input byte offset for the reference to retrieve. /// A reference at a specified offset within . /// /// None of the input arguments is validated, and it is responsibility of the caller to ensure they are valid. /// In particular, using an invalid offset might cause the retrieved reference to be misaligned with the /// desired data, which would break the type system. Or, if the offset causes the retrieved reference to point /// to a memory location outside of the input instance, that might lead to runtime crashes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetObjectDataReferenceAt(object obj, IntPtr offset) { RawObjectData? rawObj = Unsafe.As(obj)!; ref byte r0 = ref rawObj.Data; ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset); ref T r2 = ref Unsafe.As(ref r1); return ref r2; } // Description adapted from CoreCLR, see: // https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,301. // CLR objects are laid out in memory as follows: // [ sync block || pMethodTable || raw data .. ] // ^ ^ // | \-- ref Unsafe.As(owner).Data // \-- object // The reference to RawObjectData.Data points to the first data byte in the // target object, skipping over the sync block, method table and string length. // Even though the description above links to the CoreCLR source, this approach // can actually work on any .NET runtime, as it doesn't rely on a specific memory // layout. Even if some 3rd party .NET runtime had some additional fields in the // object header, before the field being referenced, the returned offset would still // be valid when used on instances of that particular type, as it's only being // used as a relative offset from the location pointed by the object reference. [StructLayout(LayoutKind.Explicit)] private sealed class RawObjectData { [FieldOffset(0)] public byte Data; } /// /// Tries to get a boxed value from an input instance. /// /// The type of value to try to unbox. /// The input instance to check. /// The resulting value, if was in fact a boxed value. /// if a value was retrieved correctly, otherwise. /// /// This extension behaves just like the following method: /// /// public static bool TryUnbox<T>(object obj, out T value) /// { /// if (obj is T) /// { /// value = (T)obj; /// /// return true; /// } /// /// value = default; /// /// return false; /// } /// /// But in a more efficient way, and with the ability to also assign the unboxed value /// directly on an existing T variable, which is not possible with the code above. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool TryUnbox(this object obj, out T value) where T : struct { if (obj.GetType() == typeof(T)) { value = Unsafe.Unbox(obj); return true; } value = default; return false; } /// /// Unboxes a value from an input instance. /// /// The type of value to unbox. /// The input instance, representing a boxed value. /// The value boxed in . /// Thrown when is not of type . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousUnbox(object obj) where T : struct { return ref Unsafe.Unbox(obj); } }