diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 8ff0ef6961c..31b81734e86 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -523,6 +523,7 @@ qt_internal_extend_target(Core CONDITION WIN32 kernel/qwinregistry.cpp kernel/qwinregistry_p.h plugin/qsystemlibrary.cpp plugin/qsystemlibrary_p.h thread/qthread_win.cpp + platform/windows/qcomobject_p.h LIBRARIES advapi32 authz diff --git a/src/corelib/platform/windows/qcomobject_p.h b/src/corelib/platform/windows/qcomobject_p.h new file mode 100644 index 00000000000..5d8d407f791 --- /dev/null +++ b/src/corelib/platform/windows/qcomobject_p.h @@ -0,0 +1,127 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCOMOBJECT_P_H +#define QCOMOBJECT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#if defined(Q_OS_WIN) || defined(Q_QDOC) + +# include + +QT_BEGIN_NAMESPACE + +namespace QtPrivate { + +template +struct QComObjectTraits +{ + static constexpr bool isGuidOf(REFIID riid) noexcept + { + return ((riid == __uuidof(TInterfaces)) || ...); + } +}; + +} // namespace QtPrivate + +// NOTE: In order to be able to query the intermediate interface, i.e. the one you do not specify in +// QComObject interface list (TFirstInterface, TInterfaces) but that is a base for any of them +// (except IUnknown) you need to provide an explicit specialization of function +// QComObjectTraits<...>::isGuidOf for that type. For example, if you want to inherit interface +// IMFSampleGrabberSinkCallback which inherits IMFClockStateSink and you want to be able to query +// the latter one you need to provide this explicit specialization: +// +// class SinkCallback : public QComObject +// { +// ... +// }; +// +// namespace QtPrivate { +// +// template <> +// struct QComObjectTraits +// { +// static constexpr bool isGuidOf(REFIID riid) noexcept +// { +// return QComObjectTraits::isGuidOf(riid); +// } +// }; +// +// } + +template +class QComObject : public TFirstInterface, public TAdditionalInterfaces... +{ +public: + STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) final + { + if (!ppvObject) + return E_POINTER; + + if (riid == __uuidof(IUnknown)) { + *ppvObject = static_cast(static_cast(this)); + AddRef(); + + return S_OK; + } + + return tryQueryInterface(riid, ppvObject); + } + + STDMETHODIMP_(ULONG) AddRef() final { return ++m_referenceCount; } + + STDMETHODIMP_(ULONG) Release() final + { + const LONG referenceCount = --m_referenceCount; + if (referenceCount == 0) + delete this; + + return referenceCount; + } + +protected: + QComObject() = default; + + // Destructor is not public. Caller should call Release. + // Derived class should make its destructor private to force this behavior. + virtual ~QComObject() = default; + +private: + template + HRESULT tryQueryInterface(REFIID riid, void **ppvObject) + { + if (QtPrivate::QComObjectTraits::isGuidOf(riid)) { + *ppvObject = static_cast(this); + AddRef(); + + return S_OK; + } + + if constexpr (sizeof...(TRest) > 0) + return tryQueryInterface(riid, ppvObject); + + *ppvObject = nullptr; + + return E_NOINTERFACE; + } + + std::atomic m_referenceCount = 1; +}; + +QT_END_NAMESPACE + +#endif // Q_OS_WIN + +#endif // QCOMOBJECT_P_H diff --git a/tests/auto/corelib/platform/CMakeLists.txt b/tests/auto/corelib/platform/CMakeLists.txt index a47ffab8d36..5f127f4e916 100644 --- a/tests/auto/corelib/platform/CMakeLists.txt +++ b/tests/auto/corelib/platform/CMakeLists.txt @@ -4,3 +4,6 @@ if(ANDROID) add_subdirectory(android) endif() +if(WIN32) + add_subdirectory(windows) +endif() diff --git a/tests/auto/corelib/platform/windows/CMakeLists.txt b/tests/auto/corelib/platform/windows/CMakeLists.txt new file mode 100644 index 00000000000..24b2a69a0e5 --- /dev/null +++ b/tests/auto/corelib/platform/windows/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(qcomobject) diff --git a/tests/auto/corelib/platform/windows/qcomobject/CMakeLists.txt b/tests/auto/corelib/platform/windows/qcomobject/CMakeLists.txt new file mode 100644 index 00000000000..9cdd6b57bce --- /dev/null +++ b/tests/auto/corelib/platform/windows/qcomobject/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qcomobject Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qcomobject LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qcomobject + SOURCES + tst_qcomobject.cpp + LIBRARIES + Qt::CorePrivate +) diff --git a/tests/auto/corelib/platform/windows/qcomobject/tst_qcomobject.cpp b/tests/auto/corelib/platform/windows/qcomobject/tst_qcomobject.cpp new file mode 100644 index 00000000000..2bc20401b7b --- /dev/null +++ b/tests/auto/corelib/platform/windows/qcomobject/tst_qcomobject.cpp @@ -0,0 +1,268 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#ifdef Q_OS_WIN + +# include + +# include + +using Microsoft::WRL::ComPtr; + +QT_BEGIN_NAMESPACE + +template +ComPtr makeComObject(Args &&...args) +{ + ComPtr p; + // Don't use Attach because of MINGW64 bug + // #892 Microsoft::WRL::ComPtr::Attach leaks references + *p.GetAddressOf() = new T(std::forward(args)...); + return p; +} + +MIDL_INTERFACE("878fab04-7da0-41ea-9c49-058c7fa0d80a") +IIntermediate : public IUnknown{}; + +MIDL_INTERFACE("65a29ce9-191c-4182-9185-06dd70aafc5d") +IDirect : public IIntermediate{}; + +class ComImplementation : public QComObject +{ +}; + +MIDL_INTERFACE("d05397e0-da7f-4055-8563-a5b80f095e6c") +IMultipleA : public IUnknown{}; + +MIDL_INTERFACE("67e298c5-ec5f-4c45-a779-bfba2484e142") +IMultipleB : public IUnknown{}; + +class MultipleComImplementation : public QComObject +{ +}; + +MIDL_INTERFACE("b8278a1b-0c3b-4bbd-99db-1e8a141483fa") +IOther : public IUnknown{}; + +# ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IIntermediate, 0x878fab04, 0x7da0, 0x41ea, 0x9c, 0x49, 0x05, 0x8c, 0x7f, 0xa0, 0xd8, + 0x0a) +__CRT_UUID_DECL(IDirect, 0x65a29ce9, 0x191c, 0x4182, 0x91, 0x85, 0x06, 0xdd, 0x70, 0xaa, 0xfc, 0x5d) +__CRT_UUID_DECL(IMultipleA, 0xd05397e0, 0xda7f, 0x4055, 0x85, 0x63, 0xa5, 0xb8, 0x0f, 0x09, 0x5e, + 0x6c) +__CRT_UUID_DECL(IMultipleB, 0x67e298c5, 0xec5f, 0x4c45, 0xa7, 0x79, 0xbf, 0xba, 0x24, 0x84, 0xe1, + 0x42) +__CRT_UUID_DECL(IOther, 0xb8278a1b, 0x0c3b, 0x4bbd, 0x99, 0xdb, 0x1e, 0x8a, 0x14, 0x14, 0x83, 0xfa) +# endif + +namespace QtPrivate { + +template <> +struct QComObjectTraits +{ + static constexpr bool isGuidOf(REFIID riid) noexcept + { + return QComObjectTraits::isGuidOf(riid); + } +}; + +} // namespace QtPrivate + +class tst_QComObject : public QObject +{ + Q_OBJECT +private slots: + void QueryInterface_returnsConvertedPointer_whenIUnknownIsRequested(); + void QueryInterface_returnsConvertedPointer_whenDirectParentIsRequested(); + void QueryInterface_returnsConvertedPointer_whenDirectIntermediateIsRequested(); + void QueryInterface_returnsConvertedPointer_whenIUnknownOfMultipleParentsIsRequested(); + void QueryInterface_returnsConvertedPointer_whenFirstOfMultipleParentsIsRequested(); + void QueryInterface_returnsConvertedPointer_whenSecondOfMultipleParentsIsRequested(); + void QueryInterface_returnsNullPointer_whenNonParentIsRequested(); + void QueryInterface_returnsNullPointer_whenNullPointerIsPassedForReceivingObject(); + void QueryInterface_incrementsReferenceCount_whenConvertedPointerIsReturned(); + void AddRef_incrementsReferenceCountByOne(); + void Release_decrementsReferenceCountByOne(); +}; + +void tst_QComObject::QueryInterface_returnsConvertedPointer_whenIUnknownIsRequested() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + ComPtr unknown; + + // Act + const HRESULT queryResult = implementation->QueryInterface(__uuidof(IUnknown), &unknown); + + // Assert + QCOMPARE(queryResult, S_OK); + QCOMPARE(unknown.Get(), implementation.Get()); +} + +void tst_QComObject::QueryInterface_returnsConvertedPointer_whenDirectParentIsRequested() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + ComPtr direct; + + // Act + const HRESULT queryResult = implementation->QueryInterface(__uuidof(IDirect), &direct); + + // Assert + QCOMPARE(queryResult, S_OK); + QCOMPARE(direct.Get(), implementation.Get()); +} + +void tst_QComObject::QueryInterface_returnsConvertedPointer_whenDirectIntermediateIsRequested() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + ComPtr intermediate; + + // Act + const HRESULT queryResult = + implementation->QueryInterface(__uuidof(IIntermediate), &intermediate); + + // Assert + QCOMPARE(queryResult, S_OK); + QCOMPARE(intermediate.Get(), implementation.Get()); +} + +void tst_QComObject:: + QueryInterface_returnsConvertedPointer_whenIUnknownOfMultipleParentsIsRequested() +{ + // Arrange + const ComPtr implementation = + makeComObject(); + + ComPtr unknown; + + // Act + const HRESULT queryResult = implementation->QueryInterface(__uuidof(IUnknown), &unknown); + + // Assert + QCOMPARE(queryResult, S_OK); + + // Cast MultipleComImplementation to IMultipleA to prevent ambiguity + QCOMPARE(unknown.Get(), static_cast(implementation.Get())); +} + +void tst_QComObject::QueryInterface_returnsConvertedPointer_whenFirstOfMultipleParentsIsRequested() +{ + // Arrange + const ComPtr implementation = + makeComObject(); + + ComPtr multiple; + + // Act + const HRESULT queryResult = implementation->QueryInterface(__uuidof(IMultipleA), &multiple); + + // Assert + QCOMPARE(queryResult, S_OK); + QCOMPARE(multiple.Get(), implementation.Get()); +} + +void tst_QComObject::QueryInterface_returnsConvertedPointer_whenSecondOfMultipleParentsIsRequested() +{ + // Arrange + const ComPtr implementation = + makeComObject(); + + ComPtr multiple; + + // Act + const HRESULT queryResult = implementation->QueryInterface(__uuidof(IMultipleB), &multiple); + + // Assert + QCOMPARE(queryResult, S_OK); + QCOMPARE(multiple.Get(), implementation.Get()); +} + +void tst_QComObject::QueryInterface_returnsNullPointer_whenNonParentIsRequested() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + ComPtr other; + + // Act + const HRESULT queryResult = implementation->QueryInterface(__uuidof(IOther), &other); + + // Assert + QCOMPARE(queryResult, E_NOINTERFACE); + QCOMPARE(other.Get(), nullptr); +} + +void tst_QComObject::QueryInterface_returnsNullPointer_whenNullPointerIsPassedForReceivingObject() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + // Act + const HRESULT queryResult = implementation->QueryInterface(__uuidof(IUnknown), nullptr); + + // Assert + QCOMPARE(queryResult, E_POINTER); +} + +void tst_QComObject::QueryInterface_incrementsReferenceCount_whenConvertedPointerIsReturned() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + ComPtr unknown; + + // Act + implementation->QueryInterface(__uuidof(IUnknown), &unknown); + + // As there's no any way to get the current reference count of an object, just add one more + // reference and assert against cumulative reference count value + const ULONG referenceCount = implementation->AddRef(); + + // Assert + QCOMPARE(referenceCount, 3); +} + +void tst_QComObject::AddRef_incrementsReferenceCountByOne() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + // Act + const ULONG referenceCount1 = implementation->AddRef(); + const ULONG referenceCount2 = implementation->AddRef(); + + // Assert + QCOMPARE(referenceCount1, 2); + QCOMPARE(referenceCount2, 3); +} + +void tst_QComObject::Release_decrementsReferenceCountByOne() +{ + // Arrange + const ComPtr implementation = makeComObject(); + + implementation->AddRef(); + implementation->AddRef(); + + // Act + const ULONG referenceCount1 = implementation->Release(); + const ULONG referenceCount2 = implementation->Release(); + + // Assert + QCOMPARE(referenceCount1, 2); + QCOMPARE(referenceCount2, 1); +} + +QTEST_MAIN(tst_QComObject) +# include "tst_qcomobject.moc" + +QT_END_NAMESPACE + +#endif // Q_OS_WIN