// 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.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Security; namespace System.Runtime.InteropServices.WindowsRuntime { internal class CLRIPropertyValueImpl : IPropertyValue { private PropertyType _type; private Object _data; // Numeric scalar types which participate in coersion private static volatile Tuple[] s_numericScalarTypes; internal CLRIPropertyValueImpl(PropertyType type, Object data) { _type = type; _data = data; } private static Tuple[] NumericScalarTypes { get { if (s_numericScalarTypes == null) { Tuple[] numericScalarTypes = new Tuple[] { new Tuple(typeof(Byte), PropertyType.UInt8), new Tuple(typeof(Int16), PropertyType.Int16), new Tuple(typeof(UInt16), PropertyType.UInt16), new Tuple(typeof(Int32), PropertyType.Int32), new Tuple(typeof(UInt32), PropertyType.UInt32), new Tuple(typeof(Int64), PropertyType.Int64), new Tuple(typeof(UInt64), PropertyType.UInt64), new Tuple(typeof(Single), PropertyType.Single), new Tuple(typeof(Double), PropertyType.Double) }; s_numericScalarTypes = numericScalarTypes; } return s_numericScalarTypes; } } public PropertyType Type { [Pure] get { return _type; } } public bool IsNumericScalar { [Pure] get { return IsNumericScalarImpl(_type, _data); } } public override string ToString() { if (_data != null) { return _data.ToString(); } else { return base.ToString(); } } [Pure] public Byte GetUInt8() { return CoerceScalarValue(PropertyType.UInt8); } [Pure] public Int16 GetInt16() { return CoerceScalarValue(PropertyType.Int16); } public UInt16 GetUInt16() { return CoerceScalarValue(PropertyType.UInt16); } [Pure] public Int32 GetInt32() { return CoerceScalarValue(PropertyType.Int32); } [Pure] public UInt32 GetUInt32() { return CoerceScalarValue(PropertyType.UInt32); } [Pure] public Int64 GetInt64() { return CoerceScalarValue(PropertyType.Int64); } [Pure] public UInt64 GetUInt64() { return CoerceScalarValue(PropertyType.UInt64); } [Pure] public Single GetSingle() { return CoerceScalarValue(PropertyType.Single); } [Pure] public Double GetDouble() { return CoerceScalarValue(PropertyType.Double); } [Pure] public char GetChar16() { if (this.Type != PropertyType.Char16) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Char16"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (char)_data; } [Pure] public Boolean GetBoolean() { if (this.Type != PropertyType.Boolean) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Boolean"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (bool)_data; } [Pure] public String GetString() { return CoerceScalarValue(PropertyType.String); } [Pure] public Guid GetGuid() { return CoerceScalarValue(PropertyType.Guid); } [Pure] public DateTimeOffset GetDateTime() { if (this.Type != PropertyType.DateTime) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "DateTime"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (DateTimeOffset)_data; } [Pure] public TimeSpan GetTimeSpan() { if (this.Type != PropertyType.TimeSpan) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "TimeSpan"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (TimeSpan)_data; } [Pure] public Point GetPoint() { if (this.Type != PropertyType.Point) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Point"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return Unbox(IReferenceFactory.s_pointType); } [Pure] public Size GetSize() { if (this.Type != PropertyType.Size) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Size"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return Unbox(IReferenceFactory.s_sizeType); } [Pure] public Rect GetRect() { if (this.Type != PropertyType.Rect) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Rect"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return Unbox(IReferenceFactory.s_rectType); } [Pure] public Byte[] GetUInt8Array() { return CoerceArrayValue(PropertyType.UInt8Array); } [Pure] public Int16[] GetInt16Array() { return CoerceArrayValue(PropertyType.Int16Array); } [Pure] public UInt16[] GetUInt16Array() { return CoerceArrayValue(PropertyType.UInt16Array); } [Pure] public Int32[] GetInt32Array() { return CoerceArrayValue(PropertyType.Int32Array); } [Pure] public UInt32[] GetUInt32Array() { return CoerceArrayValue(PropertyType.UInt32Array); } [Pure] public Int64[] GetInt64Array() { return CoerceArrayValue(PropertyType.Int64Array); } [Pure] public UInt64[] GetUInt64Array() { return CoerceArrayValue(PropertyType.UInt64Array); } [Pure] public Single[] GetSingleArray() { return CoerceArrayValue(PropertyType.SingleArray); } [Pure] public Double[] GetDoubleArray() { return CoerceArrayValue(PropertyType.DoubleArray); } [Pure] public char[] GetChar16Array() { if (this.Type != PropertyType.Char16Array) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Char16[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (char[])_data; } [Pure] public Boolean[] GetBooleanArray() { if (this.Type != PropertyType.BooleanArray) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Boolean[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (bool[])_data; } [Pure] public String[] GetStringArray() { return CoerceArrayValue(PropertyType.StringArray); } [Pure] public Object[] GetInspectableArray() { if (this.Type != PropertyType.InspectableArray) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Inspectable[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (Object[])_data; } [Pure] public Guid[] GetGuidArray() { return CoerceArrayValue(PropertyType.GuidArray); } [Pure] public DateTimeOffset[] GetDateTimeArray() { if (this.Type != PropertyType.DateTimeArray) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "DateTimeOffset[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (DateTimeOffset[])_data; } [Pure] public TimeSpan[] GetTimeSpanArray() { if (this.Type != PropertyType.TimeSpanArray) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "TimeSpan[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return (TimeSpan[])_data; } [Pure] public Point[] GetPointArray() { if (this.Type != PropertyType.PointArray) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Point[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return UnboxArray(IReferenceFactory.s_pointType); } [Pure] public Size[] GetSizeArray() { if (this.Type != PropertyType.SizeArray) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Size[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return UnboxArray(IReferenceFactory.s_sizeType); } [Pure] public Rect[] GetRectArray() { if (this.Type != PropertyType.RectArray) throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, "Rect[]"), __HResults.TYPE_E_TYPEMISMATCH); Contract.EndContractBlock(); return UnboxArray(IReferenceFactory.s_rectType); } private T[] CoerceArrayValue(PropertyType unboxType) { // If we contain the type being looked for directly, then take the fast-path if (Type == unboxType) { return (T[])_data; } // Make sure we have an array to begin with Array dataArray = _data as Array; if (dataArray == null) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", this.Type, typeof(T).MakeArrayType().Name), __HResults.TYPE_E_TYPEMISMATCH); } // Array types are 1024 larger than their equivilent scalar counterpart BCLDebug.Assert((int)Type > 1024, "Unexpected array PropertyType value"); PropertyType scalarType = Type - 1024; // If we do not have the correct array type, then we need to convert the array element-by-element // to a new array of the requested type T[] coercedArray = new T[dataArray.Length]; for (int i = 0; i < dataArray.Length; ++i) { try { coercedArray[i] = CoerceScalarValue(scalarType, dataArray.GetValue(i)); } catch (InvalidCastException elementCastException) { Exception e = new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueArrayCoersion", this.Type, typeof(T).MakeArrayType().Name, i, elementCastException.Message), elementCastException); e.SetErrorCode(elementCastException._HResult); throw e; } } return coercedArray; } private T CoerceScalarValue(PropertyType unboxType) { // If we are just a boxed version of the requested type, then take the fast path out if (Type == unboxType) { return (T)_data; } return CoerceScalarValue(Type, _data); } private static T CoerceScalarValue(PropertyType type, object value) { // If the property type is neither one of the coercable numeric types nor IInspectable, we // should not attempt coersion, even if the underlying value is technically convertable if (!IsCoercable(type, value) && type != PropertyType.Inspectable) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", type, typeof(T).Name), __HResults.TYPE_E_TYPEMISMATCH); } try { // Try to coerce: // * String <--> Guid // * Numeric scalars if (type == PropertyType.String && typeof(T) == typeof(Guid)) { return (T)(object)Guid.Parse((string)value); } else if (type == PropertyType.Guid && typeof(T) == typeof(String)) { return (T)(object)((Guid)value).ToString("D", System.Globalization.CultureInfo.InvariantCulture); } else { // Iterate over the numeric scalars, to see if we have a match for one of the known conversions foreach (Tuple numericScalar in NumericScalarTypes) { if (numericScalar.Item1 == typeof(T)) { return (T)Convert.ChangeType(value, typeof(T), System.Globalization.CultureInfo.InvariantCulture); } } } } catch (FormatException) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", type, typeof(T).Name), __HResults.TYPE_E_TYPEMISMATCH); } catch (InvalidCastException) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", type, typeof(T).Name), __HResults.TYPE_E_TYPEMISMATCH); } catch (OverflowException) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueCoersion", type, value, typeof(T).Name), __HResults.DISP_E_OVERFLOW); } // If the property type is IInspectable, and we have a nested IPropertyValue, then we need // to pass along the request to coerce the value. IPropertyValue ipv = value as IPropertyValue; if (type == PropertyType.Inspectable && ipv != null) { if (typeof(T) == typeof(Byte)) { return (T)(object)ipv.GetUInt8(); } else if (typeof(T) == typeof(Int16)) { return (T)(object)ipv.GetInt16(); } else if (typeof(T) == typeof(UInt16)) { return (T)(object)ipv.GetUInt16(); } else if (typeof(T) == typeof(Int32)) { return (T)(object)ipv.GetUInt32(); } else if (typeof(T) == typeof(UInt32)) { return (T)(object)ipv.GetUInt32(); } else if (typeof(T) == typeof(Int64)) { return (T)(object)ipv.GetInt64(); } else if (typeof(T) == typeof(UInt64)) { return (T)(object)ipv.GetUInt64(); } else if (typeof(T) == typeof(Single)) { return (T)(object)ipv.GetSingle(); } else if (typeof(T) == typeof(Double)) { return (T)(object)ipv.GetDouble(); } else { BCLDebug.Assert(false, "T in coersion function wasn't understood as a type that can be coerced - make sure that CoerceScalarValue and NumericScalarTypes are in sync"); } } // Otherwise, this is an invalid coersion throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", type, typeof(T).Name), __HResults.TYPE_E_TYPEMISMATCH); } private static bool IsCoercable(PropertyType type, object data) { // String <--> Guid is allowed if (type == PropertyType.Guid || type == PropertyType.String) { return true; } // All numeric scalars can also be coerced return IsNumericScalarImpl(type, data); } private static bool IsNumericScalarImpl(PropertyType type, object data) { if (data.GetType().IsEnum) { return true; } foreach (Tuple numericScalar in NumericScalarTypes) { if (numericScalar.Item2 == type) { return true; } } return false; } // Unbox the data stored in the property value to a structurally equivilent type [Pure] private unsafe T Unbox(Type expectedBoxedType) where T : struct { Contract.Requires(expectedBoxedType != null); Contract.Requires(Marshal.SizeOf(expectedBoxedType) == Marshal.SizeOf(typeof(T))); if (_data.GetType() != expectedBoxedType) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", _data.GetType(), expectedBoxedType.Name), __HResults.TYPE_E_TYPEMISMATCH); } T unboxed = new T(); fixed (byte *pData = &JitHelpers.GetPinningHelper(_data).m_data) { byte* pUnboxed = (byte*)JitHelpers.UnsafeCastToStackPointer(ref unboxed); Buffer.Memcpy(pUnboxed, pData, Marshal.SizeOf(unboxed)); } return unboxed; } // Convert the array stored in the property value to a structurally equivilent array type [Pure] private unsafe T[] UnboxArray(Type expectedArrayElementType) where T : struct { Contract.Requires(expectedArrayElementType != null); Contract.Requires(Marshal.SizeOf(expectedArrayElementType) == Marshal.SizeOf(typeof(T))); Array dataArray = _data as Array; if (dataArray == null || _data.GetType().GetElementType() != expectedArrayElementType) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_WinRTIPropertyValueElement", _data.GetType(), expectedArrayElementType.MakeArrayType().Name), __HResults.TYPE_E_TYPEMISMATCH); } T[] converted = new T[dataArray.Length]; if (converted.Length > 0) { fixed (byte * dataPin = &JitHelpers.GetPinningHelper(dataArray).m_data) { fixed (byte * convertedPin = &JitHelpers.GetPinningHelper(converted).m_data) { byte *pData = (byte *)Marshal.UnsafeAddrOfPinnedArrayElement(dataArray, 0); byte *pConverted = (byte *)Marshal.UnsafeAddrOfPinnedArrayElement(converted, 0); Buffer.Memcpy(pConverted, pData, checked(Marshal.SizeOf(typeof(T)) * converted.Length)); } } } return converted; } } }