src/Windows/Avalonia.Win32.Automation/Marshalling/ComVariant.cs (227 lines of code) (raw):

using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Avalonia.Win32.Automation.Marshalling; #if NET7_0_OR_GREATER // Oversimplified ComVariant implementation based on https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ComVariant.cs // Available [StructLayout(LayoutKind.Explicit)] internal struct ComVariant : IDisposable { // VARIANT_BOOL constants. internal const short VARIANT_TRUE = -1; internal const short VARIANT_FALSE = 0; // Most of the data types in the Variant are carried in _typeUnion [FieldOffset(0)] private TypeUnion _typeUnion; [StructLayout(LayoutKind.Sequential)] private struct TypeUnion { public ushort _vt; public ushort _wReserved1; public ushort _wReserved2; public ushort _wReserved3; public UnionTypes _unionTypes; } [StructLayout(LayoutKind.Explicit)] private unsafe struct UnionTypes { [FieldOffset(0)] public sbyte _i1; [FieldOffset(0)] public short _i2; [FieldOffset(0)] public int _i4; [FieldOffset(0)] public long _i8; [FieldOffset(0)] public byte _ui1; [FieldOffset(0)] public ushort _ui2; [FieldOffset(0)] public uint _ui4; [FieldOffset(0)] public ulong _ui8; [FieldOffset(0)] public int _int; [FieldOffset(0)] public uint _uint; [FieldOffset(0)] public short _bool; [FieldOffset(0)] public int _error; [FieldOffset(0)] public float _r4; [FieldOffset(0)] public double _r8; [FieldOffset(0)] public long _cy; [FieldOffset(0)] public double _date; [FieldOffset(0)] public IntPtr _bstr; [FieldOffset(0)] public IntPtr _unknown; [FieldOffset(0)] public IntPtr _dispatch; [FieldOffset(0)] public IntPtr _pvarVal; [FieldOffset(0)] public IntPtr _byref; [FieldOffset(0)] public SafeArrayRef parray; [FieldOffset(0)] public SafeArrayRef*pparray; } /// <summary> /// Release resources owned by this <see cref="ComVariant"/> instance. /// </summary> public void Dispose() { // Re-implement the same clearing semantics as PropVariantClear manually for non-Windows platforms. if (VarType == VarEnum.VT_BSTR) { Marshal.FreeBSTR(_typeUnion._unionTypes._bstr); } else if (VarType.HasFlag(VarEnum.VT_ARRAY)) { _typeUnion._unionTypes.parray.Destroy(); } else if (VarType == VarEnum.VT_UNKNOWN || VarType == VarEnum.VT_DISPATCH) { if (_typeUnion._unionTypes._unknown != IntPtr.Zero) { Marshal.Release(_typeUnion._unionTypes._unknown); } } else if (VarType == VarEnum.VT_LPSTR || VarType == VarEnum.VT_LPWSTR || VarType == VarEnum.VT_CLSID) { Marshal.FreeCoTaskMem(_typeUnion._unionTypes._byref); } else if (VarType == VarEnum.VT_STREAM || VarType == VarEnum.VT_STREAMED_OBJECT || VarType == VarEnum.VT_STORAGE || VarType == VarEnum.VT_STORED_OBJECT) { if (_typeUnion._unionTypes._unknown != IntPtr.Zero) { Marshal.Release(_typeUnion._unionTypes._unknown); } } // Clear out this ComVariant instance. this = default; } /// <summary> /// Create an <see cref="ComVariant"/> instance from the specified value. /// </summary> /// <param name="value">The value to wrap in an <see cref="ComVariant"/>.</param> /// <returns>An <see cref="ComVariant"/> that contains the provided value.</returns> public static unsafe ComVariant Create(object? value) { if (value is null) return Null; Unsafe.SkipInit(out ComVariant variant); if (value.GetType().IsEnum) { var underlyingType = Enum.GetUnderlyingType(value.GetType()); value = Convert.ChangeType(value, underlyingType); } if (value is short) { variant.VarType = VarEnum.VT_I2; variant._typeUnion._unionTypes._i2 = (short)value; } else if (value is int) { variant.VarType = VarEnum.VT_I4; variant._typeUnion._unionTypes._i4 = (int)value; } else if (value is float) { variant.VarType = VarEnum.VT_R4; variant._typeUnion._unionTypes._r4 = (float)value; } else if (value is double) { variant.VarType = VarEnum.VT_R8; variant._typeUnion._unionTypes._r8 = (double)value; } else if (value is DateTime) { variant.VarType = VarEnum.VT_DATE; variant._typeUnion._unionTypes._date = ((DateTime)value).ToOADate(); } else if (value is string) { variant.VarType = VarEnum.VT_BSTR; variant._typeUnion._unionTypes._bstr = Marshal.StringToBSTR((string)value); } else if (value is bool) { // bool values in OLE VARIANTs are VARIANT_BOOL values. variant.VarType = VarEnum.VT_BOOL; variant._typeUnion._unionTypes._bool = ((bool)value) ? VARIANT_TRUE : VARIANT_FALSE; } else if (value is sbyte) { variant.VarType = VarEnum.VT_I1; variant._typeUnion._unionTypes._i1 = (sbyte)value; } else if (value is byte) { variant.VarType = VarEnum.VT_UI1; variant._typeUnion._unionTypes._ui1 = (byte)value; } else if (value is ushort) { variant.VarType = VarEnum.VT_UI2; variant._typeUnion._unionTypes._ui2 = (ushort)value; } else if (value is uint) { variant.VarType = VarEnum.VT_UI4; variant._typeUnion._unionTypes._ui4 = (uint)value; } else if (value is long) { variant.VarType = VarEnum.VT_I8; variant._typeUnion._unionTypes._i8 = (long)value; } else if (value is ulong) { variant.VarType = VarEnum.VT_UI8; variant._typeUnion._unionTypes._ui8 = (ulong)value; } else if (value is IEnumerable list && SafeArrayRef.TryCreate(list, out var array, out var arrayEnum)) { variant.VarType = arrayEnum | VarEnum.VT_ARRAY; variant._typeUnion._unionTypes.parray = array.Value; } else if (ComWrappers.TryGetComInstance(value, out var unknown)) { variant.VarType = VarEnum.VT_UNKNOWN; variant._typeUnion._unionTypes._unknown = unknown; } else { throw new ArgumentException("UnsupportedType", value.GetType().Name); } return variant; } /// <summary> /// A <see cref="ComVariant"/> instance that represents a null value with <see cref="VarEnum.VT_NULL"/> type. /// </summary> public static ComVariant Null { get; } = new() { VarType = VarEnum.VT_NULL }; /// <summary> /// Create a managed value based on the value in the <see cref="ComVariant"/> instance. /// </summary> /// <returns>The managed value contained in this variant.</returns> public readonly unsafe object? AsObject() { if (VarType == VarEnum.VT_EMPTY) { return null; } return VarType switch { VarEnum.VT_NULL => null, // integer VarEnum.VT_I1 => _typeUnion._unionTypes._i1, VarEnum.VT_I2 => _typeUnion._unionTypes._i2, VarEnum.VT_I4 => _typeUnion._unionTypes._i4, VarEnum.VT_I8 => _typeUnion._unionTypes._i8, VarEnum.VT_INT => _typeUnion._unionTypes._i4, VarEnum.VT_ERROR => _typeUnion._unionTypes._i4, // unsigned integer VarEnum.VT_UI1 => _typeUnion._unionTypes._ui1, VarEnum.VT_UI2 => _typeUnion._unionTypes._ui2, VarEnum.VT_UI4 => _typeUnion._unionTypes._ui4, VarEnum.VT_UI8 => _typeUnion._unionTypes._ui8, VarEnum.VT_UINT => _typeUnion._unionTypes._ui4, // floating VarEnum.VT_R4 => _typeUnion._unionTypes._r4, VarEnum.VT_R8 => _typeUnion._unionTypes._r8, // date VarEnum.VT_DATE => DateTime.FromOADate(_typeUnion._unionTypes._date), // string VarEnum.VT_BSTR => Marshal.PtrToStringBSTR(_typeUnion._unionTypes._bstr), // bool VarEnum.VT_BOOL => _typeUnion._unionTypes._bool != VARIANT_FALSE, // unknown VarEnum.VT_UNKNOWN => ComWrappers.TryGetObject(_typeUnion._unionTypes._unknown, out var obj) ? obj : null, // array { } varEnum when varEnum.HasFlag(VarEnum.VT_ARRAY) => (varEnum ^ VarEnum.VT_ARRAY) switch { // integer VarEnum.VT_I1 => SafeArrayRef.ToArray<sbyte>(_typeUnion._unionTypes.parray), VarEnum.VT_I2 => SafeArrayRef.ToArray<short>(_typeUnion._unionTypes.parray), VarEnum.VT_I4 => SafeArrayRef.ToArray<int>(_typeUnion._unionTypes.parray), VarEnum.VT_I8 => SafeArrayRef.ToArray<long>(_typeUnion._unionTypes.parray), VarEnum.VT_INT => SafeArrayRef.ToArray<int>(_typeUnion._unionTypes.parray), // unsigned integer VarEnum.VT_UI1 => SafeArrayRef.ToArray<byte>(_typeUnion._unionTypes.parray), VarEnum.VT_UI2 => SafeArrayRef.ToArray<ushort>(_typeUnion._unionTypes.parray), VarEnum.VT_UI4 => SafeArrayRef.ToArray<uint>(_typeUnion._unionTypes.parray), VarEnum.VT_UI8 => SafeArrayRef.ToArray<ulong>(_typeUnion._unionTypes.parray), VarEnum.VT_UINT => SafeArrayRef.ToArray<uint>(_typeUnion._unionTypes.parray), // floating VarEnum.VT_R4 => SafeArrayRef.ToArray<float>(_typeUnion._unionTypes.parray), VarEnum.VT_R8 => SafeArrayRef.ToArray<double>(_typeUnion._unionTypes.parray), // string VarEnum.VT_BSTR => SafeArrayRef.ToArray<string>(_typeUnion._unionTypes.parray), // variant VarEnum.VT_UNKNOWN => SafeArrayRef.ToArray<IntPtr>(_typeUnion._unionTypes.parray), _ => throw new ArgumentException($"Unknown variant type: {varEnum}") }, _ => throw new ArgumentException($"Unknown variant type: {VarType}") }; } /// <summary> /// The type of the data stored in this <see cref="ComVariant"/>. /// </summary> public VarEnum VarType { readonly get => (VarEnum)_typeUnion._vt; private set => _typeUnion._vt = (ushort)value; } } #endif