|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using Windows.Win32.System.Variant;
namespace System.Windows.Forms.ComponentModel.Com2Interop;
/// <summary>
/// Browsing handler for <see cref="IVSMDPerPropertyBrowsing"/>.
/// </summary>
[RequiresUnreferencedCode(ComNativeDescriptor.ComTypeDescriptorsMessage + " Uses reflection to inspect types whose names are not statically known.")]
internal sealed unsafe class Com2IManagedPerPropertyBrowsingHandler : Com2ExtendedBrowsingHandler<IVSMDPerPropertyBrowsing>
{
public override void RegisterEvents(Com2PropertyDescriptor[]? properties)
{
if (properties is null)
{
return;
}
for (int i = 0; i < properties.Length; i++)
{
properties[i].QueryGetDynamicAttributes += OnGetAttributes;
}
}
/// <summary>
/// Here is where we handle IVsPerPropertyBrowsing.GetLocalizedPropertyInfo and IVsPerPropertyBrowsing.
/// Hide properties such as IPerPropertyBrowsing, IProvidePropertyBuilder, etc.
/// </summary>
private void OnGetAttributes(Com2PropertyDescriptor sender, GetAttributesEvent e)
{
using var propertyBrowsing = TryGetComScope(sender.TargetObject, out HRESULT hr);
if (hr.Succeeded)
{
Attribute[] attributes = GetComponentAttributes(propertyBrowsing.Value, sender.DISPID);
for (int i = 0; i < attributes.Length; i++)
{
e.Add(attributes[i]);
}
}
}
internal static Attribute[] GetComponentAttributes(IVSMDPerPropertyBrowsing* propertyBrowsing, int dispid)
{
uint attributeCount = 0;
BSTR* nativeTypeNames = null;
VARIANT* nativeValues = null;
HRESULT hr = propertyBrowsing->GetPropertyAttributes(dispid, &attributeCount, &nativeTypeNames, &nativeValues);
if (hr != HRESULT.S_OK || attributeCount == 0 || nativeValues is null)
{
return [];
}
List<Attribute> attributes = [];
string[] typeNames = GetStringsFromPtr(nativeTypeNames, attributeCount);
object?[] values = GetVariantsFromPtr(nativeValues, attributeCount);
Debug.Assert(typeNames.Length == values.Length, "Mismatched parameter and attribute name length");
if (typeNames.Length != values.Length)
{
return [];
}
// Get the types.
for (int i = 0; i < typeNames.Length; i++)
{
string typeName = typeNames[i];
// Try the name first.
Type? type = null;
if (typeName.Length > 0)
{
type = Type.GetType(typeName);
}
Assembly? assembly = type?.Assembly;
if (type is null)
{
// Couldn't find the type by name, try to parse it as a fully qualified field name
// and look at that field for the type.
string assemblyName = string.Empty;
int comma = typeName.LastIndexOf(',');
if (comma != -1)
{
assemblyName = typeName[comma..];
typeName = typeName[..comma];
}
string fieldName;
int lastDot = typeName.LastIndexOf('.');
if (lastDot != -1)
{
fieldName = typeName[(lastDot + 1)..];
}
else
{
Debug.Fail("No dot in class name?");
continue;
}
// Try to get the field value
type = assembly is null
? Type.GetType(string.Concat(typeName.AsSpan(0, lastDot), assemblyName))
: assembly.GetType(string.Concat(typeName.AsSpan(0, lastDot), assemblyName));
if (type is null)
{
Debug.Fail($"Failed load attribute '{typeName}{assemblyName}'. It's Type could not be found.");
continue;
}
Debug.Assert(typeof(Attribute).IsAssignableFrom(type), $"Attribute type {type.FullName} does not derive from Attribute");
if (!typeof(Attribute).IsAssignableFrom(type))
{
continue;
}
if (type.GetField(fieldName) is { } field && field.IsStatic)
{
if (field.GetValue(null) is Attribute attribute)
{
attributes.Add(attribute);
continue;
}
}
else
{
Debug.Fail($"Couldn't load field '{fieldName}' from type '{typeName[..lastDot]}'. It does not exist or is not static");
}
}
Debug.Assert(typeof(Attribute).IsAssignableFrom(type), $"Attribute type {type.FullName} does not derive from Attribute");
if (!typeof(Attribute).IsAssignableFrom(type))
{
continue;
}
// If we got here, we need to build the attribute.
object? value = values[i];
if (!Convert.IsDBNull(value) && value is not null)
{
// We have an initializer value for a one item constructor.
foreach (var constructor in type.GetConstructors())
{
ParameterInfo[] parameters = constructor.GetParameters();
if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(value.GetType()))
{
// Found a one-parameter ctor, use it to try to construct a default one.
try
{
if (Activator.CreateInstance(type, [value]) is Attribute attribute)
{
attributes.Add(attribute);
}
}
catch
{
Debug.Fail($"Attribute {type.FullName} did not have a initializer specified and has no default constructor");
continue;
}
}
}
}
else
{
// Try to construct a default one.
try
{
if (Activator.CreateInstance(type) is Attribute attribute)
{
attributes.Add(attribute);
}
}
catch
{
Debug.Fail($"Attribute {type.FullName} did not have a initializer specified and has no default constructor");
continue;
}
}
}
return [.. attributes];
}
private static string[] GetStringsFromPtr(BSTR* values, uint count)
{
if (values is null)
{
return [];
}
string[] strings = new string[count];
for (int i = 0; i < count; i++)
{
strings[i] = values[i].ToStringAndFree();
}
try
{
Marshal.FreeCoTaskMem((nint)(void*)values);
}
catch (Exception ex)
{
Debug.Fail("Failed to free BSTR array memory", ex.ToString());
}
return strings;
}
private static unsafe object?[] GetVariantsFromPtr(VARIANT* values, uint count)
{
object?[] objects = new object?[count];
for (int i = 0; i < count; i++)
{
try
{
using VARIANT variant = values[i];
objects[i] = variant.ToObject();
}
catch (Exception ex)
{
Debug.Fail($"Failed to marshal component attribute VARIANT {i}", ex.ToString());
}
}
try
{
Marshal.FreeCoTaskMem((IntPtr)values);
}
catch (Exception ex)
{
Debug.Fail("Failed to free VARIANT array memory", ex.ToString());
}
return objects;
}
}
|