From 807c18f8c9326246f93c65d2f5f6204c25186668 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Tue, 27 Jan 2026 16:40:10 -0800 Subject: [PATCH 1/4] Fix DynamicInterfaceMap look-up This was originally fixed naively which resulted in an incomplete fix. The activation path issues remained and this current fix addresses the underlying issue - adding IDIC to __ComObject. --- src/coreclr/vm/methodtable.h | 5 ++--- src/tests/Interop/COM/NETClients/MiscTypes/Program.cs | 5 ++++- src/tests/Interop/COM/NativeServer/MiscTypesTesting.h | 6 ++++-- src/tests/Interop/COM/ServerContracts/Server.Contracts.cs | 6 +++++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index f8ca9962d209eb..c7bf1124772290 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1140,10 +1140,9 @@ class MethodTable // All ComObjects except for __ComObject // have dynamic Interface maps - return GetNumInterfaces() > 0 + return GetNumInterfaces() > 1 && IsComObjectType() - && !ParentEquals(g_pObjectClass) - && this != g_pBaseCOMObject; + && !ParentEquals(g_pObjectClass); } #endif // FEATURE_COMINTEROP diff --git a/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs b/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs index f01ddd3cfdad24..065f4e71f7c316 100644 --- a/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs +++ b/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs @@ -214,9 +214,12 @@ private static void ValidationTests() Console.WriteLine("-- Interfaces..."); { + Assert.True(new MiscTypesTestingClass() is Server.Contract.IInterface2); + Assert.True(Activator.CreateInstance(typeof(MiscTypesTestingClass)) is Server.Contract.IInterface2); + var interfaceMaybe = miscTypeTesting.Marshal_Interface(new InterfaceImpl()); - Assert.True(interfaceMaybe is Server.Contract.IInterface1); Assert.True(interfaceMaybe is Server.Contract.IInterface2); + Assert.True(interfaceMaybe is Server.Contract.IInterface1); } } diff --git a/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h b/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h index b2c71e5082c625..221fe6b55851d6 100644 --- a/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h +++ b/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h @@ -6,7 +6,7 @@ #include #include "Servers.h" -class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting +class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting, public IInterface2 { struct InterfaceImpl : public UnknownImpl, public IInterface2 { @@ -22,6 +22,8 @@ class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting DEFINE_REF_COUNTING(); }; +public: // IInterface1 +public: // IInterface2 public: // IMiscTypesTesting DEF_FUNC(Marshal_Variant)(_In_ VARIANT obj, _Out_ VARIANT* result) { @@ -59,7 +61,7 @@ class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting /* [in] */ REFIID riid, /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) { - return DoQueryInterface(riid, ppvObject, static_cast(this)); + return DoQueryInterface(riid, ppvObject, static_cast(this), static_cast(this), static_cast(this)); } DEFINE_REF_COUNTING(); diff --git a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs index a697d0359a2b62..c20c76a0ff0d02 100644 --- a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs +++ b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs @@ -184,6 +184,10 @@ string Add_BStr( void Pass_Through_LCID(out int lcid); } + public interface Interface0 + { + } + [ComVisible(true)] [Guid("4242A2F9-995D-4302-A722-02058CF58158")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] @@ -194,7 +198,7 @@ public interface IInterface1 [ComVisible(true)] [Guid("7AC820FE-E227-4C4D-A8B0-FCA68C459B43")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IInterface2 : IInterface1 + public interface IInterface2 : IInterface1, Interface0 { } From 6edacfda257fa53da0c13bbf03da9215ebab27fb Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 28 Jan 2026 10:46:10 -0800 Subject: [PATCH 2/4] Simplify test complexity for dynamic interface map. --- src/coreclr/vm/methodtable.h | 12 ++++++++---- .../COM/NETClients/MiscTypes/Program.cs | 7 +++---- .../COM/NativeClients/MiscTypes/MiscTypes.cpp | 7 +++---- .../COM/NativeServer/MiscTypesTesting.h | 18 ++++++++---------- .../COM/ServerContracts/Server.Contracts.cs | 11 ++--------- .../COM/ServerContracts/Server.Contracts.h | 7 +------ 6 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index c7bf1124772290..3087a28edc69d2 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1138,10 +1138,14 @@ class MethodTable { LIMITED_METHOD_DAC_CONTRACT; - // All ComObjects except for __ComObject - // have dynamic Interface maps - return GetNumInterfaces() > 1 - && IsComObjectType() + // All ComObjects except for __ComObject have dynamic Interface maps + // to avoid costly QIs on required managed interfaces. The __ComObject + // type is inserted automatically when a COM object enters the runtime. + // For example, an incoming COM interface pointer or activation via a + // COM coclass. See ComObject::SupportsInterface() for relevant use of + // the dynamic interface map. + return IsComObjectType() + && GetNumInterfaces() > g_pBaseCOMObject->GetNumInterfaces() && !ParentEquals(g_pObjectClass); } #endif // FEATURE_COMINTEROP diff --git a/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs b/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs index 065f4e71f7c316..597e155a50bc1e 100644 --- a/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs +++ b/src/tests/Interop/COM/NETClients/MiscTypes/Program.cs @@ -79,7 +79,7 @@ public static int TestEntryPoint() return 100; } - private class InterfaceImpl : Server.Contract.IInterface2 + private class InterfaceImpl : Server.Contract.IInterface1 { } @@ -214,11 +214,10 @@ private static void ValidationTests() Console.WriteLine("-- Interfaces..."); { - Assert.True(new MiscTypesTestingClass() is Server.Contract.IInterface2); - Assert.True(Activator.CreateInstance(typeof(MiscTypesTestingClass)) is Server.Contract.IInterface2); + Assert.True(new MiscTypesTestingClass() is Server.Contract.IInterface1); + Assert.True(Activator.CreateInstance(typeof(MiscTypesTestingClass)) is Server.Contract.IInterface1); var interfaceMaybe = miscTypeTesting.Marshal_Interface(new InterfaceImpl()); - Assert.True(interfaceMaybe is Server.Contract.IInterface2); Assert.True(interfaceMaybe is Server.Contract.IInterface1); } } diff --git a/src/tests/Interop/COM/NativeClients/MiscTypes/MiscTypes.cpp b/src/tests/Interop/COM/NativeClients/MiscTypes/MiscTypes.cpp index c85df4871c43df..8ea031624f912d 100644 --- a/src/tests/Interop/COM/NativeClients/MiscTypes/MiscTypes.cpp +++ b/src/tests/Interop/COM/NativeClients/MiscTypes/MiscTypes.cpp @@ -91,16 +91,15 @@ struct VariantMarshalTest class InterfaceImpl : public UnknownImpl, - public IInterface2 + public IInterface1 { public: // IInterface1 -public: // IInterface2 public: // IUnknown STDMETHOD(QueryInterface)( /* [in] */ REFIID riid, /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) { - return DoQueryInterface(riid, ppvObject, static_cast(this), static_cast(this)); + return DoQueryInterface(riid, ppvObject, static_cast(this)); } DEFINE_REF_COUNTING(); @@ -354,7 +353,7 @@ void ValidationTests() ComSmartPtr iface; iface.Attach(new InterfaceImpl()); - ComSmartPtr result; + ComSmartPtr result; HRESULT hr = miscTypesTesting->Marshal_Interface(iface, &result); THROW_IF_FAILED(hr); } diff --git a/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h b/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h index 221fe6b55851d6..a0b70aaec534fd 100644 --- a/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h +++ b/src/tests/Interop/COM/NativeServer/MiscTypesTesting.h @@ -6,24 +6,22 @@ #include #include "Servers.h" -class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting, public IInterface2 +class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting, public IInterface1 { - struct InterfaceImpl : public UnknownImpl, public IInterface2 + struct InterfaceImpl : public UnknownImpl, public IInterface1 { public: // IInterface1 - public: // IInterface2 public: // IUnknown STDMETHOD(QueryInterface)( /* [in] */ REFIID riid, /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) { - return DoQueryInterface(riid, ppvObject, static_cast(this), static_cast(this)); + return DoQueryInterface(riid, ppvObject, static_cast(this)); } DEFINE_REF_COUNTING(); }; public: // IInterface1 -public: // IInterface2 public: // IMiscTypesTesting DEF_FUNC(Marshal_Variant)(_In_ VARIANT obj, _Out_ VARIANT* result) { @@ -40,18 +38,18 @@ class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting, public II return E_NOTIMPL; } - DEF_FUNC(Marshal_Interface)(_In_ IUnknown* input, _Outptr_ IInterface2** value) + DEF_FUNC(Marshal_Interface)(_In_ IUnknown* input, _Outptr_ IInterface1** value) { HRESULT hr; - IInterface2* ifaceMaybe = nullptr; - hr = input->QueryInterface(__uuidof(IInterface2), (void**)&ifaceMaybe); + IInterface1* ifaceMaybe = nullptr; + hr = input->QueryInterface(__uuidof(IInterface1), (void**)&ifaceMaybe); if (FAILED(hr)) return hr; (void)ifaceMaybe->Release(); InterfaceImpl* inst = new InterfaceImpl(); - hr = inst->QueryInterface(__uuidof(IInterface2), (void**)value); + hr = inst->QueryInterface(__uuidof(IInterface1), (void**)value); (void)inst->Release(); return hr; } @@ -61,7 +59,7 @@ class MiscTypesTesting : public UnknownImpl, public IMiscTypesTesting, public II /* [in] */ REFIID riid, /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) { - return DoQueryInterface(riid, ppvObject, static_cast(this), static_cast(this), static_cast(this)); + return DoQueryInterface(riid, ppvObject, static_cast(this), static_cast(this)); } DEFINE_REF_COUNTING(); diff --git a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs index c20c76a0ff0d02..42bc327f6245af 100644 --- a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs +++ b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs @@ -191,14 +191,7 @@ public interface Interface0 [ComVisible(true)] [Guid("4242A2F9-995D-4302-A722-02058CF58158")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IInterface1 - { - } - - [ComVisible(true)] - [Guid("7AC820FE-E227-4C4D-A8B0-FCA68C459B43")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IInterface2 : IInterface1, Interface0 + public interface IInterface1 : Interface0 { } @@ -215,7 +208,7 @@ public interface IMiscTypesTesting void Marshal_ByRefVariant(ref object result, object value); [return: MarshalAs(UnmanagedType.Interface)] - IInterface2 Marshal_Interface([MarshalAs(UnmanagedType.Interface)] object inst); + IInterface1 Marshal_Interface([MarshalAs(UnmanagedType.Interface)] object inst); } public struct HResult diff --git a/src/tests/Interop/COM/ServerContracts/Server.Contracts.h b/src/tests/Interop/COM/ServerContracts/Server.Contracts.h index 76301fe0ae2b40..61a2c38884a225 100644 --- a/src/tests/Interop/COM/ServerContracts/Server.Contracts.h +++ b/src/tests/Interop/COM/ServerContracts/Server.Contracts.h @@ -371,11 +371,6 @@ IInterface1 : IUnknown { }; -struct __declspec(uuid("7AC820FE-E227-4C4D-A8B0-FCA68C459B43")) -IInterface2 : public IInterface1 -{ -}; - struct __declspec(uuid("7FBB8677-BDD0-4E5A-B38B-CA92A4555466")) IMiscTypesTesting : IUnknown { @@ -393,7 +388,7 @@ IMiscTypesTesting : IUnknown virtual HRESULT STDMETHODCALLTYPE Marshal_Interface ( /*[in]*/ IUnknown* value, - /*[out,ret]*/ IInterface2** iface) = 0; + /*[out,ret]*/ IInterface1** iface) = 0; }; struct __declspec(uuid("592386a5-6837-444d-9de3-250815d18556")) From 9275c4ec9bf3e8eab84bb11871e06cbf0d6fb127 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 28 Jan 2026 10:55:17 -0800 Subject: [PATCH 3/4] Add note on usage of Interface 0 --- src/tests/Interop/COM/ServerContracts/Server.Contracts.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs index 42bc327f6245af..88e4bcc0e4715e 100644 --- a/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs +++ b/src/tests/Interop/COM/ServerContracts/Server.Contracts.cs @@ -184,6 +184,8 @@ string Add_BStr( void Pass_Through_LCID(out int lcid); } + // This interface must not be an explicit COM interface to trigger + // the dynamic interface map codepath in ComObject. public interface Interface0 { } From d0f1c1fe0e978eeac60a7be36c2fe405fc51dadb Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Wed, 28 Jan 2026 11:12:32 -0800 Subject: [PATCH 4/4] Remove uses of IInterface2. --- src/tests/Interop/COM/NETServer/MiscTypesTesting.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tests/Interop/COM/NETServer/MiscTypesTesting.cs b/src/tests/Interop/COM/NETServer/MiscTypesTesting.cs index b9079b31b6c88b..766a768c197ad0 100644 --- a/src/tests/Interop/COM/NETServer/MiscTypesTesting.cs +++ b/src/tests/Interop/COM/NETServer/MiscTypesTesting.cs @@ -46,13 +46,13 @@ void Server.Contract.IMiscTypesTesting.Marshal_ByRefVariant(ref object result, o result = value; } - private class InterfaceImpl : Server.Contract.IInterface2 + private class InterfaceImpl : Server.Contract.IInterface1 { } - Server.Contract.IInterface2 Server.Contract.IMiscTypesTesting.Marshal_Interface(object inst) + Server.Contract.IInterface1 Server.Contract.IMiscTypesTesting.Marshal_Interface(object inst) { - if (inst is not Server.Contract.IInterface2) + if (inst is not Server.Contract.IInterface1) { throw new InvalidCastException(); }