using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Security; using Vanara.Extensions; using Vanara.InteropServices; using static Vanara.PInvoke.OleAut32; using static Vanara.PInvoke.PropSys; using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; namespace Vanara.PInvoke { /// public static partial class Ole32 { /// /// Structure to mimic behavior of VT_BLOB type. "DWORD count of bytes, followed by that many bytes of data. The byte count does not /// include the four bytes for the length of the count itself; an empty blob member would have a count of zero, followed by zero bytes." /// [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct BLOB { /// The count of bytes public uint cbSize; /// A pointer to the allocated array of bytes. public IntPtr pBlobData; } /// Structure to hold VT_CLIPDATE content. [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct CLIPDATA { /// The size of the buffer pointed to by pClipData, plus sizeof(ulClipFmt) public int cbSize; /// The clipboard format. public int ulClipFmt; /// The clipboard data. public IntPtr pClipData; /// Initializes a new instance of the struct. /// The clipboard format. /// A pointer to the data. /// /// Length of the data in bytes. Do not include any length other than that pointed to by . /// public CLIPDATA(int clipFmt, IntPtr dataPtr, int dataLength) { cbSize = dataLength + Marshal.SizeOf(typeof(int)); ulClipFmt = clipFmt; pClipData = dataPtr; } /// Initializes a new instance of the struct. /// The string value to register as a new clipboard format using RegisterClipboardFormat. public CLIPDATA(string clipFmt) { pClipData = Marshal.StringToHGlobalAuto(clipFmt); ulClipFmt = clipFmt.Length + 1; cbSize = ulClipFmt * Marshal.SystemDefaultCharSize + Marshal.SizeOf(typeof(int)); } /// The clipboard format. public uint ClipboardFormat => ulClipFmt == -1 || ulClipFmt == -2 ? (uint)Marshal.ReadInt32(pClipData) : 0; /// The clipboard name. public string ClipboardFormatName => ulClipFmt > 0 ? Marshal.PtrToStringUni(pClipData) : null; /// The clipboard format id. public Guid FMTID => ulClipFmt == -3 ? pClipData.ToStructure() : Guid.Empty; /// public override string ToString() { switch (ulClipFmt) { case 0: return "[No data]"; case -1: case -2: return $"CF={ClipboardFormat}"; case -3: return $"FMTID={FMTID:B}"; default: return $"Name={ClipboardFormatName}"; } } } #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member [StructLayout(LayoutKind.Sequential, Pack = 2)] public struct PACKEDMETA { public ushort mm; public ushort xExt; public ushort yExt; public ushort reserved; } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member /// /// The PROPVARIANT structure is used in the ReadMultiple and WriteMultiple methods of IPropertyStorage to define the type tag and /// the value of a property in a property set. /// /// The PROPVARIANT structure is also used by the GetValue and SetValue methods of IPropertyStore, which replaces IPropertySetStorage /// as the primary way to program item properties in Windows Vista. For more information, see Property Handlers. /// /// /// There are five members. The first member, the value-type tag, and the last member, the value of the property, are significant. /// The middle three members are reserved for future use. /// /// [StructLayout(LayoutKind.Explicit, Size = 16, Pack = 8)] public sealed class PROPVARIANT : ICloneable, IComparable, IComparable, IDisposable, IEquatable { /// Value type tag. [FieldOffset(0)] public VARTYPE vt; /// Reserved for future use. [FieldOffset(2)] public ushort wReserved1; /// Reserved for future use. [FieldOffset(4)] public ushort wReserved2; /// Reserved for future use. [FieldOffset(6)] public ushort wReserved3; /// The decimal value when VT_DECIMAL. [FieldOffset(0)] internal decimal _decimal; /// The raw data pointer. [FieldOffset(8)] internal IntPtr _ptr; /// The FILETIME when VT_FILETIME. [FieldOffset(8)] internal FILETIME _ft; /// The BLOB when VT_BLOB [FieldOffset(8)] internal BLOB _blob; /// The value when a numeric value less than 8 bytes. [FieldOffset(8)] internal ulong _ulong; /// Initializes a new instance of the class as VT_EMPTY. public PROPVARIANT() { } /// Initializes a new instance of the class with an object. /// The object to wrap. Based on the object type, it will infer the value type and allocate memory as needed. /// If not VT_EMPTY, this value will override the inferred value type. public PROPVARIANT(object obj, VarEnum type = VarEnum.VT_EMPTY) { if (obj is null) VarType = type; else if (obj is PROPVARIANT pv) PropVariantCopy(this, pv); else SetValue(obj, type); } /// Finalizes an instance of the class. ~PROPVARIANT() { Dispose(); } /// Gets the BLOB value. public BLOB blob => GetRawValue().GetValueOrDefault(); /// Gets the boolean value. public bool boolVal => GetRawValue().GetValueOrDefault(); /// Gets the BSTR value. public string bstrVal => GetString(VarType); /// Gets the byte value. public byte bVal => GetRawValue().GetValueOrDefault(); /// Gets the boolean array value. public IEnumerable cabool => GetVector().Select(s => s != 0); /// Gets the string array value. public IEnumerable cabstr => GetStringVector(); /// Gets the sbyte array value. public IEnumerable cac => GetVector(); /// Gets the CLIPDATA array value. public IEnumerable caclipdata => GetVector(); /// Gets the decimal array value. public IEnumerable cacy => GetVector().Select(decimal.FromOACurrency); /// Gets the DateTime array value. public IEnumerable cadate => GetVector().Select(DateTime.FromOADate); /// Gets the double array value. public IEnumerable cadbl => GetVector(); /// Gets the FILETIME array value. public IEnumerable cafiletime => GetVector(); /// Gets the float array value. public IEnumerable caflt => GetVector(); /// Gets the long array value. public IEnumerable cah => GetVector(); /// Gets the short array value. public IEnumerable cai => GetVector(); /// Gets the int array value. public IEnumerable cal => GetVector(); /// Gets the ANSI string array value. public IEnumerable calpstr => GetStringVector(); /// Gets the Unicode string array value. public IEnumerable calpwstr => GetStringVector(); /// Gets the PROPVARIANT array value. public IEnumerable capropvar => GetVector(); /// Gets the Win32Error array value. public IEnumerable cascode => GetVector(); /// Gets the byte array value. public IEnumerable caub => GetVector(); /// Gets the ulong array value. public IEnumerable cauh => GetVector(); /// Gets the ushort array value. public IEnumerable caui => GetVector(); /// Gets the uint array value. public IEnumerable caul => GetVector(); /// Gets the Guid array value. public IEnumerable cauuid => GetVector(); /// Gets the sbyte value. public sbyte cVal => GetRawValue().GetValueOrDefault(); /// Gets the decimal value. public decimal cyVal => decimal.FromOACurrency(GetRawValue().GetValueOrDefault()); /// Gets the date. public DateTime date => DateTime.FromOADate(GetRawValue().GetValueOrDefault()); /// Gets the double value. public double dblVal => GetRawValue().GetValueOrDefault(); /// Gets the FILETIME. public FILETIME filetime => _ft; /// Gets the float value. public float fltVal => GetRawValue().GetValueOrDefault(); /// Gets the long value. public long hVal => GetRawValue().GetValueOrDefault(); /// Gets the int value. public int intVal => GetRawValue().GetValueOrDefault(); //public IntPtr pparray { get { return GetRefValue(); } } /// Gets a value indicating whether this instance is by reference. /// true if this instance is null or empty; otherwise, false. public bool IsByRef => vt.IsFlagSet(VARTYPE.VT_BYREF); /// Gets a value indicating whether this instance is null or empty. /// true if this instance is null or empty; otherwise, false. public bool IsNullOrEmpty => vt == VARTYPE.VT_EMPTY || vt == VARTYPE.VT_NULL; /// Gets a value indicating whether this instance is a string. /// true if this instance is a VT_BSTR or VT_LPWSTR; otherwise, false. public bool IsString => vt == VARTYPE.VT_BSTR || vt == VARTYPE.VT_LPWSTR; /// Gets a value indicating whether this instance has a vector type. /// true if this instance is a VT_ARRAY or VT_VECTOR; otherwise, false. public bool IsVector => vt.IsFlagSet(VARTYPE.VT_ARRAY) || vt.IsFlagSet(VARTYPE.VT_VECTOR); /// Gets the short value. public short iVal => GetRawValue().GetValueOrDefault(); /// Gets the int value. public int lVal => GetRawValue().GetValueOrDefault(); /// Gets the array of objects. public IEnumerable parray => GetSafeArray(); /// Gets the "by value" boolean value. public bool? pboolVal => GetRawValue(); /// Gets the "by value" string value. public IntPtr pbstrVal => _ptr; /// Gets the "by value" byte value. public byte? pbVal => GetRawValue(); /// Gets the "by value" CLIPDATA value. public CLIPDATA pclipdata => GetRawValue().GetValueOrDefault(); /// Gets the "by value" sbyte value. public sbyte? pcVal => GetRawValue(); /// Gets the "by value" decimal value. public decimal? pcyVal { get { var d = GetRawValue(); return d.HasValue ? decimal.FromOACurrency(d.Value) : (decimal?)null; } } /// Gets the "by value" DateTime value. public DateTime? pdate { get { var d = GetRawValue(); return d.HasValue ? DateTime.FromOADate(d.Value) : (DateTime?)null; } } /// Gets the "by value" double value. public double? pdblVal => GetRawValue(); /// Gets the "by value" decimal value. public decimal? pdecVal => GetRawValue(); /// Gets the "by value" pointer value. public object pdispVal => punkVal; /// Gets the "by value" float value. public float? pfltVal => GetRawValue(); /// Gets the "by value" int value. public int? pintVal => GetRawValue(); /// Gets the "by value" short value. public short? piVal => GetRawValue(); /// Gets the "by value" int value. public int? plVal => GetRawValue(); /// Gets the IDispatch value. public object ppdispVal => ppunkVal; /// Gets the IUnknown value. public object ppunkVal => GetRawValue().GetValueOrDefault(); /// Gets the "by value" Win32Error value. public Win32Error? pscode { get { var r = GetRawValue(); return r.HasValue ? new Win32Error(r.Value) : (Win32Error?)null; } } /// Gets the IStorage value. public IStorage pStorage => (IStorage)punkVal; /// Gets the IStream value. public IStream pStream => (IStream)punkVal; /// Gets the ANSI string value. public string pszVal => GetString(VarType); /// Gets the "by value" uint value. public uint? puintVal => GetRawValue(); /// Gets the "by value" ushort value. public ushort? puiVal => GetRawValue(); /// Gets the "by value" uint value. public uint? pulVal => GetRawValue(); /// Gets the "by value" IUnknown value. public object punkVal => _ptr == IntPtr.Zero ? null : Marshal.GetObjectForIUnknown(_ptr); /// Gets the "by value" Guid value. public Guid? puuid => GetRawValue(); /// Gets the "by value" PROPVARIANT value. public PROPVARIANT pvarVal => _ptr.ToStructure(); /// Gets a stream with a Guid version. public IntPtr pVersionedStream => GetRawValue().GetValueOrDefault(); /// Gets the Unicode string value. public string pwszVal => GetString(VarType); /// Gets the Win32Error value. public Win32Error scode => new Win32Error(GetRawValue().GetValueOrDefault()); /// Gets the ulong value. public ulong uhVal => GetRawValue().GetValueOrDefault(); /// Gets the uint value. public uint uintVal => GetRawValue().GetValueOrDefault(); /// Gets the ushort value. public ushort uiVal => GetRawValue().GetValueOrDefault(); /// Gets the uint value. public uint ulVal => GetRawValue().GetValueOrDefault(); /// Gets the value base on the value. public object Value { get => GetValue(); private set => SetValue(value); } /// Gets or sets the type of the variable. /// The value type. public VarEnum VarType { get => (VarEnum)vt; set => vt = (VARTYPE)value; } private bool IsPrimitive { get { switch (vt) { // Signed case VARTYPE.VT_I1: case VARTYPE.VT_I2: case VARTYPE.VT_I4: case VARTYPE.VT_I8: // Unsigned case VARTYPE.VT_UI1: case VARTYPE.VT_UI2: case VARTYPE.VT_UI4: case VARTYPE.VT_UI8: // Converts to int or uint case VARTYPE.VT_BOOL: case VARTYPE.VT_ERROR: case VARTYPE.VT_HRESULT: case VARTYPE.VT_INT: case VARTYPE.VT_UINT: // Floats case VARTYPE.VT_R4: case VARTYPE.VT_R8: // Dates case VARTYPE.VT_DATE: case VARTYPE.VT_FILETIME: return true; default: return false; } } } /// Creates a new instance from a pointer to a VARIANT. /// A pointer to a native in-memory VARIANT. /// A new instance converted from the VARIANT pointer. public static PROPVARIANT FromNativeVariant(IntPtr pSrcNativeVariant) { var pv = new PROPVARIANT(); VariantToPropVariant(pSrcNativeVariant, pv).ThrowIfFailed(); return pv; } /// Gets the Type for a provided VARTYPE. /// The VARTYPE value to lookup. /// A best fit for the provided VARTYPE. public static Type GetType(VARTYPE vt) { // Safe arrays are always pointers if (vt.IsFlagSet(VARTYPE.VT_ARRAY)) return typeof(IntPtr); var elemType = (VARTYPE)((int)vt & 0xFFF); // VT_NULL is always DBNull if (elemType == VARTYPE.VT_NULL) return typeof(DBNull); // Get type of element, return null if VT_EMPTY or not found var type = CorrespondingTypeAttribute.GetCorrespondingTypes(elemType).FirstOrDefault(); if (type == null || elemType == 0) return null; // Change type if by reference if (vt.IsFlagSet(VARTYPE.VT_BYREF)) { type = Nullable.GetUnderlyingType(type); if (type.IsValueType) type = typeof(Nullable<>).MakeGenericType(type); } // Change type if vector if (vt.IsFlagSet(VARTYPE.VT_VECTOR)) { type = typeof(IEnumerable<>).MakeGenericType(type); } return type; } /// Gets the VARTYPE for a provided type. /// The type to analyze. /// A best fit for the provided type. public static VARTYPE GetVarType(Type type) { if (type == null) return VARTYPE.VT_NULL; var elemtype = type.GetElementType() ?? type; if (type.IsArray && elemtype == typeof(object)) return VARTYPE.VT_ARRAY | VARTYPE.VT_VARIANT; var isEnumerable = type.IsArray || type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type); VARTYPE ret = 0; if (isEnumerable) { ret |= VARTYPE.VT_VECTOR; var i = type.GetInterface("IEnumerable`1"); if (i != null) { var args = i.GetGenericArguments(); if (args.Length == 1) elemtype = args[0]; } } if (elemtype.IsNullable()) ret |= VARTYPE.VT_BYREF; if (elemtype == typeof(BLOB)) return ret | VARTYPE.VT_BLOB; if (elemtype == typeof(BStrWrapper)) return ret | VARTYPE.VT_BSTR; if (elemtype == typeof(CLIPDATA)) return ret | VARTYPE.VT_CF; if (elemtype == typeof(Guid)) return ret | VARTYPE.VT_CLSID; if (elemtype == typeof(CurrencyWrapper)) return ret | VARTYPE.VT_CY; if (elemtype == typeof(Win32Error)) return ret | VARTYPE.VT_ERROR; if (elemtype == typeof(FILETIME)) return ret | VARTYPE.VT_FILETIME; if (elemtype == typeof(HRESULT)) return ret | VARTYPE.VT_HRESULT; if (elemtype.IsCOMObject) { var intf = elemtype.GetInterfaces(); if (intf.Contains(typeof(IStream))) return ret | VARTYPE.VT_STREAM; if (intf.Contains(typeof(IStorage))) return ret | VARTYPE.VT_STORAGE; return ret | VARTYPE.VT_UNKNOWN; } if (elemtype == typeof(IntPtr)) return VARTYPE.VT_PTR; switch (Type.GetTypeCode(elemtype)) { case TypeCode.DBNull: return ret | VARTYPE.VT_NULL; case TypeCode.Boolean: return ret | VARTYPE.VT_BOOL; case TypeCode.Char: return ret | VARTYPE.VT_LPWSTR; case TypeCode.SByte: return ret | VARTYPE.VT_I1; case TypeCode.Byte: return ret | VARTYPE.VT_UI1; case TypeCode.Int16: return ret | VARTYPE.VT_I2; case TypeCode.UInt16: return ret | VARTYPE.VT_UI2; case TypeCode.Int32: return ret | VARTYPE.VT_I4; case TypeCode.UInt32: return ret | VARTYPE.VT_UI4; case TypeCode.Int64: return ret | VARTYPE.VT_I8; case TypeCode.UInt64: return ret | VARTYPE.VT_UI8; case TypeCode.Single: return ret | VARTYPE.VT_R4; case TypeCode.Double: return ret | VARTYPE.VT_R8; case TypeCode.Decimal: return type.IsArray ? VARTYPE.VT_VECTOR | VARTYPE.VT_DECIMAL : VARTYPE.VT_DECIMAL | VARTYPE.VT_BYREF; case TypeCode.DateTime: return ret | VARTYPE.VT_DATE; case TypeCode.String: return ret | VARTYPE.VT_LPWSTR; } return ret | VARTYPE.VT_USERDEFINED; } /// /// Frees all elements that can be freed in this instance. For complex elements with known element pointers, the underlying /// elements are freed prior to freeing the containing element. /// [SecurityCritical, SecuritySafeCritical] public void Clear() { switch (vt) { case VARTYPE.VT_VECTOR | VARTYPE.VT_BSTR: foreach (var ptr in _blob.pBlobData.ToIEnum((int)_blob.cbSize)) Marshal.FreeBSTR(Marshal.ReadIntPtr(ptr)); Marshal.FreeCoTaskMem(_blob.pBlobData); break; case VARTYPE.VT_VECTOR | VARTYPE.VT_LPSTR: //foreach (var ptr in _blob.pBlobData.ToIEnum((int)_blob.cbSize)) // Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(ptr)); Marshal.FreeCoTaskMem(_blob.pBlobData); break; case VARTYPE.VT_VECTOR | VARTYPE.VT_I1: case VARTYPE.VT_VECTOR | VARTYPE.VT_R4: case VARTYPE.VT_VECTOR | VARTYPE.VT_CLSID: case VARTYPE.VT_VECTOR | VARTYPE.VT_DECIMAL: case VARTYPE.VT_CF: case VARTYPE.VT_VECTOR | VARTYPE.VT_CF: Marshal.FreeCoTaskMem(_ptr); break; case VARTYPE.VT_UNKNOWN: case VARTYPE.VT_DISPATCH: case VARTYPE.VT_STREAM: case VARTYPE.VT_STREAMED_OBJECT: case VARTYPE.VT_STORAGE: case VARTYPE.VT_STORED_OBJECT: Marshal.Release(_ptr); break; case VARTYPE.VT_VECTOR | VARTYPE.VT_UNKNOWN: case VARTYPE.VT_VECTOR | VARTYPE.VT_DISPATCH: foreach (var iunk in GetVector()) Marshal.Release(iunk); Marshal.FreeCoTaskMem(_ptr); break; case VARTYPE.VT_VECTOR | VARTYPE.VT_VARIANT: foreach (var pv in GetVector()) pv.Dispose(); Marshal.FreeCoTaskMem(_ptr); break; default: PropVariantClear(this); break; } vt = 0; _ulong = 0; } /// Copies the contents of one PROPVARIANT structure to another. /// The cloned copy. public void Clone(out PROPVARIANT clone) { clone = new PROPVARIANT(); PropVariantCopy(clone, this); } /// /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current /// instance precedes, follows, or occurs in the same position in the sort order as the other object. /// /// An object to compare with this instance. /// /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning /// Less than zero This instance precedes in the sort order. Zero This instance occurs in the same /// position in the sort order as . Greater than zero This instance follows in /// the sort order. /// public int CompareTo(object other) { var v = Value; if (other is null) return v == null ? 0 : 1; if (other is PROPVARIANT pv) return PropVariantCompare(this, pv); if (v is null) return -1; var pvDest = new PROPVARIANT(); if (PropVariantChangeType(pvDest, this, PROPVAR_CHANGE_FLAGS.PVCHF_DEFAULT, GetVarType(other.GetType())).Succeeded) return PropVariantCompare(this, pvDest); throw new ArgumentException(@"Unable to compare supplied object to PROPVARIANT.", nameof(other)); } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { Clear(); GC.SuppressFinalize(this); } /// Determines whether the specified , is equal to this instance. /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object obj) => obj is PROPVARIANT pv ? Equals(pv.Value) : obj == this; /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals(PROPVARIANT other) => CompareTo(other) == 0; /// Returns a hash code for this instance. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() => vt.GetHashCode(); /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { string s = null; if (IsVector && Value is IEnumerable ie) s = string.Join(",", ie.Cast().Select(o => o.ToString()).ToArray()); else if (PropVariantToStringAlloc(this, out var str).Succeeded) s = str; return $"{vt}={s ?? "null"}"; } /// Creates a new object that is a copy of the current instance. /// A new object that is a copy of this instance. object ICloneable.Clone() { Clone(out var pv); return pv; } /// Compares the current object with another object of the same type. /// An object to compare with this object. /// /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following /// meanings: Value Meaning Less than zero This object is less than the parameter.Zero This object is /// equal to . Greater than zero This object is greater than . /// int IComparable.CompareTo(PROPVARIANT other) => CompareTo(other); private static IEnumerable ConvertToEnum(object array, Func conv = null) { if (array is null) return null; if (array is IEnumerable iet) return iet; conv ??= (o => (T)Convert.ChangeType(o, typeof(T))); try { var ie = array as IEnumerable; return ie?.Cast().Select(conv) ?? new[] { conv(array) }; } catch { return null; } } private static string GetString(VarEnum ve, IntPtr ptr) { switch ((VARTYPE)ve) { case VARTYPE.VT_LPSTR: return Marshal.PtrToStringAnsi(ptr); case VARTYPE.VT_LPWSTR: return Marshal.PtrToStringUni(ptr); case VARTYPE.VT_BSTR: case VARTYPE.VT_BSTR | VARTYPE.VT_BYREF: return Marshal.PtrToStringBSTR(ptr); default: throw new InvalidCastException("Cannot cast this type to a string."); } } private T? GetRawValue() where T : struct { if (((int)vt <= 1 || (vt & VARTYPE.VT_BYREF) != 0) && _ptr == IntPtr.Zero) return null; var type = typeof(T); if (vt == (VARTYPE.VT_DECIMAL | VARTYPE.VT_BYREF) && type != typeof(decimal)) return null; if (type == typeof(IntPtr)) return (T)(object)_ptr; if (type == typeof(bool)) return (T)(object)((ushort)_ulong != 0); if (type.IsPrimitive) { unsafe { fixed (void* dataPtr = &_ulong) { /*if (vt.IsFlagSet(IntVARTYPE.VT_R4)) try { return (T)Convert.ChangeType((float)Marshal.PtrToStructure(new IntPtr(dataPtr), typeof(float)), typeof(T)); } catch { return null; } if (vt.IsFlagSet(IntVARTYPE.VT_R8)) try { return (T)Convert.ChangeType((double)Marshal.PtrToStructure(new IntPtr(dataPtr), typeof(double)), typeof(T)); } catch { return null; }*/ return new IntPtr(dataPtr).ToStructure(); } } } if (type == typeof(FILETIME)) return (T)(object)_ft; if (type == typeof(BLOB)) return (T)(object)_blob; if (type == typeof(decimal)) return (T)(object)_decimal; return _ptr.ToNullableStructure(); } private IEnumerable GetSafeArray() { if (_ptr == IntPtr.Zero) return null; var sa = new SafeSAFEARRAY(_ptr, false); var dims = SafeArrayGetDim(sa); if (dims != 1) throw new NotSupportedException("Only single-dimensional arrays are supported"); SafeArrayGetLBound(sa, 1U, out var lBound); SafeArrayGetUBound(sa, 1U, out var uBound); // If these are variants, then use Marshal to get them all if (vt.IsFlagSet(VARTYPE.VT_VARIANT)) { using var d = new SafeArrayScopedAccessData(sa); return Marshal.GetObjectsForNativeVariants(d.Data, uBound - lBound + 1); } // Otherwise, pull each element out separately and stuff in an object array else { var ret = new object[uBound - lBound + 1]; var elemSz = SafeArrayGetElemsize(sa); if (elemSz == 0) throw new Win32Exception(); using var mem = new SafeCoTaskMemHandle(elemSz); SafeArrayGetVartype(sa, out var elemVT); var elemType = GetType(elemVT); for (int i = lBound; i <= uBound; i++) { SafeArrayGetElement(sa, i, mem).ThrowIfFailed(); ret[i - lBound] = mem.DangerousGetHandle().Convert(mem.Size, elemType); } return ret; } } private string GetString(VarEnum ve) => GetString(ve, _ptr); private string[] GetStringVector() { var ve = (VarEnum)((int)vt & 0x0FFF); if (ve == VarEnum.VT_LPSTR) return _blob.pBlobData.ToStringEnum((int)_blob.cbSize, CharSet.Ansi).ToArray(); PropVariantToStringVector(this, out var vals).ThrowIfFailed(); return vals; } private object GetValue() { if (vt.IsFlagSet(VARTYPE.VT_ARRAY)) return GetSafeArray(); var isVector = vt.IsFlagSet(VARTYPE.VT_VECTOR); var isRef = vt.IsFlagSet(VARTYPE.VT_BYREF); var elemType = (VARTYPE)((int)vt & 0xFFF); switch (elemType) { case VARTYPE.VT_NULL: return DBNull.Value; case VARTYPE.VT_I1: return isRef ? pcVal : (isVector ? cac : (object)cVal); case VARTYPE.VT_UI1: return isRef ? pbVal : (isVector ? caub : (object)bVal); case VARTYPE.VT_I2: return isRef ? piVal : (isVector ? cai : (object)iVal); case VARTYPE.VT_UI2: return isRef ? puiVal : (isVector ? caui : (object)uiVal); case VARTYPE.VT_INT: case VARTYPE.VT_I4: return isRef ? plVal : (isVector ? cal : (object)lVal); case VARTYPE.VT_UINT: case VARTYPE.VT_UI4: return isRef ? pulVal : (isVector ? caul : (object)ulVal); case VARTYPE.VT_I8: return isRef ? hVal : (isVector ? cah : (object)hVal); case VARTYPE.VT_UI8: return isRef ? uhVal : (isVector ? cauh : (object)uhVal); case VARTYPE.VT_R4: return isRef ? pfltVal : (isVector ? caflt : (object)fltVal); case VARTYPE.VT_R8: return isRef ? dblVal : (isVector ? cadbl : (object)dblVal); case VARTYPE.VT_BOOL: return isRef ? pboolVal : (isVector ? cabool : (object)boolVal); case VARTYPE.VT_ERROR: return isRef ? pscode : (isVector ? cascode : (object)scode); case VARTYPE.VT_HRESULT: return isRef ? (pulVal.HasValue ? new HRESULT(plVal.Value) : (HRESULT?)null) : (isVector ? cal.Select(u => new HRESULT(u)) : (object)new HRESULT(lVal)); case VARTYPE.VT_CY: return isRef ? pcyVal : (isVector ? cacy : (object)cyVal); case VARTYPE.VT_DATE: return isRef ? pdate : (isVector ? cadate : (object)date); case VARTYPE.VT_FILETIME: return isRef ? filetime : (isVector ? cafiletime : (object)filetime); case VARTYPE.VT_CLSID: return isRef ? puuid : (isVector ? cauuid : (object)puuid.GetValueOrDefault()); case VARTYPE.VT_CF: return isRef ? pclipdata : (isVector ? caclipdata : (object)pclipdata); case VARTYPE.VT_BSTR: return isRef ? pbstrVal : (isVector ? cabstr : (object)bstrVal); case VARTYPE.VT_BLOB: return isRef ? _blob : (isVector ? null : (object)_blob); case VARTYPE.VT_LPSTR: return isRef ? pszVal : (isVector ? calpstr : (object)pszVal); case VARTYPE.VT_LPWSTR: return isRef ? pwszVal : (isVector ? calpwstr : (object)pwszVal); case VARTYPE.VT_UNKNOWN: case VARTYPE.VT_DISPATCH: return isVector ? GetVector()?.Select(Marshal.GetObjectForIUnknown) : (isRef ? ppunkVal : punkVal); case VARTYPE.VT_STREAM: case VARTYPE.VT_STREAMED_OBJECT: return isRef ? pStream : (isVector ? null : pStream); case VARTYPE.VT_STORAGE: case VARTYPE.VT_STORED_OBJECT: return isRef ? pStorage : (isVector ? null : pStorage); case VARTYPE.VT_DECIMAL: return isRef ? pdecVal : (isVector ? GetVector() : (object)pdecVal.GetValueOrDefault()); case VARTYPE.VT_VARIANT: return isRef ? pvarVal : (isVector ? GetVector() : (object)pvarVal); case VARTYPE.VT_USERDEFINED: case VARTYPE.VT_RECORD: case VARTYPE.VT_PTR: case VARTYPE.VT_VOID: throw new ArgumentOutOfRangeException(nameof(vt), $"{vt}"); default: return null; } } private IEnumerable GetVector() => vt.IsFlagSet(VARTYPE.VT_VECTOR) ? (_blob.cbSize <= 0 ? new T[0] : _blob.pBlobData.ToArray((int)_blob.cbSize)) : throw new InvalidCastException(); private void SetSafeArray(IList array) { if (array == null || array.Count == 0) return; var psa = SafeArrayCreateVector(VARTYPE.VT_VARIANT, 0, (uint)array.Count); if (psa.IsNull) throw new Win32Exception(); using (var p = new SafeArrayScopedAccessData(psa)) { var elemSz = SafeArrayGetElemsize(psa); if (elemSz == 0) throw new Win32Exception(); for (var i = 0; i < array.Count; ++i) Marshal.GetNativeVariantForObject(array[i], p.Data.Offset(i * elemSz)); } _ptr = psa.DangerousGetHandle(); psa.SetHandleAsInvalid(); } private void SetSafeArray(SafeSAFEARRAY array) { SafeArrayCopy(array, out var myArray).ThrowIfFailed(); _ptr = myArray.DangerousGetHandle(); myArray.SetHandleAsInvalid(); } [SecurityCritical, SecuritySafeCritical] private void SetStringVector(IEnumerable value, VarEnum ve) { if (value == null) throw new ArgumentNullException(nameof(value)); var svt = ((VARTYPE)ve).ClearFlags(VARTYPE.VT_VECTOR); var sc = value.ToArray(); if (sc.Length <= 0) return; switch (svt) { case VARTYPE.VT_BSTR: vt = svt | VARTYPE.VT_VECTOR; _blob.cbSize = (uint)sc.Length; _blob.pBlobData = value.Select(Marshal.StringToBSTR).MarshalToPtr(Marshal.AllocCoTaskMem, out var _); break; case VARTYPE.VT_LPSTR: vt = svt | VARTYPE.VT_VECTOR; _blob.cbSize = (uint)sc.Length; _blob.pBlobData = value.Select(Marshal.StringToCoTaskMemAnsi).MarshalToPtr(Marshal.AllocCoTaskMem, out var _); break; case VARTYPE.VT_LPWSTR: InitPropVariantFromStringVector(sc, (uint)sc.Length, this).ThrowIfFailed(); break; default: break; } } private void SetStruct(T? value, VarEnum ve) where T : struct { if (value.HasValue) { var type = typeof(T); if (type == typeof(IntPtr)) _ptr = (IntPtr)(object)value.Value; else if (type == typeof(bool)) _ptr = (IntPtr)(ushort)((bool)(object)value.Value ? -1 : 0); else if (value.Value.GetType().IsPrimitive) unsafe { fixed (void* ptr = &_ulong) Marshal.StructureToPtr(value.Value, new IntPtr(ptr), true); } else if (type == typeof(FILETIME)) _ft = (FILETIME)(object)value.Value; else if (type == typeof(BLOB)) _blob = (BLOB)(object)value.Value; else throw new ArgumentException($"Unrecognized structure {typeof(T).Name}"); // This would work but there is no means to free this memory. // _ptr = value.Value.StructureToPtr()); } else vt |= VARTYPE.VT_BYREF; } /// Sets the value, clearing any existing value. /// The value. /// /// If this value equals VT_EMPTY, the method will attempt to ascertain the value type from the . /// private void SetValue(object value, VarEnum vEnum = VarEnum.VT_EMPTY) { Clear(); var newVT = vt = vEnum == VarEnum.VT_EMPTY ? GetVarType(value?.GetType()) : (VARTYPE)vEnum; // Finished if NULL or EMPTY if ((int)vt <= 1) return; // Handle SAFEARRAY if (vt.IsFlagSet(VARTYPE.VT_ARRAY)) { if (value is SafeSAFEARRAY sa) { SetSafeArray(sa); SafeArrayGetVartype(sa, out vt); vt |= VARTYPE.VT_ARRAY; } else { SetSafeArray(ConvertToEnum(value).ToList()); vt = VARTYPE.VT_ARRAY | VARTYPE.VT_VARIANT; } return; } var isVector = vt.IsFlagSet(VARTYPE.VT_VECTOR); var isRef = vt.IsFlagSet(VARTYPE.VT_BYREF); // Handle BYREF null value if (isRef && value == null) return; // Handle case where element type is put in w/o specifying VECTOR if (value != null && !isVector && value.GetType().IsArray) vt |= VARTYPE.VT_VECTOR; var sz = 0U; var elemType = (VARTYPE)((int)vt & 0xFFF); switch (elemType) { case VARTYPE.VT_I1: Init(AllocVector); break; case VARTYPE.VT_UI1: Init(InitPropVariantFromBuffer); break; case VARTYPE.VT_I2: Init(InitPropVariantFromInt16Vector); break; case VARTYPE.VT_UI2: Init(InitPropVariantFromUInt16Vector); break; case VARTYPE.VT_I4: case VARTYPE.VT_INT: Init(InitPropVariantFromInt32Vector); vt = newVT; break; case VARTYPE.VT_UI4: case VARTYPE.VT_UINT: Init(InitPropVariantFromUInt32Vector); vt = newVT; break; case VARTYPE.VT_I8: Init(InitPropVariantFromInt64Vector); break; case VARTYPE.VT_UI8: Init(InitPropVariantFromUInt64Vector); break; case VARTYPE.VT_R4: Init(AllocVector); break; case VARTYPE.VT_R8: Init(InitPropVariantFromDoubleVector); break; case VARTYPE.VT_BOOL: Init(InitPropVariantFromBooleanVector); break; case VARTYPE.VT_ERROR: Init(InitPropVariantFromUInt32Vector, o => o is Win32Error err ? (uint)err : (uint)Convert.ChangeType(o, typeof(uint))); vt = newVT; break; case VARTYPE.VT_HRESULT: Init(InitPropVariantFromUInt32Vector, o => o is HRESULT hr ? (uint)(int)hr : (uint)Convert.ChangeType(o, typeof(uint))); vt = newVT; break; case VARTYPE.VT_CY: Init(InitPropVariantFromUInt64Vector, o => o is decimal d ? (ulong)decimal.ToOACurrency(d) : (ulong)Convert.ChangeType(o, typeof(ulong))); vt = newVT; break; case VARTYPE.VT_DATE: double ToDouble(object o) { if (o is DateTime dt) return dt.ToOADate(); if (o is FILETIME ft) return ft.ToDateTime().ToOADate(); return (double)Convert.ChangeType(o, typeof(double)); } Init(InitPropVariantFromDoubleVector, ToDouble); vt = newVT; break; case VARTYPE.VT_FILETIME: FILETIME ToFileTime(object o) { if (o is DateTime dt) return dt.ToFileTimeStruct(); if (o is FILETIME ft) return ft; return FileTimeExtensions.MakeFILETIME((ulong)Convert.ChangeType(o, typeof(ulong))); } Init(InitPropVariantFromFileTimeVector, ToFileTime); break; case VARTYPE.VT_CLSID: if (isVector) AllocVector(GetArray(out sz), sz); else InitPropVariantFromCLSID(((Guid?)value).GetValueOrDefault(), this).ThrowIfFailed(); break; case VARTYPE.VT_CF: if (isVector) AllocVector(GetArray(out sz), sz); else _ptr = ((CLIPDATA)value).MarshalToPtr(Marshal.AllocCoTaskMem, out var _); break; case VARTYPE.VT_BSTR: if (isVector) SetStringVector(ConvertToEnum(value), VarType); else { if (isRef) SetStruct((IntPtr?)value, VarType); else _ptr = Marshal.StringToBSTR(value?.ToString()); } break; case VARTYPE.VT_BLOB: case VARTYPE.VT_BLOB_OBJECT: if (!isVector && !isRef) _blob = (BLOB)value; break; case VARTYPE.VT_LPSTR: if (isVector) SetStringVector(ConvertToEnum(value), VarType); else _ptr = Marshal.StringToCoTaskMemAnsi(value?.ToString()); break; case VARTYPE.VT_LPWSTR: if (isVector) SetStringVector(ConvertToEnum(value), VarType); else _ptr = Marshal.StringToCoTaskMemUni(value?.ToString()); break; case VARTYPE.VT_UNKNOWN: IntPtr ToIUnkPtr(object o) => o as IntPtr? ?? Marshal.GetIUnknownForObject(o); if (isVector) AllocVector(GetArray(out sz, ToIUnkPtr), sz); else SetStruct(ToIUnkPtr(value), VarType); break; #if !(NETSTANDARD2_0) case VARTYPE.VT_DISPATCH: IntPtr ToIDispPtr(object o) => o as IntPtr? ?? Marshal.GetIDispatchForObject(o); if (isVector) AllocVector(GetArray(out sz, ToIDispPtr), sz); else SetStruct(ToIDispPtr(value), VarType); break; #endif case VARTYPE.VT_STREAM: case VARTYPE.VT_STREAMED_OBJECT: if (!isVector && !isRef) SetStruct(Marshal.GetComInterfaceForObject(value, typeof(IStream)), VarType); break; case VARTYPE.VT_STORAGE: case VARTYPE.VT_STORED_OBJECT: if (!isVector && !isRef) SetStruct(Marshal.GetComInterfaceForObject(value, typeof(IStorage)), VarType); break; case VARTYPE.VT_DECIMAL: if (!isVector) { var tempVt = vt; _decimal = ((decimal?)value).GetValueOrDefault(); vt = tempVt; } break; case VARTYPE.VT_VARIANT: if (isVector) AllocVector(GetArray(out sz, o => { ((PROPVARIANT)o).Clone(out var oo); return oo; }), sz); else if (isRef) InitPropVariantVectorFromPropVariant((PROPVARIANT)value, this); break; case VARTYPE.VT_USERDEFINED: case VARTYPE.VT_RECORD: case VARTYPE.VT_VOID: case VARTYPE.VT_PTR: default: throw new ArgumentOutOfRangeException(nameof(Value), $"{vt}={value}"); } HRESULT AllocVector(T[] vector, uint vsz, PROPVARIANT pv = null) { _blob.cbSize = vsz; _blob.pBlobData = vector.MarshalToPtr(global::System.Runtime.InteropServices.Marshal.AllocCoTaskMem, out var _); return HRESULT.S_OK; } T[] GetArray(out uint len, Func conv = null) { var ret = ConvertToEnum(value, conv).ToArray(); len = (uint)ret.Length; return ret; } void Init(Func init, Func conv = null) where T : struct { if (isVector) init(GetArray(out sz, conv), sz, this).ThrowIfFailed(); else SetStruct((T?)(conv?.Invoke(value) ?? value), VarType); } } } /// /// The PROPVARIANT structure is used in the ReadMultiple and WriteMultiple methods of IPropertyStorage to define the type tag and /// the value of a property in a property set. /// /// The PROPVARIANT structure is also used by the GetValue and SetValue methods of IPropertyStore, which replaces /// IPropertySetStorage as the primary way to program item properties in Windows Vista. For more information, see Property Handlers. /// /// /// There are five members. The first member, the value-type tag, and the last member, the value of the property, are significant. /// The middle three members are reserved for future use. /// /// This structure is mostly used for arrays where the fixed structure size is critical for interop. /// [StructLayout(LayoutKind.Sequential, Size = 16, Pack = 8)] public struct PROPVARIANT_IMMUTABLE { /// Value type tag. public VARTYPE vt; /// Reserved for future use. private ushort wReserved1; /// Reserved for future use. private ushort wReserved2; /// Reserved for future use. private ushort wReserved3; /// The value when a numeric value less than 8 bytes. private ulong _ulong; /// Performs an implicit conversion from to . /// The PROPVARIANT instance. /// The resulting instance from the conversion. public static implicit operator PROPVARIANT_IMMUTABLE(PROPVARIANT pv) => new PROPVARIANT_IMMUTABLE { vt = pv.vt, wReserved1 = pv.wReserved1, wReserved2 = pv.wReserved2, wReserved3 = pv.wReserved3, _ulong = pv._ulong }; /// Performs an implicit conversion from to . /// The PROPVARIANT_IMMUTABLE instance. /// The resulting instance from the conversion. public static explicit operator PROPVARIANT(in PROPVARIANT_IMMUTABLE pv) => new PROPVARIANT { vt = pv.vt, wReserved1 = pv.wReserved1, wReserved2 = pv.wReserved2, wReserved3 = pv.wReserved3, _ulong = pv._ulong }; } } }