using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace WhiteMagic; /// /// Caches the widths marshalling decisions for type /// once, at static-constructor time. and /// branch on /// and pick the appropriate width from this cache. /// /// The type to cache metadata for. public static class MarshalCache { /// /// The blittable (managed layout) width of . This is /// what / /// actually consume. Equals in the general case, /// with fixed-width overrides for , , and /// enums so the cache value matches the primitive layout width used by those /// paths. /// public static readonly int Size; /// /// The unmanaged (interop) width via . The marshal /// path (/) /// reads/writes this many bytes. Exceeds whenever a struct /// carries inline unmanaged data that the marshaler expands — inline /// ByValTStr/ByValArray buffers, bool fields (4 bytes per /// default Win32 BOOL marshaling vs 1 byte managed), etc. For types that do /// not go through the marshal path, this field is still populated but unused /// by MemoryBase. /// /// /// Marshal.SizeOf throws for some reference-containing shapes (e.g. a /// bare ). When that happens, we fall back to /// — the fallback path is unreachable from production code /// because types with a reference field always have /// true, so MemoryBase reads this field only /// when it is known to be populated. /// public static readonly int MarshalSize; /// /// when cannot be copied through /// the blittable path /// and must fall back to / /// . This is the case when a top-level field /// carries , or when /// contains a managed reference /// (). /// /// /// The check inspects only top-level fields; a /// 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. /// public static readonly bool TypeRequiresMarshal; static MarshalCache() { if (typeof(T) == typeof(bool)) { Size = 1; } else if (typeof(T) == typeof(char)) { // Marshal.SizeOf reports 1 (ANSI char), but the blittable // MemoryMarshal path reads/writes a char as a 2-byte UTF-16 code unit. 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 so Size agrees with that // layout — Marshal.SizeOf disagrees when a struct contains a // `bool` field (unmanaged 4 vs managed 1). Size = Unsafe.SizeOf(); } bool hasMarshalAsField = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Any(f => f.GetCustomAttributes(typeof(MarshalAsAttribute), true).Length != 0); TypeRequiresMarshal = hasMarshalAsField || RuntimeHelpers.IsReferenceOrContainsReferences(); // MarshalSize is only consulted when TypeRequiresMarshal is true; for the // rare case where Marshal.SizeOf refuses a shape (ref-containing structs), // fall back to the managed size so the field stays populated. try { MarshalSize = Marshal.SizeOf(); } catch (ArgumentException) { MarshalSize = Size; } } }