89 lines
3.5 KiB
C#
89 lines
3.5 KiB
C#
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace WhiteMagic;
|
|
|
|
/// <summary>
|
|
/// Computes and caches marshal-related metadata for type <typeparamref name="T"/>
|
|
/// exactly once. <see cref="MemoryBase.Read{T}"/> and <see cref="MemoryBase.Write{T}"/>
|
|
/// branch on these cached flags to decide between blittable <c>Span</c>/<c>MemoryMarshal</c>
|
|
/// paths and the fallback marshal path.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to cache metadata for.</typeparam>
|
|
public static class MarshalCache<T>
|
|
{
|
|
/// <summary>The unmanaged size of <typeparamref name="T"/> in bytes.</summary>
|
|
public static readonly int Size;
|
|
|
|
/// <summary>The unmanaged size of <typeparamref name="T"/> as an unsigned integer.</summary>
|
|
public static readonly uint SizeU;
|
|
|
|
/// <summary>
|
|
/// <see langword="true"/> when <typeparamref name="T"/> cannot be copied through the
|
|
/// blittable <see cref="System.Runtime.InteropServices.MemoryMarshal"/> path and must
|
|
/// use <see cref="Marshal.PtrToStructure"/>/<see cref="Marshal.StructureToPtr"/> instead.
|
|
/// This is the case when a top-level field carries <see cref="MarshalAsAttribute"/>, or
|
|
/// when <typeparamref name="T"/> contains a managed reference
|
|
/// (<see cref="System.Runtime.CompilerServices.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;
|
|
|
|
/// <summary><see langword="true"/> when <typeparamref name="T"/> is <see cref="IntPtr"/>.</summary>
|
|
public static readonly bool IsIntPtr;
|
|
|
|
/// <summary>The underlying type code of <typeparamref name="T"/>.</summary>
|
|
public static readonly TypeCode TypeCode;
|
|
|
|
/// <summary>
|
|
/// The effective type that the marshaler uses. For an enum this is the underlying
|
|
/// integer type; for all other types it is <typeparamref name="T"/> itself.
|
|
/// </summary>
|
|
public static readonly Type RealType;
|
|
|
|
static MarshalCache()
|
|
{
|
|
TypeCode = Type.GetTypeCode(typeof(T));
|
|
|
|
if (typeof(T) == typeof(bool))
|
|
{
|
|
Size = 1;
|
|
RealType = typeof(T);
|
|
}
|
|
else if (typeof(T) == typeof(char))
|
|
{
|
|
// Marshal.SizeOf(char) is 1 (ANSI), but the blittable path reads/writes a
|
|
// char as a 2-byte UTF-16 code unit. Size must match the blittable width.
|
|
Size = 2;
|
|
RealType = typeof(T);
|
|
}
|
|
else if (typeof(T).IsEnum)
|
|
{
|
|
Type underlying = typeof(T).GetEnumUnderlyingType();
|
|
Size = Marshal.SizeOf(underlying);
|
|
RealType = underlying;
|
|
TypeCode = Type.GetTypeCode(underlying);
|
|
}
|
|
else
|
|
{
|
|
Size = Marshal.SizeOf(typeof(T));
|
|
RealType = typeof(T);
|
|
}
|
|
|
|
SizeU = (uint)Size;
|
|
IsIntPtr = RealType == typeof(IntPtr);
|
|
|
|
bool hasMarshalAsField =
|
|
RealType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
|
.Any(f => f.GetCustomAttributes(typeof(MarshalAsAttribute), true).Length != 0);
|
|
|
|
TypeRequiresMarshal =
|
|
hasMarshalAsField || System.Runtime.CompilerServices.RuntimeHelpers.IsReferenceOrContainsReferences<T>();
|
|
}
|
|
}
|