Added extension methods for lookups and SendCommand

pull/256/head
dahall 2021-10-24 15:30:43 -06:00
parent 626650318c
commit 612f854e74
2 changed files with 154 additions and 16 deletions

View File

@ -4686,7 +4686,7 @@ namespace Vanara.PInvoke
/// <summary>Determines whether a PROPERTYKEY represents a command for WPD.</summary>
/// <param name="pk">The PROPERTYKEY value to check.</param>
/// <returns><see langword="true"/> if <paramref name="pk"/> is a WPD command; otherwise, <see langword="false"/>.</returns>
public static bool IsCommandInWpdCommandAccessMap(in PROPERTYKEY pk) => GetWPDConst(pk, "WPD_COMMAND_") is not null;
public static bool IsCommandInWpdCommandAccessMap(in PROPERTYKEY pk) => pk.TryGetCommandInfo(out _, out _, out _);
/// <summary>Verifies that a IO control code is valid for the parameters exposed by an <see cref="IPortableDeviceValues"/> instance.</summary>
/// <param name="ControlCode">The control code.</param>
@ -4707,8 +4707,7 @@ namespace Vanara.PInvoke
try
{
var WpdCommand = pCommandParams.GetCommandPKey();
WPDCommandAttribute value;
if ((value = WpdCommand.GetWPDCommandFlags()) is not null)
if (WpdCommand.TryGetCommandInfo(out var value, out _, out _))
{
switch (value.Access)
{
@ -4743,19 +4742,7 @@ namespace Vanara.PInvoke
return hr.Succeeded && ControlCode != dwExpectedControlCode ? HRESULT.E_INVALIDARG : hr;
}
private static WPDCommandAttribute GetWPDCommandFlags(this PROPERTYKEY pk)
{
var pi = GetWPDConst(pk, "WPD_COMMAND_");
return pi?.GetCustomAttributes<WPDCommandAttribute>().FirstOrDefault();
}
private static System.Reflection.MemberInfo GetWPDConst<T>(T pk, string prefix = null) => typeof(PortableDeviceApi).
GetProperties(BindStPub).
FirstOrDefault(m => m.PropertyType == typeof(T) && (prefix is null || m.Name.StartsWith(prefix)) && pk.Equals(m.GetValue(null, null)));
private static PROPERTYKEY? GetWPDPKey(string propName) => propName is null ? null : (PROPERTYKEY)typeof(PortableDeviceApi).GetProperty(propName, BindStPub)?.GetValue(null, null);
private const System.Reflection.BindingFlags BindStPub = System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public;
private static PROPERTYKEY? GetWPDPKey(string propName) => propName is null ? null : PortableDeviceExtensions.GetKeyFromName(propName);
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class WPDCommandAttribute : Attribute

View File

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.PortableDeviceApi;
namespace Vanara.Extensions
{
/// <summary>Extension methods for classes in Vanara.PInvoke.PortableDeviceApi.</summary>
public static class PortableDeviceExtensions
{
private const BindingFlags BindStPub = BindingFlags.Static | BindingFlags.Public;
private static Dictionary<Type, Dictionary<PROPERTYKEY, PropertyInfo>> gReversePKLookup = new();
/// <summary>Extracts command results from an <see cref="IPortableDeviceValues"/> for a documented <see cref="PROPERTYKEY"/>.</summary>
/// <param name="results">The results from a call to <c>IPortableDevice.SendCommand</c>.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="parentType">The type in which <paramref name="cmd"/> is defined, if not other than <see cref="Vanara.PInvoke.PortableDeviceApi"/>.</param>
/// <returns>A dictionary containing the result values.</returns>
/// <exception cref="System.InvalidOperationException">Supplied PROPERTYKEY is not a recognized WPD command.</exception>
public static IReadOnlyDictionary<PROPERTYKEY, object> 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<PROPERTYKEY, object>();
foreach (var a in rAttrs)
{
PROPVARIANT pv = new();
try { pv = results.GetValue(a.Property); } catch { }
ret.Add(a.Property, pv.Value);
}
return (IReadOnlyDictionary<PROPERTYKEY, object>)ret;
}
/// <summary>Sends a command to the device and retrieves the results synchronously.</summary>
/// <param name="device">The portable device.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="pkResult">The PROPERTYKEY of the result value to return.</param>
/// <returns>The value returned in <paramref name="pkResult"/>.</returns>
public static object SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, in PROPERTYKEY pkResult) =>
device.SendCommand(cmd).GetValue(pkResult).Value;
/// <summary>Sends a command to the device and retrieves the results synchronously.</summary>
/// <param name="device">The portable device.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="addParams">
/// An action that can optionally be called to manipulate the <see cref="IPortableDeviceValues"/> instance passed to <see
/// cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.
/// </param>
/// <returns>The <see cref="IPortableDeviceValues"/> instance returned by <see cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.</returns>
public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, Action<IPortableDeviceValues> 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;
}
/// <summary>Sends a command to the device and retrieves the results synchronously.</summary>
/// <param name="device">The portable device.</param>
/// <param name="cmd">The command's PROPERTYKEY.</param>
/// <param name="addParams">
/// A list of <see cref="PROPERTYKEY"/>/ <see cref="object"/> tuples representing the property keys and their related values to add
/// to <see cref="IPortableDeviceValues"/>.
/// </param>
/// <returns>The <see cref="IPortableDeviceValues"/> instance returned by <see cref="IPortableDevice.SendCommand(uint, IPortableDeviceValues)"/>.</returns>
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));
});
/// <summary>Adds a new enumeration value or overwrites an existing one.</summary>
/// <typeparam name="T">The type of the enumeration value.</typeparam>
/// <param name="vals">The <see cref="IPortableDeviceValues"/> instance.</param>
/// <param name="key">A <c>PROPERTYKEY</c> that specifies the item to create or overwrite.</param>
/// <param name="enumVal">The enum value.</param>
/// <remarks>
/// 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.
/// </remarks>
public static void SetEnumValue<T>(this IPortableDeviceValues vals, in PROPERTYKEY key, T enumVal) where T : Enum, IConvertible =>
vals.SetValue(key, new PROPVARIANT(Convert.ChangeType(enumVal, Enum.GetUnderlyingType(typeof(T)))));
/// <summary>Tries to get the command information associated with a provided Command <c>PROPERTYKEY</c>.</summary>
/// <param name="key">The <see cref="PROPERTYKEY"/> of the WPD command.</param>
/// <param name="cmd">The <see cref="WPDCommandAttribute"/> instance with detail about the command.</param>
/// <param name="param">
/// An array of <see cref="WPDCommandParamAttribute"/> instances representing valid parameters for <paramref name="key"/>.
/// </param>
/// <param name="result">
/// An array of <see cref="WPDCommandResultAttribute"/> instances representing valid results for <paramref name="key"/>.
/// </param>
/// <param name="parentType">The type in which <paramref name="key"/> is defined, if not other than <see cref="Vanara.PInvoke.PortableDeviceApi"/>.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="key"/> is a valid command in <paramref name="parentType"/>; <see langword="false"/> otherwise.
/// </returns>
/// <remarks>
/// 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 <paramref name="parentType"/>.
/// </remarks>
public static bool TryGetCommandInfo(this PROPERTYKEY key, out WPDCommandAttribute cmd, out WPDCommandParamAttribute[] param, out WPDCommandResultAttribute[] result, Type parentType = null)
{
var pi = GetPI(key, parentType);
parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi);
if (pi is not null)
{
cmd = pi.GetCustomAttribute<WPDCommandAttribute>();
if (cmd is not null)
{
param = pi.GetCustomAttributes<WPDCommandParamAttribute>()?.ToArray() ?? new WPDCommandParamAttribute[0];
result = pi.GetCustomAttributes<WPDCommandResultAttribute>()?.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<PROPERTYKEY, PropertyInfo> 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;
}
}
}