Fixed bug in HRESULT type converter and added ability to add values and libs to error message cache

pull/423/head
David Hall 2023-09-07 15:01:05 -06:00
parent 18a005c8a2
commit f968d1aafa
2 changed files with 107 additions and 48 deletions

View File

@ -3,37 +3,80 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Vanara.PInvoke
namespace Vanara.PInvoke;
/// <summary>Gets a static field's name from its value and caches the list for faster lookups.</summary>
public class StaticFieldValueHash
{
/// <summary>Gets a static field's name from its value and caches the list for faster lookups.</summary>
public static class StaticFieldValueHash
private static readonly Dictionary<(Type, Type), IDictionary<int, (string, string)>> cache = new();
/// <summary>Adds the seqence of field values to the associated cache.</summary>
/// <typeparam name="TType">The type of the type.</typeparam>
/// <typeparam name="TFieldType">The type of the field type.</typeparam>
/// <param name="fields">The list of field values and names to add.</param>
/// <param name="lib">The optional library name.</param>
public static void AddFields<TType, TFieldType>(IEnumerable<(TFieldType value, string name)> fields, string lib = null)
where TFieldType : struct, IComparable
{
private static readonly Dictionary<(Type, Type), IDictionary<int, string>> cache = new();
/// <summary>Tries to get the name of a static field from it's value.</summary>
/// <typeparam name="TType">The type of the type.</typeparam>
/// <typeparam name="TFieldType">The type of the field type.</typeparam>
/// <param name="value">The value for which to search.</param>
/// <param name="fieldName">On success, the name of the field.</param>
/// <returns><see langword="true"/> if the value was found, otherwise <see langword="false"/>.</returns>
public static bool TryGetFieldName<TType, TFieldType>(TFieldType value, out string fieldName)
TryGetFieldName<TType, TFieldType>(default, out _); // Load default values
var tt = (typeof(TType), typeof(TFieldType));
if (cache.TryGetValue(tt, out var hash))
{
var tt = (typeof(TType), typeof(TFieldType));
lock (cache)
{
if (!cache.TryGetValue(tt, out var hash))
cache.Add(tt, hash = typeof(TType).GetFields(BindingFlags.Public | BindingFlags.Static).Where(fi => fi.FieldType == typeof(TFieldType)).Distinct(FIValueComp<TFieldType>.Default).ToDictionary(fi => fi.GetValue(null).GetHashCode(), fi => fi.Name));
return hash.TryGetValue(value.GetHashCode(), out fieldName);
}
}
private class FIValueComp<TFieldType> : IEqualityComparer<FieldInfo>
{
bool IEqualityComparer<FieldInfo>.Equals(FieldInfo x, FieldInfo y) => Comparer<TFieldType>.Default.Compare((TFieldType)x.GetValue(null), (TFieldType)y.GetValue(null)) == 0;
int IEqualityComparer<FieldInfo>.GetHashCode(FieldInfo obj) => ((TFieldType)obj.GetValue(null)).GetHashCode();
public static readonly FIValueComp<TFieldType> Default = new();
foreach (var (value, name) in fields)
if (!hash.ContainsKey(value.GetHashCode()))
hash.Add(value.GetHashCode(), (name, lib));
}
}
/// <summary>Adds the seqence of field values to the associated cache.</summary>
/// <typeparam name="TType">The type of the type.</typeparam>
/// <typeparam name="TFieldType">The type of the field type.</typeparam>
/// <typeparam name="TEnum">The type of the enum to added.</typeparam>
/// <param name="lib">The optional library name.</param>
public static void AddFields<TType, TFieldType, TEnum>(string lib = null) where TFieldType : struct, IComparable =>
AddFields<TType, TFieldType>(Enum.GetValues(typeof(TEnum)).Cast<TFieldType>().Select(v => (v, Enum.GetName(typeof(TEnum), v))), lib);
/// <summary>Tries to get the name of a value's library.</summary>
/// <typeparam name="TType">The type of the type.</typeparam>
/// <typeparam name="TFieldType">The type of the field type.</typeparam>
/// <param name="value">The value for which to search.</param>
/// <returns>The name of the library or <see langword="null"/>.</returns>
public static string GetFieldLib<TType, TFieldType>(TFieldType value)
where TFieldType : struct, IComparable
{
var tt = (typeof(TType), typeof(TFieldType));
return cache.TryGetValue(tt, out var hash) && hash.TryGetValue(value.GetHashCode(), out var t) ? t.Item2 : null;
}
/// <summary>Tries to get the name of a static field from it's value.</summary>
/// <typeparam name="TType">The type of the type.</typeparam>
/// <typeparam name="TFieldType">The type of the field type.</typeparam>
/// <param name="value">The value for which to search.</param>
/// <param name="fieldName">On success, the name of the field.</param>
/// <returns><see langword="true"/> if the value was found, otherwise <see langword="false"/>.</returns>
public static bool TryGetFieldName<TType, TFieldType>(TFieldType value, out string fieldName)
where TFieldType : struct, IComparable
{
var tt = (typeof(TType), typeof(TFieldType));
if (!cache.TryGetValue(tt, out var hash))
{
hash = typeof(TType).GetFields(BindingFlags.Public | BindingFlags.Static).
Where(fi => fi.FieldType == typeof(TFieldType)).Distinct(FIValueComp<TFieldType>.Default).
ToDictionary<FieldInfo, int, (string, string)>(fi => fi.GetValue(null)!.GetHashCode(), fi => (fi.Name, null));
cache.Add(tt, hash);
}
var ret = hash.TryGetValue(value.GetHashCode(), out var t);
fieldName = t.Item1;
return ret;
}
private class FIValueComp<TFieldType> : IEqualityComparer<FieldInfo> where TFieldType : struct, IComparable
{
bool IEqualityComparer<FieldInfo>.Equals(FieldInfo x, FieldInfo y) =>
Comparer<TFieldType?>.Default.Compare((TFieldType?)x?.GetValue(null), (TFieldType?)y?.GetValue(null)) == 0;
int IEqualityComparer<FieldInfo>.GetHashCode(FieldInfo obj) => ((TFieldType?)obj.GetValue(null))?.GetHashCode() ?? 0;
public static readonly FIValueComp<TFieldType> Default = new();
}
}

View File

@ -745,16 +745,16 @@ namespace Vanara.PInvoke
// Check for defined HRESULT value
if (!StaticFieldValueHash.TryGetFieldName<HRESULT, int>(_value, out var err) && Facility == FacilityCode.FACILITY_WIN32)
{
foreach (var info2 in typeof(Win32Error).GetFields(BindingFlags.Public | BindingFlags.Static).Where(fi => fi.FieldType == typeof(uint)))
foreach (FieldInfo info2 in typeof(Win32Error).GetFields(BindingFlags.Public | BindingFlags.Static).Where(fi => fi.FieldType == typeof(uint)))
{
if ((HRESULT)(Win32Error)(uint)info2.GetValue(null) == this)
if ((HRESULT)(Win32Error)(uint)info2.GetValue(null)! == this)
{
err = $"HRESULT_FROM_WIN32({info2.Name})";
break;
}
}
}
var msg = FormatMessage(unchecked((uint)_value));
var msg = FormatMessage(unchecked((uint)_value), StaticFieldValueHash.GetFieldLib<HRESULT, int>(_value));
return (err ?? string.Format(CultureInfo.InvariantCulture, "0x{0:X8}", _value)) + (msg == null ? "" : ": " + msg);
}
@ -800,26 +800,42 @@ namespace Vanara.PInvoke
/// <summary>Formats the message.</summary>
/// <param name="id">The error.</param>
/// <returns>The string.</returns>
internal static string FormatMessage(uint id)
internal static string FormatMessage(uint id, string? lib = null)
{
var flags = 0x1200U; // FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM
var buf = new System.Text.StringBuilder(1024);
do
var flags = lib is null ? 0x1200U /*FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM*/ : 0xA00U /*FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE*/;
HINSTANCE hInst = lib is null ? default : LoadLibraryEx(lib, default, 0x1002 /*LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_AS_DATAFILE*/);
var buf = new StringBuilder(1024);
try
{
if (0 != FormatMessage(flags, default, id, 0, buf, (uint)buf.Capacity, default))
return buf.ToString();
var lastError = Win32Error.GetLastError();
if (lastError == Win32Error.ERROR_MR_MID_NOT_FOUND || lastError == Win32Error.ERROR_MUI_FILE_NOT_FOUND)
break;
if (lastError != Win32Error.ERROR_INSUFFICIENT_BUFFER)
lastError.ThrowIfFailed();
buf.Capacity *= 2;
} while (true && buf.Capacity < 1024 * 16); // Don't go crazy
do
{
if (0 != FormatMessage(flags, hInst, id, 0, buf, (uint)buf.Capacity, default))
return buf.ToString();
var lastError = Win32Error.GetLastError();
if (lastError == Win32Error.ERROR_MR_MID_NOT_FOUND || lastError == Win32Error.ERROR_MUI_FILE_NOT_FOUND || lastError == Win32Error.ERROR_RESOURCE_TYPE_NOT_FOUND)
break;
if (lastError != Win32Error.ERROR_INSUFFICIENT_BUFFER)
lastError.ThrowIfFailed();
buf.Capacity *= 2;
} while (true && buf.Capacity < 1024 * 16); // Don't go crazy
}
finally
{
if (hInst != default)
FreeLibrary(hInst);
}
return string.Empty;
}
[DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
private static extern int FormatMessage(uint dwFlags, HINSTANCE lpSource, uint dwMessageId, uint dwLanguageId, System.Text.StringBuilder lpBuffer, uint nSize, IntPtr Arguments);
[DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
static extern int FormatMessage(uint dwFlags, HINSTANCE lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, uint nSize, IntPtr Arguments);
[DllImport(Lib.Kernel32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool FreeLibrary([In] HINSTANCE hLibModule);
[DllImport(Lib.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
static extern HINSTANCE LoadLibraryEx([MarshalAs(UnmanagedType.LPTStr)] string lpLibFileName, HANDLE hFile, uint dwFlags);
}
private static int? ValueFromObj(object obj)
{
@ -845,7 +861,7 @@ namespace Vanara.PInvoke
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType is IErrorProvider || sourceType.IsPrimitive && sourceType != typeof(char))
if (typeof(IErrorProvider).IsAssignableFrom(sourceType) || sourceType.IsPrimitive && sourceType != typeof(char))
return true;
return base.CanConvertFrom(context, sourceType);
}