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:
parent
cb841b449a
commit
3373247873
@ -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
|
||||
|
127
src/corelib/platform/windows/qcomobject_p.h
Normal file
127
src/corelib/platform/windows/qcomobject_p.h
Normal 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
|
@ -4,3 +4,6 @@
|
||||
if(ANDROID)
|
||||
add_subdirectory(android)
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_subdirectory(windows)
|
||||
endif()
|
||||
|
4
tests/auto/corelib/platform/windows/CMakeLists.txt
Normal file
4
tests/auto/corelib/platform/windows/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
add_subdirectory(qcomobject)
|
@ -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
|
||||
)
|
@ -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
|
Loading…
Reference in New Issue
Block a user