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)
- {
- }
}
}