using System; using System.Reflection; using System.Runtime.InteropServices; using Unosquare.Swan.Attributes; namespace Unosquare.Swan { /// /// Provides various extension methods for value types and structs. /// public static class ValueTypeExtensions { /// /// Clamps the specified value between the minimum and the maximum. /// /// The type of value to clamp. /// The value. /// The minimum. /// The maximum. /// A value that indicates the relative order of the objects being compared. public static T Clamp(this T value, T min, T max) where T : struct, IComparable => value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value; /// /// Clamps the specified value between the minimum and the maximum. /// /// The value. /// The minimum. /// The maximum. /// A value that indicates the relative order of the objects being compared. public static Int32 Clamp(this Int32 value, Int32 min, Int32 max) => value < min ? min : (value > max ? max : value); /// /// Determines whether the specified value is between a minimum and a maximum value. /// /// The type of value to check. /// The value. /// The minimum. /// The maximum. /// /// true if the specified minimum is between; otherwise, false. /// public static Boolean IsBetween(this T value, T min, T max) where T : struct, IComparable => value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; /// /// Converts an array of bytes into the given struct type. /// /// The type of structure to convert. /// The data. /// a struct type derived from convert an array of bytes ref=ToStruct". public static T ToStruct(this Byte[] data) where T : struct => ToStruct(data, 0, data.Length); /// /// Converts an array of bytes into the given struct type. /// /// The type of structure to convert. /// The data. /// The offset. /// The length. /// /// A managed object containing the data pointed to by the ptr parameter. /// /// data. public static T ToStruct(this Byte[] data, Int32 offset, Int32 length) where T : struct { if(data == null) { throw new ArgumentNullException(nameof(data)); } Byte[] buffer = new Byte[length]; Array.Copy(data, offset, buffer, 0, buffer.Length); GCHandle handle = GCHandle.Alloc(GetStructBytes(buffer), GCHandleType.Pinned); try { return Marshal.PtrToStructure(handle.AddrOfPinnedObject()); } finally { handle.Free(); } } /// /// Converts a struct to an array of bytes. /// /// The type of structure to convert. /// The object. /// A byte array containing the results of encoding the specified set of characters. public static Byte[] ToBytes(this T obj) where T : struct { Byte[] data = new Byte[Marshal.SizeOf(obj)]; GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); try { Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false); return GetStructBytes(data); } finally { handle.Free(); } } /// /// Swaps the endianness of an unsigned long to an unsigned integer. /// /// The bytes contained in a long. /// /// A 32-bit unsigned integer equivalent to the ulong /// contained in longBytes. /// public static UInt32 SwapEndianness(this UInt64 longBytes) => (UInt32)(((longBytes & 0x000000ff) << 24) + ((longBytes & 0x0000ff00) << 8) + ((longBytes & 0x00ff0000) >> 8) + ((longBytes & 0xff000000) >> 24)); private static Byte[] GetStructBytes(Byte[] data) { if(data == null) { throw new ArgumentNullException(nameof(data)); } #if !NETSTANDARD1_3 FieldInfo[] fields = typeof(T).GetTypeInfo() .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); #else var fields = typeof(T).GetTypeInfo().DeclaredFields; #endif StructEndiannessAttribute endian = Runtime.AttributeCache.RetrieveOne(); foreach(FieldInfo field in fields) { if(endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false)) { continue; } Int32 offset = Marshal.OffsetOf(field.Name).ToInt32(); Int32 length = Marshal.SizeOf(field.FieldType); endian = endian ?? Runtime.AttributeCache.RetrieveOne(field); if(endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian || endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian)) { Array.Reverse(data, offset, length); } } return data; } } }