78 lines
3.4 KiB
C#
78 lines
3.4 KiB
C#
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace WhiteMagic;
|
|
|
|
/// <summary>
|
|
/// Computes and caches the byte size and marshalling decision for type
|
|
/// <typeparamref name="T"/> exactly once. <see cref="MemoryBase.Read{T}"/> and
|
|
/// <see cref="MemoryBase.Write{T}"/> branch on <see cref="TypeRequiresMarshal"/>
|
|
/// to decide between the blittable <c>Span</c> /
|
|
/// <see cref="System.Runtime.InteropServices.MemoryMarshal"/> path and the
|
|
/// <see cref="Marshal.PtrToStructure"/> path.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to cache metadata for.</typeparam>
|
|
public static class MarshalCache<T>
|
|
{
|
|
/// <summary>
|
|
/// The byte size of <typeparamref name="T"/>. For the blittable path this is
|
|
/// the managed layout size <see cref="Unsafe.SizeOf{T}"/> — the width that
|
|
/// <see cref="MemoryMarshal.Read{T}"/> / <see cref="MemoryMarshal.Write{T}"/>
|
|
/// actually consume. For primitive-sized types (<see cref="bool"/>, <see cref="char"/>,
|
|
/// and the underlying of enums) the size matches the CLR primitive width.
|
|
/// </summary>
|
|
public static readonly int Size;
|
|
|
|
/// <summary>
|
|
/// <see langword="true"/> when <typeparamref name="T"/> cannot be copied through
|
|
/// the blittable <see cref="System.Runtime.InteropServices.MemoryMarshal"/> path
|
|
/// and must fall back to <see cref="Marshal.PtrToStructure"/> /
|
|
/// <see cref="Marshal.StructureToPtr"/>. This is the case when a top-level field
|
|
/// carries <see cref="MarshalAsAttribute"/>, or when <typeparamref name="T"/>
|
|
/// contains a managed reference
|
|
/// (<see cref="RuntimeHelpers.IsReferenceOrContainsReferences{T}"/>).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <see cref="MarshalAsAttribute"/> check inspects only top-level fields; a
|
|
/// <see cref="MarshalAsAttribute"/> on a field of a nested struct is not detected.
|
|
/// Reference-containing nested structs are still caught, because the reference
|
|
/// check propagates through nested value types.
|
|
/// </remarks>
|
|
public static readonly bool TypeRequiresMarshal;
|
|
|
|
static MarshalCache()
|
|
{
|
|
if (typeof(T) == typeof(bool))
|
|
{
|
|
Size = 1;
|
|
}
|
|
else if (typeof(T) == typeof(char))
|
|
{
|
|
// Marshal.SizeOf<char> reports 1 (ANSI char), but the blittable
|
|
// MemoryMarshal path reads/writes a char as a 2-byte UTF-16 code unit.
|
|
// Use the managed layout width so Size matches what the reader actually uses.
|
|
Size = 2;
|
|
}
|
|
else if (typeof(T).IsEnum)
|
|
{
|
|
Size = Marshal.SizeOf(typeof(T).GetEnumUnderlyingType());
|
|
}
|
|
else
|
|
{
|
|
// The blittable path goes through MemoryMarshal, which uses the CLR managed
|
|
// layout. Use Unsafe.SizeOf<T> so Size agrees with that layout —
|
|
// Marshal.SizeOf<T> can disagree when a struct contains a `bool` field
|
|
// (unmanaged width 4 vs managed width 1).
|
|
Size = Unsafe.SizeOf<T>();
|
|
}
|
|
|
|
bool hasMarshalAsField =
|
|
typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
|
.Any(f => f.GetCustomAttributes(typeof(MarshalAsAttribute), true).Length != 0);
|
|
|
|
TypeRequiresMarshal =
|
|
hasMarshalAsField || RuntimeHelpers.IsReferenceOrContainsReferences<T>();
|
|
}
|
|
}
|