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 };
}
}
}