mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2024-11-25 01:42:46 +01:00
142 lines
6.6 KiB
C#
142 lines
6.6 KiB
C#
// 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;
|
|
|
|
/// <summary>
|
|
/// Helpers for working with <see cref="object"/> instances.
|
|
/// </summary>
|
|
public static class ObjectMarshal
|
|
{
|
|
/// <summary>
|
|
/// Calculates the byte offset to a specific field within a given <see cref="object"/>.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of field being referenced.</typeparam>
|
|
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
|
/// <param name="data">A reference to a target field of type <typeparamref name="T"/> within <paramref name="obj"/>.</param>
|
|
/// <returns>
|
|
/// The <see cref="IntPtr"/> value representing the offset to the target field from the start of the object data
|
|
/// for the parameter <paramref name="obj"/>. The offset is in relation to the first usable byte after the method table.
|
|
/// </returns>
|
|
/// <remarks>The input parameters are not validated, and it's responsibility of the caller to ensure that
|
|
/// the <paramref name="data"/> reference is actually pointing to a memory location within <paramref name="obj"/>.
|
|
/// </remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static IntPtr DangerousGetObjectDataByteOffset<T>(object obj, ref T data)
|
|
{
|
|
RawObjectData? rawObj = Unsafe.As<RawObjectData>(obj)!;
|
|
ref byte r0 = ref rawObj.Data;
|
|
ref byte r1 = ref Unsafe.As<T, byte>(ref data);
|
|
|
|
return Unsafe.ByteOffset(ref r0, ref r1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a <typeparamref name="T"/> reference to data within a given <see cref="object"/> at a specified offset.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of reference to retrieve.</typeparam>
|
|
/// <param name="obj">The input <see cref="object"/> hosting the target field.</param>
|
|
/// <param name="offset">The input byte offset for the <typeparamref name="T"/> reference to retrieve.</param>
|
|
/// <returns>A <typeparamref name="T"/> reference at a specified offset within <paramref name="obj"/>.</returns>
|
|
/// <remarks>
|
|
/// 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 <see cref="object"/> instance, that might lead to runtime crashes.
|
|
/// </remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ref T DangerousGetObjectDataReferenceAt<T>(object obj, IntPtr offset)
|
|
{
|
|
RawObjectData? rawObj = Unsafe.As<RawObjectData>(obj)!;
|
|
ref byte r0 = ref rawObj.Data;
|
|
ref byte r1 = ref Unsafe.AddByteOffset(ref r0, offset);
|
|
ref T r2 = ref Unsafe.As<byte, T>(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<RawObjectData>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get a boxed <typeparamref name="T"/> value from an input <see cref="object"/> instance.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of value to try to unbox.</typeparam>
|
|
/// <param name="obj">The input <see cref="object"/> instance to check.</param>
|
|
/// <param name="value">The resulting <typeparamref name="T"/> value, if <paramref name="obj"/> was in fact a boxed <typeparamref name="T"/> value.</param>
|
|
/// <returns><see langword="true"/> if a <typeparamref name="T"/> value was retrieved correctly, <see langword="false"/> otherwise.</returns>
|
|
/// <remarks>
|
|
/// This extension behaves just like the following method:
|
|
/// <code>
|
|
/// public static bool TryUnbox<T>(object obj, out T value)
|
|
/// {
|
|
/// if (obj is T)
|
|
/// {
|
|
/// value = (T)obj;
|
|
///
|
|
/// return true;
|
|
/// }
|
|
///
|
|
/// value = default;
|
|
///
|
|
/// return false;
|
|
/// }
|
|
/// </code>
|
|
/// 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.
|
|
/// </remarks>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static bool TryUnbox<T>(this object obj, out T value)
|
|
where T : struct
|
|
{
|
|
if (obj.GetType() == typeof(T))
|
|
{
|
|
value = Unsafe.Unbox<T>(obj);
|
|
|
|
return true;
|
|
}
|
|
|
|
value = default;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unboxes a <typeparamref name="T"/> value from an input <see cref="object"/> instance.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of value to unbox.</typeparam>
|
|
/// <param name="obj">The input <see cref="object"/> instance, representing a boxed <typeparamref name="T"/> value.</param>
|
|
/// <returns>The <typeparamref name="T"/> value boxed in <paramref name="obj"/>.</returns>
|
|
/// <exception cref="InvalidCastException">Thrown when <paramref name="obj"/> is not of type <typeparamref name="T"/>.</exception>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ref T DangerousUnbox<T>(object obj)
|
|
where T : struct
|
|
{
|
|
return ref Unsafe.Unbox<T>(obj);
|
|
}
|
|
}
|