Skip to content

Commit

Permalink
Start of event handling in ComTypeDescriptor
Browse files Browse the repository at this point in the history
Parses events and builds EventDescriptors for callbacks that don't return a value. Funcs aren't handled yet and there isn't a way to hook the events yet.
  • Loading branch information
JeremyKuhne committed Feb 3, 2024
1 parent 33dce14 commit cf5ee95
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 26 deletions.
104 changes: 104 additions & 0 deletions src/thirtytwo/Win32/System/Com/ComEventDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.ComponentModel;
using Windows.Win32.System.Variant;

namespace Windows.Win32.System.Com;

internal unsafe class ComEventDescriptor : EventDescriptor
{
private readonly Type _eventType;
private readonly string _name;
private readonly string _description;
private readonly string[] _parameterNames;

public ComEventDescriptor(
string name,
int dispatchId,
Guid interfaceId,
string description,
string[] parameterNames,
Type eventType,
Attribute[]? attrs) : base(name, attrs)
{
_eventType = eventType;
_name = name;
_description = description;
_parameterNames = parameterNames;
DispatchId = dispatchId;
InterfaceId = interfaceId;
}

public ReadOnlySpan<string> ParameterNames => _parameterNames;
public int DispatchId { get; }
public Guid InterfaceId { get; }

public override Type ComponentType => typeof(IComPointer);
public override Type EventType => _eventType;
public override bool IsMulticast => false;
public override string DisplayName => _name;
public override string Description => _description;

// In order for this to work without creating a separate connection point for every event we would need to
// take a factory in the constructor that the ComTypeDescriptor would host to create the connection point.
public override void AddEventHandler(object component, Delegate value) => throw new NotImplementedException();
public override void RemoveEventHandler(object component, Delegate value) => throw new NotImplementedException();

public static Type? GetDelegateType(ITypeInfo* typeInfo, FUNCDESC* description)
{
if (description->funckind != FUNCKIND.FUNC_DISPATCH
|| description->callconv != CALLCONV.CC_STDCALL
|| !description->invkind.HasFlag(INVOKEKIND.INVOKE_FUNC))
{
return null;
}

if (description->elemdescFunc.tdesc.vt == VARENUM.VT_VOID)
{
// Action
switch (description->cParams)
{
case 0:
return typeof(Action);
case 1:
{
Type? parameter1 = VARIANT.GetManagedType(description->lprgelemdescParam[0].tdesc.vt);
return parameter1 is null ? null : typeof(Action<>).MakeGenericType(parameter1);
}

case 2:
{
Type? parameter1 = VARIANT.GetManagedType(description->lprgelemdescParam[0].tdesc.vt);
Type? parameter2 = VARIANT.GetManagedType(description->lprgelemdescParam[1].tdesc.vt);
return parameter1 is null || parameter2 is null
? null
: typeof(Action<,>).MakeGenericType(parameter1, parameter2);
}

case 3:
{
Type? parameter1 = VARIANT.GetManagedType(description->lprgelemdescParam[0].tdesc.vt);
Type? parameter2 = VARIANT.GetManagedType(description->lprgelemdescParam[1].tdesc.vt);
Type? parameter3 = VARIANT.GetManagedType(description->lprgelemdescParam[2].tdesc.vt);
return parameter1 is null || parameter2 is null || parameter3 is null
? null
: typeof(Action<,,>).MakeGenericType(parameter1, parameter2, parameter3);
}

case 4:
{
Type? parameter1 = VARIANT.GetManagedType(description->lprgelemdescParam[0].tdesc.vt);
Type? parameter2 = VARIANT.GetManagedType(description->lprgelemdescParam[1].tdesc.vt);
Type? parameter3 = VARIANT.GetManagedType(description->lprgelemdescParam[2].tdesc.vt);
Type? parameter4 = VARIANT.GetManagedType(description->lprgelemdescParam[3].tdesc.vt);
return parameter1 is null || parameter2 is null || parameter3 is null || parameter4 is null
? null
: typeof(Action<,,,>).MakeGenericType(parameter1, parameter2, parameter3, parameter4);
}
}
}

return null;
}
}
7 changes: 7 additions & 0 deletions src/thirtytwo/Win32/System/Com/ComPropertyDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

namespace Windows.Win32.System.Com;

/// <summary>
/// Maps a COM property to a managed property.
/// </summary>
internal unsafe class ComPropertyDescriptor : PropertyDescriptor
{
private readonly bool _readOnly;
Expand All @@ -31,12 +34,16 @@ public ComPropertyDescriptor(
_variantType = variantType;
}

/// <summary>
/// Returns true if the given type is supported by this descriptor.
/// </summary>
internal static bool IsSupportedType(VARENUM type) => GetManagedType(type) is not null;

private static Type? GetManagedType(VARENUM type) => type switch
{
VARENUM.VT_BSTR => typeof(string),
VARENUM.VT_BOOL => typeof(bool),
VARENUM.VT_INT or VARENUM.VT_I4 => typeof(int),
_ => null,
};

Expand Down
176 changes: 150 additions & 26 deletions src/thirtytwo/Win32/System/Com/ComTypeDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal unsafe sealed class ComTypeDescriptor : ICustomTypeDescriptor
private string? _className;
private readonly IComPointer _comObject;
private List<PropertyDescriptor>? _properties;
private List<EventDescriptor>? _events;

public ComTypeDescriptor(IComPointer comObject)
{
Expand Down Expand Up @@ -87,7 +88,112 @@ public ComTypeDescriptor(IComPointer comObject)
EventDescriptor? ICustomTypeDescriptor.GetDefaultEvent() => throw new NotImplementedException();
PropertyDescriptor? ICustomTypeDescriptor.GetDefaultProperty() => throw new NotImplementedException();
object? ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null;
EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => throw new NotImplementedException();

EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
InitializeEventDescriptors();
return new([.. _events]);
}

[MemberNotNull(nameof(_events))]
private void InitializeEventDescriptors()
{
if (_events is not null)
{
return;
}

_events = [];

using var typeInfo = GetObjectTypeInfo();
if (typeInfo.IsNull)
{
return;
}

using ComScope<ITypeLib> typeLib = new(null);
uint typeIndex;
HRESULT hr = typeInfo.Value->GetContainingTypeLib(typeLib, &typeIndex);
if (hr.Failed)
{
return;
}

using var container = _comObject.TryGetInterface<IConnectionPointContainer>(out hr);
if (hr.Failed)
{
return;
}

using ComScope<IEnumConnectionPoints> enumerator = new(null);
container.Value->EnumConnectionPoints(enumerator);
if (hr.Failed)
{
return;
}

uint count;
IConnectionPoint* connectionPoint = null;
while (enumerator.Value->Next(1u, &connectionPoint, &count).Succeeded && count == 1)
{
using ComScope<IConnectionPoint> scope = new(connectionPoint);
Guid connectionId;
hr = connectionPoint->GetConnectionInterface(&connectionId);
if (hr.Failed)
{
continue;
}

using ComScope<ITypeInfo> eventTypeInfo = new(null);
hr = typeLib.Value->GetTypeInfoOfGuid(connectionId, eventTypeInfo);
if (hr.Failed)
{
continue;
}

using var typeAttr = eventTypeInfo.Value->GetTypeAttr(out hr);
if (hr.Failed
|| typeAttr.Value->typekind != TYPEKIND.TKIND_DISPATCH
|| ((TYPEFLAGS)typeAttr.Value->wTypeFlags).HasFlag(TYPEFLAGS.TYPEFLAG_FDUAL))
{
// We only handle IDispatch interfaces
continue;
}

using BSTR name = default;
hr = typeInfo.Value->GetDocumentation(Interop.MEMBERID_NIL, &name, null, null, null);
if (hr.Failed)
{
continue;
}

string interfaceName = name.ToString();
Guid interfaceGuid = connectionId;

EnumerateFunctionDescriptions(eventTypeInfo, HandleFunction);

void HandleFunction(ITypeInfo* typeInfo, FUNCDESC* description, ReadOnlySpan<BSTR> names)
{
if (ComEventDescriptor.GetDelegateType(typeInfo, description) is not Type delegateType)
{
return;
}

using BSTR documentation = default;
HRESULT hr = typeInfo->GetDocumentation(description->memid, null, &documentation, out _, null);

_events.Add(new ComEventDescriptor(
names[0].ToString(),
description->memid,
interfaceGuid,
documentation.ToString(),
names[1..].ToStringArray(),
delegateType,
attrs: null));
}
}
}

EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[]? attributes) => throw new NotImplementedException();

PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
Expand All @@ -96,11 +202,12 @@ PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
return new([.. _properties]);
}

PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes) => throw new NotImplementedException();
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes) =>
((ICustomTypeDescriptor)this).GetProperties();

object? ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor? pd) => _comObject;

private ComScope<ITypeInfo> GetObjectTypeInfo(bool preferIProvideClassInfo)
private ComScope<ITypeInfo> GetObjectTypeInfo(bool preferIProvideClassInfo = false)
{
if (preferIProvideClassInfo)
{
Expand Down Expand Up @@ -150,53 +257,70 @@ ComScope<ITypeInfo> FromIProvideClassInfo()
}
}

[MemberNotNull(nameof(_properties))]
private void InitializePropertyDescriptors()
{
if (_properties is not null)
{
return;
}

_properties = [];
private delegate void EnumerateFunctionDescriptionDelegate(ITypeInfo* typeInfo, FUNCDESC* function, ReadOnlySpan<BSTR> names);

using var typeInfo = GetObjectTypeInfo(preferIProvideClassInfo: false);
if (typeInfo.IsNull)
private void EnumerateFunctionDescriptions(ITypeInfo* typeInfo, EnumerateFunctionDescriptionDelegate func)
{
if (typeInfo == null)
{
return;
}

TYPEATTR* ta;
if (typeInfo.Value->GetTypeAttr(&ta).Failed)
if (typeInfo->GetTypeAttr(&ta).Failed)
{
return;
}

TYPEATTR typeAttributes = *ta;
typeInfo.Value->ReleaseTypeAttr(ta);
Dictionary<int, PropertyInfo> propertyInfo = [];
typeInfo->ReleaseTypeAttr(ta);

for (int i = 0; i < typeAttributes.cFuncs; i++)
{
FUNCDESC* function;
HRESULT hr = typeInfo.Value->GetFuncDesc((uint)i, &function);
HRESULT hr = typeInfo->GetFuncDesc((uint)i, &function);
if (hr.Failed)
{
continue;
}

try
{
using BSTR name = default;
uint count;
hr = typeInfo.Value->GetNames(function->memid, &name, 1u, &count);
ProcessFunction(function, name);
uint count = (uint)function->cParams + 1u;
using BstrBuffer names = new((int)count);
hr = typeInfo->GetNames(function->memid, names, count, &count);
if (hr.Failed)
{
return;
}

func(typeInfo, function, names[..(int)count]);
}
finally
{
typeInfo.Value->ReleaseFuncDesc(function);
typeInfo->ReleaseFuncDesc(function);
}
}
}

[MemberNotNull(nameof(_properties))]
private void InitializePropertyDescriptors()
{
if (_properties is not null)
{
return;
}

_properties = [];

using var typeInfo = GetObjectTypeInfo();
if (typeInfo.IsNull)
{
return;
}

Dictionary<int, PropertyInfo> propertyInfo = [];
EnumerateFunctionDescriptions(typeInfo, ProcessFunction);

foreach (PropertyInfo property in propertyInfo.Values)
{
Expand All @@ -208,7 +332,7 @@ private void InitializePropertyDescriptors()
attrs: null));
}

void ProcessFunction(FUNCDESC* function, BSTR name)
void ProcessFunction(ITypeInfo* typeInfo, FUNCDESC* function, ReadOnlySpan<BSTR> names)
{
propertyInfo.TryGetValue(function->memid, out PropertyInfo info);
VARENUM type = VARENUM.VT_EMPTY;
Expand Down Expand Up @@ -241,9 +365,9 @@ void ProcessFunction(FUNCDESC* function, BSTR name)
info.Type = type;
if (info.Name is null)
{
info.Name = name.ToString();
info.Name = names[0].ToString();
}
else if (!name.AsSpan().SequenceEqual(info.Name))
else if (!names[0].AsSpan().SequenceEqual(info.Name))
{
throw new NotSupportedException("Mismatched put/get type.");
}
Expand Down
Loading

0 comments on commit cf5ee95

Please sign in to comment.