Introduce QComObject base class

This base class implementation for COM objects provides IUnknown
interface implementation with reference counting which will allow to
keep all this functionality and implementation in the same place.

Pick-to 6.6 6.5

Change-Id: I8ec597b1040ac33295317e06338ffdcb61b78f85
Reviewed-by: Jøger Hansegård <joger.hansegard@qt.io>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Pavel Dubsky 2023-10-12 15:48:42 +02:00
parent cb841b449a
commit 3373247873
6 changed files with 422 additions and 0 deletions

View File

@ -540,6 +540,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

View File

@ -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 <QtCore/private/qglobal_p.h>
#if defined(Q_OS_WIN) || defined(Q_QDOC)
# include <QtCore/qt_windows.h>
QT_BEGIN_NAMESPACE
namespace QtPrivate {
template <typename... TInterfaces>
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<IMFSampleGrabberSinkCallback>
// {
// ...
// };
//
// namespace QtPrivate {
//
// template <>
// struct QComObjectTraits<IMFSampleGrabberSinkCallback>
// {
// static constexpr bool isGuidOf(REFIID riid) noexcept
// {
// return QComObjectTraits<IMFSampleGrabberSinkCallback, IMFClockStateSink>::isGuidOf(riid);
// }
// };
//
// }
template <typename TFirstInterface, typename... TAdditionalInterfaces>
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<IUnknown *>(static_cast<TFirstInterface *>(this));
AddRef();
return S_OK;
}
return tryQueryInterface<TFirstInterface, TAdditionalInterfaces...>(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 <typename TInterface, typename... TRest>
HRESULT tryQueryInterface(REFIID riid, void **ppvObject)
{
if (QtPrivate::QComObjectTraits<TInterface>::isGuidOf(riid)) {
*ppvObject = static_cast<TInterface *>(this);
AddRef();
return S_OK;
}
if constexpr (sizeof...(TRest) > 0)
return tryQueryInterface<TRest...>(riid, ppvObject);
*ppvObject = nullptr;
return E_NOINTERFACE;
}
std::atomic<LONG> m_referenceCount = 1;
};
QT_END_NAMESPACE
#endif // Q_OS_WIN
#endif // QCOMOBJECT_P_H

View File

@ -4,3 +4,6 @@
if(ANDROID)
add_subdirectory(android)
endif()
if(WIN32)
add_subdirectory(windows)
endif()

View File

@ -0,0 +1,4 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qcomobject)

View File

@ -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
)

View File

@ -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 <QTest>
#ifdef Q_OS_WIN
# include <private/qcomobject_p.h>
# include <wrl/client.h>
using Microsoft::WRL::ComPtr;
QT_BEGIN_NAMESPACE
template <typename T, typename... Args>
ComPtr<T> makeComObject(Args &&...args)
{
ComPtr<T> p;
// Don't use Attach because of MINGW64 bug
// #892 Microsoft::WRL::ComPtr::Attach leaks references
*p.GetAddressOf() = new T(std::forward<Args>(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<IDirect>
{
};
MIDL_INTERFACE("d05397e0-da7f-4055-8563-a5b80f095e6c")
IMultipleA : public IUnknown{};
MIDL_INTERFACE("67e298c5-ec5f-4c45-a779-bfba2484e142")
IMultipleB : public IUnknown{};
class MultipleComImplementation : public QComObject<IMultipleA, IMultipleB>
{
};
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<IDirect>
{
static constexpr bool isGuidOf(REFIID riid) noexcept
{
return QComObjectTraits<IDirect, IIntermediate>::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<ComImplementation> implementation = makeComObject<ComImplementation>();
ComPtr<IUnknown> 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<ComImplementation> implementation = makeComObject<ComImplementation>();
ComPtr<IDirect> 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<ComImplementation> implementation = makeComObject<ComImplementation>();
ComPtr<IIntermediate> 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<MultipleComImplementation> implementation =
makeComObject<MultipleComImplementation>();
ComPtr<IUnknown> 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<IMultipleA *>(implementation.Get()));
}
void tst_QComObject::QueryInterface_returnsConvertedPointer_whenFirstOfMultipleParentsIsRequested()
{
// Arrange
const ComPtr<MultipleComImplementation> implementation =
makeComObject<MultipleComImplementation>();
ComPtr<IMultipleA> 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<MultipleComImplementation> implementation =
makeComObject<MultipleComImplementation>();
ComPtr<IMultipleB> 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<ComImplementation> implementation = makeComObject<ComImplementation>();
ComPtr<IOther> 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<ComImplementation> implementation = makeComObject<ComImplementation>();
// Act
const HRESULT queryResult = implementation->QueryInterface(__uuidof(IUnknown), nullptr);
// Assert
QCOMPARE(queryResult, E_POINTER);
}
void tst_QComObject::QueryInterface_incrementsReferenceCount_whenConvertedPointerIsReturned()
{
// Arrange
const ComPtr<ComImplementation> implementation = makeComObject<ComImplementation>();
ComPtr<IUnknown> 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<ComImplementation> implementation = makeComObject<ComImplementation>();
// 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<ComImplementation> implementation = makeComObject<ComImplementation>();
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