diff --git a/src/thirtytwo/Accessibility/AccessibleBase.cs b/src/thirtytwo/Accessibility/AccessibleBase.cs index 581ab99..335dccb 100644 --- a/src/thirtytwo/Accessibility/AccessibleBase.cs +++ b/src/thirtytwo/Accessibility/AccessibleBase.cs @@ -16,16 +16,13 @@ namespace Windows.Accessibility; /// Trying to use this class directly will not get picked up by and will end up /// with the .NET provided . /// -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); @@ -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 . /// public AccessibleBase(IAccessible.Interface? childHandler = default) - : base(s_accessibilityTypeLib, 1, 1, IAccessible.IID_Guid) { _childHandler = childHandler; } diff --git a/src/thirtytwo/Accessibility/AccessibleDispatch.cs b/src/thirtytwo/Accessibility/AccessibleDispatch.cs new file mode 100644 index 0000000..fcb8524 --- /dev/null +++ b/src/thirtytwo/Accessibility/AccessibleDispatch.cs @@ -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; + +/// +/// Base class for . +/// +public unsafe abstract class AccessibleDispatch : StandardDispatch +{ + // 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) { } +} \ No newline at end of file diff --git a/src/thirtytwo/DotNet/DotNetDispatch.cs b/src/thirtytwo/DotNet/DotNetDispatch.cs index 8e6a789..2f76ab8 100644 --- a/src/thirtytwo/DotNet/DotNetDispatch.cs +++ b/src/thirtytwo/DotNet/DotNetDispatch.cs @@ -10,7 +10,7 @@ namespace Windows.DotNet; /// just documenting the .NET CCW behavior. /// public unsafe abstract class DotNetDispatch : - StandardDispatch, + UnknownDispatch, ISupportErrorInfo.Interface { // .NET CCWs always support IMarshal, ISupportErrorInfo, IDispatchEx, IProvideClassInfo, and diff --git a/src/thirtytwo/Win32/ComHelpers.cs b/src/thirtytwo/Win32/ComHelpers.cs index 50c8daa..f55305f 100644 --- a/src/thirtytwo/Win32/ComHelpers.cs +++ b/src/thirtytwo/Win32/ComHelpers.cs @@ -179,4 +179,31 @@ static partial void PopulateIUnknownImpl(IUnknown.Vtbl* vtable) w // interface, such as typeof(IAccessible). CustomComWrappers.PopulateIUnknown(vtable); } + + /// + /// Find the given interface's from the specified type library. + /// + public static ComScope 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 typelib = new(null); + HRESULT hr = Interop.LoadRegTypeLib(typeLibrary, majorVersion, minorVersion, 0, typelib); + hr.ThrowOnFailure(); + + ComScope typeInfo = new(null); + typelib.Value->GetTypeInfoOfGuid(interfaceId, typeInfo).ThrowOnFailure(); + return typeInfo; + } } \ No newline at end of file diff --git a/src/thirtytwo/Win32/System/Com/CustomComWrappers.cs b/src/thirtytwo/Win32/System/Com/CustomComWrappers.cs index b43f457..890f431 100644 --- a/src/thirtytwo/Win32/System/Com/CustomComWrappers.cs +++ b/src/thirtytwo/Win32/System/Com/CustomComWrappers.cs @@ -59,11 +59,6 @@ internal static void PopulateIUnknown(IUnknown.Vtbl* vtable) CreateComInterfaceFlags.None); #endif - if (obj is IWrapperInitialize initialize) - { - initialize.OnInitialized(result); - } - return result; } diff --git a/src/thirtytwo/Win32/System/Com/IWrapperInitialize.cs b/src/thirtytwo/Win32/System/Com/IWrapperInitialize.cs deleted file mode 100644 index 8be0862..0000000 --- a/src/thirtytwo/Win32/System/Com/IWrapperInitialize.cs +++ /dev/null @@ -1,13 +0,0 @@ -// 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; - -/// -/// Implement to get called back after has generated the -/// for a managed object. -/// -internal unsafe interface IWrapperInitialize -{ - void OnInitialized(IUnknown* unknown); -} \ No newline at end of file diff --git a/src/thirtytwo/Win32/System/Com/ReflectPropertiesDispatch.cs b/src/thirtytwo/Win32/System/Com/ReflectPropertiesDispatch.cs index 35e6977..8e3b98d 100644 --- a/src/thirtytwo/Win32/System/Com/ReflectPropertiesDispatch.cs +++ b/src/thirtytwo/Win32/System/Com/ReflectPropertiesDispatch.cs @@ -9,7 +9,7 @@ namespace Windows.Win32.System.Com; /// /// Base class that provides an projection of all it's public properties. /// -public unsafe abstract class ReflectPropertiesDispatch : StandardDispatch +public unsafe abstract class ReflectPropertiesDispatch : UnknownDispatch { private readonly ClassPropertyDispatchAdapter _dispatchAdapter; diff --git a/src/thirtytwo/Win32/System/Com/StandardDispatch.cs b/src/thirtytwo/Win32/System/Com/StandardDispatch.cs index 9884c7f..d404fcd 100644 --- a/src/thirtytwo/Win32/System/Com/StandardDispatch.cs +++ b/src/thirtytwo/Win32/System/Com/StandardDispatch.cs @@ -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; @@ -11,93 +10,78 @@ namespace Windows.Win32.System.Com; /// Base class for providing services through a standard dispatch implementation /// generated from a type library. /// -public unsafe abstract class StandardDispatch : IDispatch.Interface, IDispatchEx.Interface, IWrapperInitialize, IDisposable +public unsafe abstract class StandardDispatch : 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? _standardDispatch; - - // StdOle32.tlb - private static readonly Guid s_stdole = new("00020430-0000-0000-C000-000000000046"); + private ITypeInfo* _typeInfo; /// - /// Construct an instance. This is useful as a replacement for types exposed as - /// and purely through . + /// Construct a new instance with the specified backing . /// - public StandardDispatch() : this(s_stdole, 2, 0, IUnknown.IID_Guid) + public StandardDispatch(ITypeInfo* typeInfo) { - } + if (typeInfo is null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } - /// - /// Construct a new instance from a registered type library. - /// - /// for the registered type library. - /// Type library major version. - /// Type library minor version. - /// The for the interface the derived class presents. - 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 typelib = new(null); - HRESULT hr = Interop.LoadRegTypeLib(_typeLibrary, _majorVersion, _minorVersion, 0, typelib); - hr.ThrowOnFailure(); - - using ComScope 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*)standard, takeOwnership: true); + _typeInfo = typeInfo; + _typeInfo->AddRef(); } - /// - /// Get the standard dispatch interface. - /// - private ComScope 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( @@ -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, @@ -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 @interface = new(ComHelpers.GetComPointer(this)); + return _typeInfo->Invoke(@interface, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, pArgErr); } HRESULT IDispatchEx.Interface.GetDispID(BSTR bstrName, uint grfdex, int* pid) @@ -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 @interface = new(ComHelpers.GetComPointer(this)); + return _typeInfo->Invoke(@interface, id, (DISPATCH_FLAGS)wFlags, pdp, pvarRes, pei, puArgErr: null); } protected virtual HRESULT Invoke( @@ -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; } } diff --git a/src/thirtytwo/Win32/System/Com/UnknownDispatch.cs b/src/thirtytwo/Win32/System/Com/UnknownDispatch.cs new file mode 100644 index 0000000..12d8407 --- /dev/null +++ b/src/thirtytwo/Win32/System/Com/UnknownDispatch.cs @@ -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; + +/// +/// Base class for . +/// +public unsafe abstract class UnknownDispatch : StandardDispatch +{ + // 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) { } +} \ No newline at end of file diff --git a/src/thirtytwo_tests/Win32/System/Com/StandardDispatchTests.cs b/src/thirtytwo_tests/Win32/System/Com/StandardDispatchTests.cs index 460c848..33699df 100644 --- a/src/thirtytwo_tests/Win32/System/Com/StandardDispatchTests.cs +++ b/src/thirtytwo_tests/Win32/System/Com/StandardDispatchTests.cs @@ -54,7 +54,7 @@ public class OleWindow : IOleWindow.Interface, IManagedWrapper [Fact] public void StandardDispatch_IUnknown() { - UnknownDispatch unknownDispatch = new(); + SimpleDispatch unknownDispatch = new(); using ComScope dispatch = new(ComHelpers.GetComPointer(unknownDispatch)); using ComScope dispatchEx = dispatch.TryQueryInterface(out HRESULT hr); @@ -125,13 +125,7 @@ public void StandardDispatch_IUnknown() name.ToStringAndFree().Should().Be("QueryInterface"); } - private class UnknownDispatch : StandardDispatch, IManagedWrapper + private class SimpleDispatch : UnknownDispatch, IManagedWrapper { - // 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) - { - } } }