using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.PortableDeviceApi; namespace Vanara.Extensions; /// Extension methods for classes in Vanara.PInvoke.PortableDeviceApi. public static class PortableDeviceExtensions { private const BindingFlags BindStPub = BindingFlags.Static | BindingFlags.Public; private static Dictionary> gReversePKLookup = new(); /// Extracts command results from an for a documented . /// The results from a call to IPortableDevice.SendCommand. /// The command's PROPERTYKEY. /// The type in which is defined, if not other than . /// A dictionary containing the result values. /// Supplied PROPERTYKEY is not a recognized WPD command. public static IReadOnlyDictionary ExtractResults(this IPortableDeviceValues results, in PROPERTYKEY cmd, Type? parentType = null) { if (!cmd.TryGetCommandInfo(out _, out _, out var rAttrs, parentType)) throw new InvalidOperationException("Supplied PROPERTYKEY is not a recognized WPD command."); var ret = new Dictionary(); foreach (var a in rAttrs) { PROPVARIANT pv = new(); try { pv = results.GetValue(a.Property); } catch { } ret.Add(a.Property, pv.Value); } return ret; } /// Sends a command to the device and retrieves the results synchronously. /// The portable device. /// The command's PROPERTYKEY. /// The PROPERTYKEY of the result value to return. /// The value returned in . public static object? SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, in PROPERTYKEY pkResult) => device.SendCommand(cmd).GetValue(pkResult).Value; /// Sends a command to the device and retrieves the results synchronously. /// The portable device. /// The command's PROPERTYKEY. /// /// An action that can optionally be called to manipulate the instance passed to . /// /// The instance returned by . public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, Action? addParams = null) { IPortableDeviceValues cmdParams = new(); cmdParams.SetCommandPKey(cmd); addParams?.Invoke(cmdParams); var cmdResults = device.SendCommand(0, cmdParams); cmdResults.GetErrorValue(WPD_PROPERTY_COMMON_HRESULT).ThrowIfFailed(); return cmdResults; } /// Sends a command to the device and retrieves the results synchronously. /// The portable device. /// The command's PROPERTYKEY. /// /// A list of / tuples representing the property keys and their related values to add /// to . /// /// The instance returned by . public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, params (PROPERTYKEY key, object value)[] addParams) => SendCommand(device, cmd, v => { if (addParams is not null) foreach ((PROPERTYKEY key, object value) in addParams) v.SetValue(key, new PROPVARIANT(value)); }); /// Adds a new enumeration value or overwrites an existing one. /// The type of the enumeration value. /// The instance. /// A PROPERTYKEY that specifies the item to create or overwrite. /// The enum value. /// /// If an existing value has the same key that is specified by the key parameter, it overwrites the existing value without any /// warning. The existing key memory is released appropriately. /// public static void SetEnumValue(this IPortableDeviceValues vals, in PROPERTYKEY key, T enumVal) where T : Enum, IConvertible => vals.SetValue(key, new PROPVARIANT(Convert.ChangeType(enumVal, Enum.GetUnderlyingType(typeof(T))))); /// Tries to get the command information associated with a provided Command PROPERTYKEY. /// The of the WPD command. /// The instance with detail about the command. /// /// An array of instances representing valid parameters for . /// /// /// An array of instances representing valid results for . /// /// The type in which is defined, if not other than . /// /// if is a valid command in ; otherwise. /// /// /// The reflection based lookup is cached so that subsequent lookups are accelerated. As such, expect a slower response the first /// time this method is called with each unique . /// public static bool TryGetCommandInfo(this PROPERTYKEY key, [NotNullWhen(true)] out WPDCommandAttribute? cmd, [NotNullWhen(true)] out WPDCommandParamAttribute[]? param, [NotNullWhen(true)] out WPDCommandResultAttribute[]? result, Type? parentType = null) { var pi = GetPI(key, parentType); parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi); if (pi is not null) { cmd = pi.GetCustomAttribute(); if (cmd is not null) { param = pi.GetCustomAttributes()?.ToArray() ?? new WPDCommandParamAttribute[0]; result = pi.GetCustomAttributes()?.ToArray() ?? new WPDCommandResultAttribute[0]; return true; } } cmd = null; param = null; result = null; return false; } internal static PROPERTYKEY? GetKeyFromName(string keyName, Type? parentType = null) { parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi); var kv = GetDict(parentType).FirstOrDefault(kv => kv.Value.Name == keyName); return kv.Value is null ? null : kv.Key; } private static Dictionary GetDict(Type type) { if (!gReversePKLookup.TryGetValue(type, out var dict)) { dict = type.GetProperties(BindStPub).ToDictionary(m => (PROPERTYKEY)m.GetValue(null, null)!); gReversePKLookup.Add(type, dict); } return dict; } private static PropertyInfo? GetPI(in PROPERTYKEY key, Type? parentType = null) { parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi); return GetDict(parentType).TryGetValue(key, out var pi) ? pi : null; } }