From 267e378d1492862fe13b9b3243d8b2e1f399f9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8ger=20Hanseg=C3=A5rd?= Date: Thu, 27 Jun 2024 16:46:51 +0200 Subject: [PATCH] Simplify VARIANT handling using a QComVariant RAII wrapper The QComVariant RAII wrapper automates lifetime management of VARIANT structures and elements that it contains. For example, it will call IUnknown::AddRef if a COM interface is assigned to it. It will also clear all held resources at destruction. Task-number: QTBUG-126530 Change-Id: I543d236293d25cbc70ff25046e553351eccfb850 Reviewed-by: Oliver Wolff (cherry picked from commit 717580c1985dd0804363bafb8822f82dacfb3f90) Reviewed-by: Qt Cherry-pick Bot --- src/corelib/CMakeLists.txt | 1 + src/corelib/platform/windows/qcomvariant_p.h | 134 ++++++++++++ .../uiautomation/qwindowsuiamainprovider.cpp | 94 ++++----- .../qwindowsuiatextrangeprovider.cpp | 11 +- .../windows/uiautomation/qwindowsuiautils.cpp | 24 --- .../windows/uiautomation/qwindowsuiautils.h | 8 - .../corelib/platform/windows/CMakeLists.txt | 1 + .../windows/qcomvariant/CMakeLists.txt | 15 ++ .../windows/qcomvariant/tst_qcomvariant.cpp | 199 ++++++++++++++++++ 9 files changed, 401 insertions(+), 86 deletions(-) create mode 100644 src/corelib/platform/windows/qcomvariant_p.h create mode 100644 tests/auto/corelib/platform/windows/qcomvariant/CMakeLists.txt create mode 100644 tests/auto/corelib/platform/windows/qcomvariant/tst_qcomvariant.cpp diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 57b48ad3c07..ed3a544e3f3 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -563,6 +563,7 @@ qt_internal_extend_target(Core CONDITION WIN32 platform/windows/qcomobject_p.h platform/windows/qcomptr_p.h platform/windows/qbstr_p.h + platform/windows/qcomvariant_p.h LIBRARIES advapi32 authz diff --git a/src/corelib/platform/windows/qcomvariant_p.h b/src/corelib/platform/windows/qcomvariant_p.h new file mode 100644 index 00000000000..34ce5f179ce --- /dev/null +++ b/src/corelib/platform/windows/qcomvariant_p.h @@ -0,0 +1,134 @@ +// Copyright (C) 2024 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 QCOMVARIANT_P_H +#define QCOMVARIANT_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. +// + +#if defined(Q_OS_WIN) || defined(Q_QDOC) + +// clang-format off +#include +#include +#include +#include +// clang-format on + +QT_BEGIN_NAMESPACE + +struct QComVariant +{ + // clang-format off + QComVariant() noexcept + { + VariantInit(&m_variant); + } + + ~QComVariant() noexcept + { + clear(); + } + + explicit QComVariant(bool value) noexcept + { + m_variant.vt = VT_BOOL; + m_variant.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE; + } + + explicit QComVariant(int value) noexcept + { + m_variant.vt = VT_INT; + m_variant.intVal = value; + } + + explicit QComVariant(long value) noexcept + { + m_variant.vt = VT_I4; + m_variant.lVal = value; + } + + explicit QComVariant(double value) noexcept + { + m_variant.vt = VT_R8; + m_variant.dblVal = value; + } + + template + QComVariant(const ComPtr &value) noexcept + { + static_assert(std::is_base_of_v, "Invalid COM interface"); + ComPtr unknown = value; + m_variant.vt = VT_UNKNOWN; + m_variant.punkVal = unknown.Detach(); // Transfer ownership + } + + QComVariant(QBStr &&value) noexcept + { + m_variant.vt = VT_BSTR; + m_variant.bstrVal = value.release(); // Transfer ownership + } + + QComVariant(const QString &str) + { + m_variant.vt = VT_BSTR; + m_variant.bstrVal = QBStr{ str }.release(); // Transfer ownership of copy + } + + const VARIANT &get() const noexcept + { + return m_variant; + } + + VARIANT &get() noexcept + { + return m_variant; + } + // clang-format on + + [[nodiscard]] VARIANT *operator&() noexcept // NOLINT(google-runtime-operator) + { + clear(); + return &m_variant; + } + + VARIANT release() noexcept + { + const VARIANT detached{ m_variant }; + VariantInit(&m_variant); + return detached; + } + + QComVariant(const QComVariant &) = delete; + QComVariant &operator=(const QComVariant &) = delete; + QComVariant(QComVariant && other) = delete; + QComVariant &operator=(QComVariant &&) = delete; + +private: + + void clear() + { + const HRESULT hr = VariantClear(&m_variant); + Q_ASSERT(hr == S_OK); + Q_UNUSED(hr) + + VariantInit(&m_variant); // Clear value field + } + + VARIANT m_variant{}; +}; + +QT_END_NAMESPACE + +#endif // Q_OS_WIN + +#endif // QCOMVARIANT_P_H diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index e6d72bbe05d..56181caa215 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #if !defined(Q_CC_BOR) && !defined (Q_CC_GNU) #include @@ -85,14 +86,14 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve // Notifies states changes in checkboxes. if (accessible->role() == QAccessible::CheckBox) { if (auto provider = providerForAccessible(accessible)) { - VARIANT oldVal, newVal; - clearVariant(&oldVal); - int toggleState = ToggleState_Off; + long toggleState = ToggleState_Off; if (accessible->state().checked) toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On; - setVariantI4(toggleState, &newVal); + + QComVariant oldVal; + QComVariant newVal{toggleState}; UiaRaiseAutomationPropertyChangedEvent( - provider.Get(), UIA_ToggleToggleStatePropertyId, oldVal, newVal); + provider.Get(), UIA_ToggleToggleStatePropertyId, oldVal.get(), newVal.get()); } } } @@ -139,24 +140,18 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve if (event->value().typeId() == QMetaType::QString) { if (auto provider = providerForAccessible(accessible)) { // Notifies changes in string values. - VARIANT oldVal, newVal; - clearVariant(&oldVal); - setVariantString(event->value().toString(), &newVal); + const QComVariant oldVal; + const QComVariant newVal{ event->value().toString() }; UiaRaiseAutomationPropertyChangedEvent(provider.Get(), UIA_ValueValuePropertyId, - oldVal, newVal); - - HRESULT hr = VariantClear(&newVal); // Free string allocated by setVariantString - Q_ASSERT(hr == S_OK); - Q_UNUSED(hr) + oldVal.get(), newVal.get()); } } else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) { if (auto provider = providerForAccessible(accessible)) { // Notifies changes in values of controls supporting the value interface. - VARIANT oldVal, newVal; - clearVariant(&oldVal); - setVariantDouble(valueInterface->currentValue().toDouble(), &newVal); + const QComVariant oldVal; + const QComVariant newVal{ valueInterface->currentValue().toDouble() }; UiaRaiseAutomationPropertyChangedEvent( - provider.Get(), UIA_RangeValueValuePropertyId, oldVal, newVal); + provider.Get(), UIA_RangeValueValuePropertyId, oldVal.get(), newVal.get()); } } } @@ -169,12 +164,10 @@ void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event) // in order to avoid slowdowns with unnecessary notifications. if (accessible->role() == QAccessible::ComboBox) { if (auto provider = providerForAccessible(accessible)) { - VARIANT oldVal, newVal; - clearVariant(&oldVal); - setVariantString(accessible->text(QAccessible::Name), &newVal); - UiaRaiseAutomationPropertyChangedEvent(provider.Get(), UIA_NamePropertyId, oldVal, - newVal); - ::SysFreeString(newVal.bstrVal); + QComVariant oldVal; + QComVariant newVal{ accessible->text(QAccessible::Name) }; + UiaRaiseAutomationPropertyChangedEvent(provider.Get(), UIA_NamePropertyId, + oldVal.get(), newVal.get()); } } } @@ -424,7 +417,7 @@ void QWindowsUiaMainProvider::setAriaProperties(QAccessibleInterface *accessible } } - setVariantString(ariaString, pRetVal); + *pRetVal = QComVariant{ ariaString }.release(); } void QWindowsUiaMainProvider::setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal) @@ -449,8 +442,8 @@ void QWindowsUiaMainProvider::setStyle(QAccessibleInterface *accessible, VARIANT if (level < 1 || level > 9) return; - const int styleId = styleIdForHeadingLevel(level); - setVariantI4(styleId, pRetVal); + const long styleId = styleIdForHeadingLevel(level); + *pRetVal = QComVariant{ styleId }.release(); } int QWindowsUiaMainProvider::styleIdForHeadingLevel(int headingLevel) @@ -490,24 +483,24 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR switch (idProp) { case UIA_ProcessIdPropertyId: // PID - setVariantI4(int(GetCurrentProcessId()), pRetVal); + *pRetVal = QComVariant{ static_cast(GetCurrentProcessId()) }.release(); break; case UIA_AccessKeyPropertyId: // Accelerator key. - setVariantString(accessible->text(QAccessible::Accelerator), pRetVal); + *pRetVal = QComVariant{ accessible->text(QAccessible::Accelerator) }.release(); break; case UIA_AriaPropertiesPropertyId: setAriaProperties(accessible, pRetVal); break; case UIA_AutomationIdPropertyId: // Automation ID, which can be used by tools to select a specific control in the UI. - setVariantString(QAccessibleBridgeUtils::accessibleId(accessible), pRetVal); + *pRetVal = QComVariant{ QAccessibleBridgeUtils::accessibleId(accessible) }.release(); break; case UIA_ClassNamePropertyId: // Class name. if (QObject *o = accessible->object()) { QString className = QLatin1StringView(o->metaObject()->className()); - setVariantString(className, pRetVal); + *pRetVal = QComVariant{ className }.release(); } break; case UIA_DescribedByPropertyId: @@ -520,12 +513,12 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal); break; case UIA_FrameworkIdPropertyId: - setVariantString(QStringLiteral("Qt"), pRetVal); + *pRetVal = QComVariant{ QStringLiteral("Qt") }.release(); break; case UIA_ControlTypePropertyId: if (topLevelWindow) { // Reports a top-level widget as a window, instead of "custom". - setVariantI4(UIA_WindowControlTypeId, pRetVal); + *pRetVal = QComVariant{ UIA_WindowControlTypeId }.release(); } else { // Control type converted from role. auto controlType = roleToControlTypeId(accessible->role()); @@ -540,63 +533,66 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR if (controlType == UIA_EditControlTypeId && (!imModuleEmpty || nativeVKDisabled)) controlType = UIA_TextControlTypeId; - setVariantI4(controlType, pRetVal); + *pRetVal = QComVariant{ controlType }.release(); } break; case UIA_HelpTextPropertyId: - setVariantString(accessible->text(QAccessible::Help), pRetVal); + *pRetVal = QComVariant{ accessible->text(QAccessible::Help) }.release(); break; case UIA_HasKeyboardFocusPropertyId: if (topLevelWindow) { // Windows set the active state to true when they are focused - setVariantBool(accessible->state().active, pRetVal); + *pRetVal = QComVariant{ accessible->state().active ? true : false }.release(); } else { - setVariantBool(accessible->state().focused, pRetVal); + *pRetVal = QComVariant{ accessible->state().focused ? true : false }.release(); } break; case UIA_IsKeyboardFocusablePropertyId: if (topLevelWindow) { // Windows should always be focusable - setVariantBool(true, pRetVal); + *pRetVal = QComVariant{ true }.release(); } else { - setVariantBool(accessible->state().focusable, pRetVal); + *pRetVal = QComVariant{ accessible->state().focusable ? true : false }.release(); } break; case UIA_IsOffscreenPropertyId: - setVariantBool(accessible->state().offscreen, pRetVal); + *pRetVal = QComVariant{ accessible->state().offscreen ? true : false }.release(); break; case UIA_IsContentElementPropertyId: - setVariantBool(true, pRetVal); + *pRetVal = QComVariant{ true }.release(); break; case UIA_IsControlElementPropertyId: - setVariantBool(true, pRetVal); + *pRetVal = QComVariant{ true }.release(); break; case UIA_IsEnabledPropertyId: - setVariantBool(!accessible->state().disabled, pRetVal); + *pRetVal = QComVariant{ !accessible->state().disabled }.release(); break; case UIA_IsPasswordPropertyId: - setVariantBool(accessible->role() == QAccessible::EditableText - && accessible->state().passwordEdit, pRetVal); + *pRetVal = QComVariant{ accessible->role() == QAccessible::EditableText + && accessible->state().passwordEdit } + .release(); break; case UIA_IsPeripheralPropertyId: // True for peripheral UIs. if (QWindow *window = windowForAccessible(accessible)) { const Qt::WindowType wt = window->type(); - setVariantBool(wt == Qt::Popup || wt == Qt::ToolTip || wt == Qt::SplashScreen, pRetVal); + *pRetVal = QComVariant{ wt == Qt::Popup || wt == Qt::ToolTip || wt == Qt::SplashScreen } + .release(); } break; case UIA_IsDialogPropertyId: - setVariantBool(accessible->role() == QAccessible::Dialog - || accessible->role() == QAccessible::AlertMessage, pRetVal); + *pRetVal = QComVariant{ accessible->role() == QAccessible::Dialog + || accessible->role() == QAccessible::AlertMessage } + .release(); break; case UIA_FullDescriptionPropertyId: - setVariantString(accessible->text(QAccessible::Description), pRetVal); + *pRetVal = QComVariant{ accessible->text(QAccessible::Description) }.release(); break; case UIA_NamePropertyId: { QString name = accessible->text(QAccessible::Name); if (name.isEmpty() && topLevelWindow) name = QCoreApplication::applicationName(); - setVariantString(name, pRetVal); + *pRetVal = QComVariant{ name }.release(); break; } case UIA_StyleIdAttributeId: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp index 8604acff3e6..50dc1d0b35c 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp @@ -13,6 +13,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -157,15 +158,15 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTAT switch (attributeId) { case UIA_IsReadOnlyAttributeId: - setVariantBool(accessible->state().readOnly, pRetVal); + *pRetVal = QComVariant{ accessible->state().readOnly ? true : false }.release(); break; case UIA_CaretPositionAttributeId: if (textInterface->cursorPosition() == 0) - setVariantI4(CaretPosition_BeginningOfLine, pRetVal); + *pRetVal = QComVariant{ static_cast(CaretPosition_BeginningOfLine) }.release(); else if (textInterface->cursorPosition() == textInterface->characterCount()) - setVariantI4(CaretPosition_EndOfLine, pRetVal); + *pRetVal = QComVariant{ static_cast(CaretPosition_EndOfLine) }.release(); else - setVariantI4(CaretPosition_Unknown, pRetVal); + *pRetVal = QComVariant{ static_cast(CaretPosition_Unknown) }.release(); break; case UIA_StrikethroughStyleAttributeId: { @@ -173,7 +174,7 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTAT if (value.isEmpty()) break; const TextDecorationLineStyle uiaLineStyle = uiaLineStyleForIA2LineStyle(value); - setVariantI4(uiaLineStyle, pRetVal); + *pRetVal = QComVariant{ static_cast(uiaLineStyle) }.release(); break; } default: diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp index d8b788bc5a9..91e9cf53d31 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp @@ -63,30 +63,6 @@ void clearVariant(VARIANT *variant) variant->punkVal = nullptr; } -void setVariantI4(int value, VARIANT *variant) -{ - variant->vt = VT_I4; - variant->lVal = value; -} - -void setVariantBool(bool value, VARIANT *variant) -{ - variant->vt = VT_BOOL; - variant->boolVal = value ? -1 : 0; -} - -void setVariantDouble(double value, VARIANT *variant) -{ - variant->vt = VT_R8; - variant->dblVal = value; -} - -void setVariantString(const QString &value, VARIANT *variant) -{ - variant->vt = VT_BSTR; - variant->bstrVal = QBStr(value).release(); -} - // Scales a rect to native coordinates, according to high dpi settings. void rectToNativeUiaRect(const QRect &rect, const QWindow *w, UiaRect *uiaRect) { diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h index f4c0781f427..670d37408ed 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h @@ -33,14 +33,6 @@ bool isTextUnitSeparator(TextUnit unit, const QChar &ch); void clearVariant(VARIANT *variant); -void setVariantI4(int value, VARIANT *variant); - -void setVariantBool(bool value, VARIANT *variant); - -void setVariantDouble(double value, VARIANT *variant); - -void setVariantString(const QString &value, VARIANT *variant); - } // namespace QWindowsUiAutomation QT_END_NAMESPACE diff --git a/tests/auto/corelib/platform/windows/CMakeLists.txt b/tests/auto/corelib/platform/windows/CMakeLists.txt index 7fe58b2ea2c..2c6d6e9ffee 100644 --- a/tests/auto/corelib/platform/windows/CMakeLists.txt +++ b/tests/auto/corelib/platform/windows/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(qcomobject) add_subdirectory(qbstr) +add_subdirectory(qcomvariant) diff --git a/tests/auto/corelib/platform/windows/qcomvariant/CMakeLists.txt b/tests/auto/corelib/platform/windows/qcomvariant/CMakeLists.txt new file mode 100644 index 00000000000..26895c170e4 --- /dev/null +++ b/tests/auto/corelib/platform/windows/qcomvariant/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +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_qcomvariant + SOURCES + tst_qcomvariant.cpp + LIBRARIES + Qt::CorePrivate +) diff --git a/tests/auto/corelib/platform/windows/qcomvariant/tst_qcomvariant.cpp b/tests/auto/corelib/platform/windows/qcomvariant/tst_qcomvariant.cpp new file mode 100644 index 00000000000..1e781101a85 --- /dev/null +++ b/tests/auto/corelib/platform/windows/qcomvariant/tst_qcomvariant.cpp @@ -0,0 +1,199 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include + +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +QT_USE_NAMESPACE + +using namespace Qt::StringLiterals; + +struct IUnknownStub : QComObject +{ + ULONG refCount() + { + AddRef(); + return Release(); + } +}; + +extern "C" { +void __cdecl SetOaNoCache(void); +} + +class tst_qcomvariant : public QObject +{ + Q_OBJECT + +public slots: + void initTestCase() + { + SetOaNoCache(); + } + +private slots: + + static void constructor_initializesToEmpty() + { + const QComVariant var; + QCOMPARE_EQ(var.get().vt, VT_EMPTY); + } + + static void constructor_initializes_withBool() + { + QComVariant falseVal{ false }; + QCOMPARE_EQ(falseVal.get().vt, VT_BOOL); + QCOMPARE_EQ(falseVal.get().boolVal, VARIANT_FALSE); + + QComVariant trueVal{ true }; + QCOMPARE_EQ(trueVal.get().vt, VT_BOOL); + QCOMPARE_EQ(trueVal.get().boolVal, VARIANT_TRUE); + } + + static void constructor_initializes_withInt() + { + QComVariant var{ 3 }; + QCOMPARE_EQ(var.get().vt, VT_INT); + QCOMPARE_EQ(var.get().intVal, 3); + } + + static void constructor_initializes_withLong() + { + QComVariant var{ 4l }; + QCOMPARE_EQ(var.get().vt, VT_I4); + QCOMPARE_EQ(var.get().lVal, 4); + } + + static void constructor_initializes_withDouble() + { + QComVariant var{ 3.14 }; + QCOMPARE_EQ(var.get().vt, VT_R8); + QVERIFY(qFuzzyCompare(var.get().dblVal, 3.14)); + } + + static void constructor_initializes_withQBStr() + { + QComVariant var{ QBStr{ L"Hello world!" } }; + QCOMPARE_EQ(var.get().vt, VT_BSTR); + QCOMPARE_EQ(var.get().bstrVal, QStringView(L"Hello world!")); + } + + static void constructor_initializesWithNullQBStr() + { + QComVariant var{ QBStr{ nullptr } }; + QCOMPARE_EQ(var.get().vt, VT_BSTR); + QCOMPARE_EQ(var.get().bstrVal, nullptr); + } + + static void constructor_initializes_withQString() + { + QComVariant var{ "Hello world!"_L1 }; + QCOMPARE_EQ(var.get().vt, VT_BSTR); + QCOMPARE_EQ(var.get().bstrVal, QStringView(L"Hello world!")); + } + + static void constructor_initializesWithNullQString() + { + QComVariant var{ QString{} }; + QCOMPARE_EQ(var.get().vt, VT_BSTR); + QCOMPARE_EQ(var.get().bstrVal, nullptr); + } + + static void constructor_initializesWithEmptyQString() + { + QComVariant var{ QString{ ""_L1 } }; + QCOMPARE_EQ(var.get().vt, VT_BSTR); + QCOMPARE_EQ(var.get().bstrVal, ""_L1); + } + + static void constructor_initializes_withIUnknown() + { + const ComPtr value = makeComObject(); + + QComVariant var{ value }; + + QCOMPARE_EQ(var.get().vt, VT_UNKNOWN); + QCOMPARE_EQ(var.get().punkVal, value.Get()); + } + + static void constructor_initializes_withNullptrIUnknown() + { + ComPtr ptr; + QComVariant var{ ptr }; + + QCOMPARE_EQ(var.get().vt, VT_UNKNOWN); + QCOMPARE_EQ(var.get().punkVal, nullptr); + } + + static void addressOfOperator_clearsVariant_whenVariantOwnsIUnknown() + { + // Arrange + ComPtr value = makeComObject(); + + QComVariant var{ value }; + QCOMPARE_EQ(value->refCount(), 2); + + // Act + const VARIANT *ptr = &var; + + // Assert + QCOMPARE_EQ(value->refCount(), 1); + QCOMPARE_EQ(ptr->vt, VT_EMPTY); + QCOMPARE_EQ(ptr->punkVal, nullptr); + } + + static void destructor_clearsVariant_whenOwningIUnknown() + { + // Arrange + const ComPtr value = makeComObject(); + + { + QComVariant var{ value }; + QCOMPARE_EQ(value->refCount(), 2); + // Act (run destructor) + } + + // Assert + QCOMPARE_EQ(value->refCount(), 1); + } + + static void destructor_doesNotCrash_whenOwningBSTR() + { + // Arrange + QComVariant var{ QBStr{ L"Hello world!" } }; + + // Act (run destructor) + } + + static void release_releasesOwnershipAndReturnsVariant_whenOwningIUnknown() + { + // Arrange + const ComPtr value = makeComObject(); + + VARIANT detached{}; + { + QComVariant var{ value }; + QCOMPARE_EQ(value->refCount(), 2); + + // Act (Release and run destructor) + detached = var.release(); + } + + // Assert + QCOMPARE_EQ(value->refCount(), 2); + QCOMPARE_EQ(detached.vt, VT_UNKNOWN); + QCOMPARE_EQ(detached.punkVal, value.Get()); + + // Cleanup + QCOMPARE_EQ(VariantClear(&detached), S_OK); + } + +}; + +QTEST_MAIN(tst_qcomvariant) +#include "tst_qcomvariant.moc"