Skip to content

Commit

Permalink
Use ITypeInfo directly instead of CreateStdDispatch
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyKuhne committed Nov 8, 2023
1 parent 77c244f commit 6b9e3c4
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 111 deletions.
6 changes: 1 addition & 5 deletions src/thirtytwo/Accessibility/AccessibleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ namespace Windows.Accessibility;
/// Trying to use this class directly will not get picked up by <see cref="CustomComWrappers"/> and will end up
/// with the .NET provided <see cref="IDispatch"/>.
/// </remarks>
public unsafe abstract class AccessibleBase : StandardDispatch, IAccessible.Interface
public unsafe abstract class AccessibleBase : AccessibleDispatch, IAccessible.Interface
{
// https://learn.microsoft.com/windows/win32/winauto/active-accessibility-user-interface-services-dev-guide

// https://learn.microsoft.com/windows/win32/winauto/window
// https://learn.microsoft.com/windows/win32/winauto/client-object

// The accessibility TypeLib- lives in oleacc.dll
private static readonly Guid s_accessibilityTypeLib = new("1ea4dbf0-3c3b-11cf-810c-00aa00389b71");

public static VARIANT Self { get; } = (VARIANT)(int)Interop.CHILDID_SELF;

public static Rectangle InvalidBounds { get; } = new(int.MinValue, int.MinValue, int.MinValue, int.MinValue);
Expand All @@ -36,7 +33,6 @@ public unsafe abstract class AccessibleBase : StandardDispatch, IAccessible.Inte
/// Used to delegate calls to when referring to a child id other than <see cref="Interop.CHILDID_SELF"/>.
/// </param>
public AccessibleBase(IAccessible.Interface? childHandler = default)
: base(s_accessibilityTypeLib, 1, 1, IAccessible.IID_Guid)
{
_childHandler = childHandler;
}
Expand Down
21 changes: 21 additions & 0 deletions src/thirtytwo/Accessibility/AccessibleDispatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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 Windows.Win32.System.Com;
using Windows.Win32.UI.Accessibility;

namespace Windows.Accessibility;

/// <summary>
/// Base <see cref="IDispatch"/> class for <see cref="IAccessible"/>.
/// </summary>
public unsafe abstract class AccessibleDispatch : StandardDispatch<IAccessible>
{
// The accessibility TypeLib- lives in oleacc.dll
private static readonly Guid s_accessibilityTypeLib = new("1ea4dbf0-3c3b-11cf-810c-00aa00389b71");

// We don't release the ITypeInfo to avoid unloading and reloading the IAccessible ITypeLib.
private static ITypeInfo* TypeInfo { get; } = ComHelpers.GetRegisteredTypeInfo(s_accessibilityTypeLib, 1, 1, IAccessible.IID_Guid);

public AccessibleDispatch() : base(TypeInfo) { }
}
2 changes: 1 addition & 1 deletion src/thirtytwo/DotNet/DotNetDispatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Windows.DotNet;
/// just documenting the .NET CCW behavior.
/// </summary>
public unsafe abstract class DotNetDispatch :
StandardDispatch,
UnknownDispatch,
ISupportErrorInfo.Interface
{
// .NET CCWs always support IMarshal, ISupportErrorInfo, IDispatchEx, IProvideClassInfo, and
Expand Down
27 changes: 27 additions & 0 deletions src/thirtytwo/Win32/ComHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,31 @@ static partial void PopulateIUnknownImpl<TComInterface>(IUnknown.Vtbl* vtable) w
// interface, such as typeof(IAccessible).
CustomComWrappers.PopulateIUnknown(vtable);
}

/// <summary>
/// Find the given interface's <see cref="ITypeInfo"/> from the specified type library.
/// </summary>
public static ComScope<ITypeInfo> GetRegisteredTypeInfo(
Guid typeLibrary,
ushort majorVersion,
ushort minorVersion,
Guid interfaceId)
{
// Load the registered type library and get the relevant ITypeInfo for the specified interface.
//
// Note that the ITypeLib and ITypeInfo are free to be used on any thread. ITypeInfo add refs the
// ITypeLib and keeps a reference to it.
//
// While type library loading is cached, that is only while it is still referenced (directly or via
// an ITypeInfo reference) and there is still a fair amount of overhead to look up the right instance. The
// caching is by the type library path, so the guid needs looked up again in the registry to figure out the
// path again.
using ComScope<ITypeLib> typelib = new(null);
HRESULT hr = Interop.LoadRegTypeLib(typeLibrary, majorVersion, minorVersion, 0, typelib);
hr.ThrowOnFailure();

ComScope<ITypeInfo> typeInfo = new(null);
typelib.Value->GetTypeInfoOfGuid(interfaceId, typeInfo).ThrowOnFailure();
return typeInfo;
}
}
5 changes: 0 additions & 5 deletions src/thirtytwo/Win32/System/Com/CustomComWrappers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ internal static void PopulateIUnknown(IUnknown.Vtbl* vtable)
CreateComInterfaceFlags.None);
#endif

if (obj is IWrapperInitialize initialize)
{
initialize.OnInitialized(result);
}

return result;
}

Expand Down
13 changes: 0 additions & 13 deletions src/thirtytwo/Win32/System/Com/IWrapperInitialize.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Windows.Win32.System.Com;
/// <summary>
/// Base class that provides an <see cref="IDispatchEx"/> projection of all it's public properties.
/// </summary>
public unsafe abstract class ReflectPropertiesDispatch : StandardDispatch
public unsafe abstract class ReflectPropertiesDispatch : UnknownDispatch
{
private readonly ClassPropertyDispatchAdapter _dispatchAdapter;

Expand Down
144 changes: 66 additions & 78 deletions src/thirtytwo/Win32/System/Com/StandardDispatch.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// 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.Reflection;
using Windows.Win32.System.Ole;
using Windows.Win32.System.Variant;

Expand All @@ -11,93 +10,78 @@ namespace Windows.Win32.System.Com;
/// Base class for providing <see cref="IDispatch"/> services through a standard dispatch implementation
/// generated from a type library.
/// </summary>
public unsafe abstract class StandardDispatch : IDispatch.Interface, IDispatchEx.Interface, IWrapperInitialize, IDisposable
public unsafe abstract class StandardDispatch<T> : IDispatch.Interface, IDispatchEx.Interface, IDisposable
where T : unmanaged, IComIID
{
private readonly Guid _typeLibrary;
private readonly ushort _majorVersion;
private readonly ushort _minorVersion;
private readonly Guid _interfaceId;
private AgileComPointer<IDispatch>? _standardDispatch;

// StdOle32.tlb
private static readonly Guid s_stdole = new("00020430-0000-0000-C000-000000000046");
private ITypeInfo* _typeInfo;

/// <summary>
/// Construct an <see cref="IUnknown"/> instance. This is useful as a replacement for types exposed as
/// <see cref="IDispatch"/> and <see cref="IDispatchEx"/> purely through <see cref="IReflect"/>.
/// Construct a new instance with the specified backing <see cref="ITypeInfo"/>.
/// </summary>
public StandardDispatch() : this(s_stdole, 2, 0, IUnknown.IID_Guid)
public StandardDispatch(ITypeInfo* typeInfo)
{
}
if (typeInfo is null)
{
throw new ArgumentNullException(nameof(typeInfo));
}

/// <summary>
/// Construct a new instance from a registered type library.
/// </summary>
/// <param name="typeLibrary"><see cref="Guid"/> for the registered type library.</param>
/// <param name="majorVersion">Type library major version.</param>
/// <param name="minorVersion">Type library minor version.</param>
/// <param name="interfaceId">The <see cref="Guid"/> for the interface the derived class presents.</param>
public StandardDispatch(
Guid typeLibrary,
ushort majorVersion,
ushort minorVersion,
Guid interfaceId)
{
_typeLibrary = typeLibrary;
_majorVersion = majorVersion;
_minorVersion = minorVersion;
_interfaceId = interfaceId;
}
#if DEBUG
typeInfo->GetTypeAttr(out TYPEATTR* typeAttributes).ThrowOnFailure();
try
{
if (typeAttributes->guid != T.Guid)
{
throw new ArgumentException("Interface guid doesn't match type info", nameof(typeInfo));
}
}
finally
{
typeInfo->ReleaseTypeAttr(typeAttributes);
}
#endif

void IWrapperInitialize.OnInitialized(IUnknown* unknown)
{
// Load the registered type library and get the relevant ITypeInfo for the specified interface.
using ComScope<ITypeLib> typelib = new(null);
HRESULT hr = Interop.LoadRegTypeLib(_typeLibrary, _majorVersion, _minorVersion, 0, typelib);
hr.ThrowOnFailure();

using ComScope<ITypeInfo> typeinfo = new(null);
typelib.Value->GetTypeInfoOfGuid(_interfaceId, typeinfo);

// The unknown we get is a wrapper unknown.
unknown->QueryInterface(_interfaceId, out void* instance).ThrowOnFailure();
IUnknown* standard = default;
Interop.CreateStdDispatch(
unknown,
instance,
typeinfo.Value,
&standard).ThrowOnFailure();

_standardDispatch = new AgileComPointer<IDispatch>((IDispatch*)standard, takeOwnership: true);
_typeInfo = typeInfo;
_typeInfo->AddRef();
}

/// <summary>
/// Get the standard dispatch interface.
/// </summary>
private ComScope<IDispatch> Dispatch =>
_standardDispatch is not { } standardDispatch
? throw new InvalidOperationException()
: standardDispatch.GetInterface();

HRESULT IDispatch.Interface.GetTypeInfoCount(uint* pctinfo)
{
using var dispatch = Dispatch;
dispatch.Value->GetTypeInfoCount(pctinfo);
if (pctinfo is null)
{
return HRESULT.E_POINTER;
}

*pctinfo = 1;
return HRESULT.S_OK;
}

HRESULT IDispatch.Interface.GetTypeInfo(uint iTInfo, uint lcid, ITypeInfo** ppTInfo)
{
using var dispatch = Dispatch;
dispatch.Value->GetTypeInfo(iTInfo, lcid, ppTInfo);
if (ppTInfo is null)
{
return HRESULT.E_POINTER;
}

if (iTInfo != 0)
{
*ppTInfo = null;
return HRESULT.DISP_E_BADINDEX;
}

_typeInfo->AddRef();
*ppTInfo = _typeInfo;
return HRESULT.S_OK;
}

HRESULT IDispatch.Interface.GetIDsOfNames(Guid* riid, PWSTR* rgszNames, uint cNames, uint lcid, int* rgDispId)
{
using var dispatch = Dispatch;
dispatch.Value->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId);
return HRESULT.S_OK;
// This must bee IID_NULL
if (riid != IID.Empty())
{
return HRESULT.DISP_E_UNKNOWNINTERFACE;
}

return _typeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);
}

HRESULT IDispatch.Interface.Invoke(
Expand All @@ -110,6 +94,12 @@ HRESULT IDispatch.Interface.Invoke(
EXCEPINFO* pExcepInfo,
uint* pArgErr)
{
// This must bee IID_NULL
if (riid != IID.Empty())
{
return HRESULT.DISP_E_UNKNOWNINTERFACE;
}

HRESULT hr = MapDotNetHRESULTs(Invoke(
dispIdMember,
lcid,
Expand All @@ -125,10 +115,9 @@ HRESULT IDispatch.Interface.Invoke(
return hr;
}

// The override couldn't find it, pass it along to the standard dispatch.
using var dispatch = Dispatch;
hr = dispatch.Value->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, pArgErr);
return hr;
// The override couldn't find it, pass it along via the ITypeInfo.
using ComScope<T> @interface = new(ComHelpers.GetComPointer<T>(this));
return _typeInfo->Invoke(@interface, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, pArgErr);
}

HRESULT IDispatchEx.Interface.GetDispID(BSTR bstrName, uint grfdex, int* pid)
Expand Down Expand Up @@ -179,10 +168,9 @@ HRESULT IDispatchEx.Interface.InvokeEx(
return hr;
}

// The override couldn't find it, pass it along to the standard dispatch.
using var dispatch = Dispatch;
hr = dispatch.Value->Invoke(id, IID.Empty(), lcid, (DISPATCH_FLAGS)wFlags, pdp, pvarRes, pei, puArgErr: null);
return hr;
// The override couldn't find it, pass it along via the ITypeInfo.
using ComScope<T> @interface = new(ComHelpers.GetComPointer<T>(this));
return _typeInfo->Invoke(@interface, id, (DISPATCH_FLAGS)wFlags, pdp, pvarRes, pei, puArgErr: null);
}

protected virtual HRESULT Invoke(
Expand Down Expand Up @@ -281,10 +269,10 @@ private static HRESULT MapDotNetHRESULTs(HRESULT hr)

protected virtual void Dispose(bool disposing)
{
if (disposing)
if (_typeInfo is not null)
{
_standardDispatch?.Dispose();
_standardDispatch = null;
_typeInfo->Release();
_typeInfo = null;
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/thirtytwo/Win32/System/Com/UnknownDispatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Windows.Win32.System.Com;

/// <summary>
/// Base <see cref="IDispatch"/> class for <see cref="IUnknown"/>.
/// </summary>
public unsafe abstract class UnknownDispatch : StandardDispatch<IUnknown>
{
// StdOle32.tlb
private static readonly Guid s_stdole = new("00020430-0000-0000-C000-000000000046");

// We don't release the ITypeInfo to avoid unloading and reloading the standard OLE ITypeLib.
private static ITypeInfo* TypeInfo { get; } = ComHelpers.GetRegisteredTypeInfo(s_stdole, 2, 0, IUnknown.IID_Guid);

public UnknownDispatch() : base(TypeInfo) { }
}
10 changes: 2 additions & 8 deletions src/thirtytwo_tests/Win32/System/Com/StandardDispatchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class OleWindow : IOleWindow.Interface, IManagedWrapper<IOleWindow>
[Fact]
public void StandardDispatch_IUnknown()
{
UnknownDispatch unknownDispatch = new();
SimpleDispatch unknownDispatch = new();
using ComScope<IDispatch> dispatch = new(ComHelpers.GetComPointer<IDispatch>(unknownDispatch));
using ComScope<IDispatchEx> dispatchEx = dispatch.TryQueryInterface<IDispatchEx>(out HRESULT hr);

Expand Down Expand Up @@ -125,13 +125,7 @@ public void StandardDispatch_IUnknown()
name.ToStringAndFree().Should().Be("QueryInterface");
}

private class UnknownDispatch : StandardDispatch, IManagedWrapper<IDispatch>
private class SimpleDispatch : UnknownDispatch, IManagedWrapper<IDispatch>
{
// Comes from stdole32.tlb
private static readonly Guid s_stdole = new("00020430-0000-0000-C000-000000000046");

public UnknownDispatch() : base(s_stdole, 2, 0, IUnknown.IID_Guid)
{
}
}
}

0 comments on commit 6b9e3c4

Please sign in to comment.